Krush iOS應(yīng)用架構(gòu)

為避免撕逼,提前聲明:本文純屬翻譯,僅僅是為了學(xué)習(xí),加上水平有限,見諒!

【原文】https://www.teehanlax.com/blog/krush-ios-architecture/

Krush iOS應(yīng)用架構(gòu)

在Teehan+Lax公司,到目前為止,我們已經(jīng)著手開發(fā)Krush項(xiàng)目好幾個(gè)月了。從iOS架構(gòu)上講Krush是一個(gè)非常有趣的應(yīng)用,因
為它會(huì)涉及到很多iOS新手都會(huì)遇見的常見誤區(qū)。特別是,需要訪問API,擁有磁盤緩存,展示感興趣內(nèi)容的聯(lián)網(wǎng)應(yīng)用。在這篇文章中,我將會(huì)探討一些關(guān)于應(yīng)用方面的案例研究:為什么我們選擇特定的方法,如何在實(shí)踐中應(yīng)用,以及事后我們應(yīng)該怎么做。

在90天內(nèi),我們把Krush作為最小化可行產(chǎn)品(MVP:minimum viable product)推出,所以,“為什么”我們選擇特定方法的動(dòng)機(jī)主要是基于速度:如何快速的獲取能向市場(chǎng)推出的特性和功能最小的集合所組成的可測(cè)試版本,并且在此后能多快的進(jìn)行迭代?這些動(dòng)機(jī)影響了我們做的決定,所以,如果你的動(dòng)機(jī)不同,你可以通過這個(gè)鏡頭(lens)看待我們的決定。


案例研究 1:網(wǎng)絡(luò)層

網(wǎng)絡(luò)層主要由我的天才同事Brendan Lynch構(gòu)建。網(wǎng)絡(luò)層主要負(fù)責(zé)處理Krush發(fā)送的所有連接,他們調(diào)用服務(wù)器API或者CDN進(jìn)行資源傳輸。所有的東西都使用一個(gè)公共接口。

我們選擇使用更熟悉了網(wǎng)絡(luò)操作技術(shù),而不是使用像NSURLSession這樣的新API。更具體就是,使用屬于我們應(yīng)用委托的請(qǐng)求客戶端管理所有的網(wǎng)絡(luò)活動(dòng)。這個(gè)請(qǐng)求客戶端持有一個(gè)NSOperationQueue,這是管理你網(wǎng)絡(luò)請(qǐng)求的隊(duì)列。

網(wǎng)絡(luò)請(qǐng)求包含URL、參數(shù)和認(rèn)證編碼規(guī)范。請(qǐng)求對(duì)象知道如何構(gòu)造認(rèn)證NSURLRequests,在連接請(qǐng)求失敗的情況下重新建立請(qǐng)求。網(wǎng)絡(luò)請(qǐng)會(huì)求子類化NSOperation并遵守NSURLConnectionDataDelegate 協(xié)議。

如果網(wǎng)絡(luò)請(qǐng)求失敗或者超時(shí),請(qǐng)求客戶端將自動(dòng)重新排隊(duì),這樣會(huì)嘗試數(shù)次,如果還是無法連接,那就徹底失敗了。

每個(gè)操作都有回調(diào)塊(block)。當(dāng)一個(gè)操作完成或失敗后,塊(block)會(huì)被調(diào)用,同時(shí)傳遞網(wǎng)絡(luò)返回的數(shù)據(jù)或操作的結(jié)果。下節(jié)我們會(huì)提及在請(qǐng)求客戶端中定義的回調(diào)塊(block),它會(huì)把數(shù)據(jù)轉(zhuǎn)存到磁盤緩存中。

在實(shí)際中,這個(gè)網(wǎng)絡(luò)架構(gòu)可以正常工作。當(dāng)一個(gè)請(qǐng)求失敗后,它會(huì)自動(dòng)重啟,所以,我們的應(yīng)用非常健壯。而不是使用新的iOS7 API,通過使用熟知的方法,我們可以快速的把產(chǎn)品推出來。

如果我們必須從頭來過,為了減少代碼量并利用iOS 7的后臺(tái)fetch API,研究一下NSURLSession還是很值得的。我還想探討一下把來自視圖控制器的命令通過響應(yīng)者鏈發(fā)送到應(yīng)用委托中的想法,然后把他們發(fā)送到請(qǐng)求客戶端中。通過這種方式我們可以完全解耦視圖控制器和請(qǐng)求客戶端。


案例研究 2:磁盤上的緩存

Krush是一個(gè)視覺化的應(yīng)用,他下載并展示很多圖片。一旦這些圖片從JPEG格式解壓成展示用的位圖,將會(huì)占用大量的內(nèi)存。在內(nèi)存中保存應(yīng)用的所有內(nèi)容不是一個(gè)好的選擇,但是在每次展示時(shí)都去下載可能會(huì)占用用戶大量的網(wǎng)絡(luò)資源。這個(gè)問題的解決辦法是使用磁盤緩存。

對(duì)于可讀性,Brendan使用他熟悉的SQLite構(gòu)建了磁盤存儲(chǔ)系統(tǒng)。然而,在我構(gòu)建磁盤緩存的時(shí)候,他正在忙于構(gòu)建網(wǎng)絡(luò)層,而且我的SQLite功底很弱。所以,我使用了我熟悉的Core Data

Core Data不是一個(gè)對(duì)象持久化庫,而是一個(gè)能數(shù)據(jù)持久化到磁盤存儲(chǔ)器中的對(duì)象圖管理框架。我們把它當(dāng)做一個(gè)緩存;應(yīng)用的每次啟動(dòng)都會(huì)刪除存儲(chǔ)的數(shù)據(jù)。

應(yīng)用啟動(dòng)時(shí)應(yīng)用最重要的方面之一。如果應(yīng)用沒有在一個(gè)合理的時(shí)間內(nèi)啟動(dòng)的話,用戶可能會(huì)放棄這個(gè)應(yīng)用的。至于Krush,我們正在收集來自用戶的反饋,并且客戶端應(yīng)用程序啟動(dòng)很慢。(⊙o⊙)…

我打開Instruments并在設(shè)備上測(cè)試了一下應(yīng)用的啟動(dòng)時(shí)間。

哦~,應(yīng)用創(chuàng)建了很多網(wǎng)絡(luò)連接啊。在一次跟蹤應(yīng)用第一次啟動(dòng)的時(shí)候,我測(cè)出了170個(gè)網(wǎng)絡(luò)請(qǐng)求。這表明我們提前創(chuàng)建了很多請(qǐng)求而不是按照需要?jiǎng)?chuàng)建。我增加了按需請(qǐng)求網(wǎng)絡(luò)情況使得網(wǎng)絡(luò)請(qǐng)求不那么樂觀,這很容易改變。然而,這個(gè)改變導(dǎo)致了大量的界面卡頓。之后,我又測(cè)試了一遍。

我們發(fā)布的Krush使用了很簡單的Core Data緩存,因?yàn)槲覀儧]有過多的時(shí)間投入到更復(fù)雜的東西上。堆棧有主線程上的單個(gè)管理對(duì)象上下文組成。不管怎樣,我從來都不喜歡過早的進(jìn)行優(yōu)化;相比與此,我更喜歡測(cè)試——調(diào)整——測(cè)試這樣的開發(fā)節(jié)奏。當(dāng)我測(cè)試界面卡頓的時(shí)候,我立即就發(fā)現(xiàn)了問題:Core Data在主線程上阻塞了。

我做了些研究后最終決定采取不同的方式。請(qǐng)求客戶端實(shí)例持有一個(gè)在自己隊(duì)列中運(yùn)行的后臺(tái)上下文;后臺(tái)隊(duì)列和主線程隊(duì)列共用一個(gè)持久化存儲(chǔ)協(xié)調(diào)器(Persistent Store Coordinator)。

我們來看一個(gè)獲取用戶詳情的網(wǎng)絡(luò)請(qǐng)求示例。

用戶對(duì)象已經(jīng)存在于主管理對(duì)象上下文中,但是不需要后臺(tái)上下文。為了保證對(duì)象已經(jīng)存在于持久存儲(chǔ)器中,我們必須保存主上下文。然后,從用戶信息中獲取對(duì)象id(objectId),并在網(wǎng)絡(luò)請(qǐng)求的回調(diào)塊(block)中從后臺(tái)上下文獲取相應(yīng)的的用戶對(duì)象。在后臺(tái)線程,我們執(zhí)行JSON解析,并在后臺(tái)上下文中形成用戶和其他對(duì)象之間的關(guān)系。最后,保存后臺(tái)上下文,這會(huì)出發(fā)一個(gè)通知去把后臺(tái)上下問的改變合并到主上下文中。相應(yīng)的視圖也會(huì)通過KVO進(jìn)行更新。呼~。

結(jié)果很令人振奮。我們顯著的改善了啟動(dòng)時(shí)間,并且整個(gè)界面頁面的極為流暢。

理論上,我們做的所有改變都應(yīng)該在后臺(tái)管理對(duì)象向下文中進(jìn)行。如果我必須重做的個(gè)方案的話,我會(huì)把主管理對(duì)象上下文模型實(shí)例設(shè)置為只讀(語義上的)并且只能在后臺(tái)上下文中執(zhí)行更新。通過這種方式,可以避免在我需要在后臺(tái)訪問對(duì)象的時(shí)候必須先保存主上下文的麻煩。

這個(gè)經(jīng)驗(yàn)教訓(xùn)告訴我們一定要對(duì)應(yīng)用啟動(dòng)進(jìn)行測(cè)試。優(yōu)化界面和啟動(dòng)時(shí)間僅僅只是耗費(fèi)了幾天時(shí)間。如果我們?cè)侔l(fā)布之前投入了這幾天做優(yōu)化,我們可能會(huì)在應(yīng)用啟動(dòng)的時(shí)候獲得更加流暢的用戶體驗(yàn),而不是在迭代之后。


案例研究 3:用戶信息視圖

Krush的用戶信息是一個(gè)復(fù)雜的東西。不管是從設(shè)計(jì)的角度還是從編碼的角度來看,正確的處理是很重要的。我們看到的設(shè)計(jì)中有三個(gè)tabKrushes,InfluenceNetwork。

image

不僅如此,這些tab需要進(jìn)行模塊化,因?yàn)閷?duì)于一個(gè)品牌的用戶頁,我們可能會(huì)需要不同的tabs。這是一個(gè)很有趣的架構(gòu)問題;如何以模塊化的方式重用代碼?

我們可以使用子視圖控制器,但是我更想嘗試一下數(shù)據(jù)驅(qū)動(dòng)。我僅僅只使用了一個(gè)由UITableViewController控制的table view。這個(gè)控制器強(qiáng)引用了一個(gè)遵守協(xié)議的datasource。

當(dāng)選中不同的tab時(shí),數(shù)據(jù)源就會(huì)發(fā)生改變。此外,當(dāng)數(shù)據(jù)元發(fā)生改變時(shí),table view也會(huì)重新加載?,F(xiàn)在,當(dāng)table view詢問控制器應(yīng)該展示什么的時(shí)候,它則會(huì)去查詢數(shù)據(jù)源。

數(shù)據(jù)源用來填充我們自己寫的選項(xiàng)卡選擇控件。不同的數(shù)據(jù)源是否可用,取決于新式的用戶是否是一個(gè)品牌。通過使用ReactiveCocoa,我們可以在viewDidLoad方法中獲取視圖控制器中數(shù)據(jù)源的狀態(tài)。沒有把布局方面的考量委托給數(shù)據(jù)源,這使得我們的table view控制器的邏輯非常簡單。

每一個(gè)數(shù)據(jù)源竇澤提過像列表行數(shù),行高,定制個(gè)別行cell等這樣的信息。每一個(gè)數(shù)據(jù)源還有一個(gè)類屬性和重用標(biāo)識(shí),這個(gè)重用標(biāo)識(shí)用來在viewDidLoad方法中注冊(cè)自定義的UITableViewCell子類。最后,每一個(gè)數(shù)據(jù)源也負(fù)責(zé)暴露將觸發(fā)tableview重載的ReactiveCocoa信號(hào)。

當(dāng)設(shè)計(jì)在項(xiàng)目的迭代中發(fā)生改變時(shí),這種數(shù)據(jù)源方法也很有效。它仍然保持著代碼的簡潔,解耦。使用這種方法的一個(gè)缺點(diǎn)是,當(dāng)把Network選項(xiàng)卡中的各個(gè)設(shè)計(jì)整合到Krushes選項(xiàng)卡中后,在不同的數(shù)據(jù)源間共享相同的邏輯就變得不是那么輕松了。我希望Objective-C在語言層面支持抽象類,因?yàn)檫@樣就可以有助于減少數(shù)據(jù)源對(duì)象之間的重復(fù)代碼。


案例研究 4:Feed模塊中的MVVM

早期發(fā)布的應(yīng)用版本有一個(gè)簡單地反饋頁面和簡單地用戶培訓(xùn)教程。當(dāng)我們把它演示給同事時(shí),大家都認(rèn)為教程部分是最初用戶體驗(yàn)的一個(gè)弱點(diǎn)。Geoff建議在用戶第一次啟動(dòng)應(yīng)用的時(shí)候整合信息卡片到feed模塊中,向用戶展示如何使用應(yīng)用。這樣,他們?cè)诳梢允褂脩?yīng)用之前就不需要記住教程中的用法說明。

在那時(shí),feed視圖控制器使用一個(gè)NSFetchedResultsController去展示存儲(chǔ)在Core Data中的內(nèi)容。不是把新用戶培訓(xùn)卡邏輯整合到feed視圖控制器中,我更想探索一下一個(gè)新出現(xiàn)的Objective-C架構(gòu)模式:Model-View-ViewModel

簡而言之,我們把視圖控制器中展示所有內(nèi)容的邏輯抽象到視圖模型(view model)中,這對(duì)實(shí)際的UI是不可知的。視圖模型只是提供Endorse和Save按鈕是否顯示或者用于特定列表單元格的圖片等信息。我們也會(huì)把fetched results controller的協(xié)議代碼從視圖控制器中移動(dòng)到視圖模型(view model)中,這些代碼會(huì)把培訓(xùn)模型插入到它所維護(hù)的內(nèi)部數(shù)組中。

當(dāng)用戶達(dá)到feed的終點(diǎn)獲取跟多數(shù)據(jù)或者當(dāng)用戶下拉刷新的時(shí)候,視圖模型也會(huì)接收到通知。

當(dāng)我們把話題標(biāo)簽整合到應(yīng)用中時(shí),這種架構(gòu)很有效。使用相同的視圖控制器僅僅是視圖模型和展示邏輯不同。通過讓不同的視圖模型遵守視圖控制器可以依賴的相同協(xié)議,我們能夠讓視圖控制器不需要知道它展示什么東西,以及如何展示的。

很高興這種方法對(duì)我們很有用。如果必須重做,我會(huì)盡力減少視圖模型之間的重復(fù)代碼。同樣的,抽象類也可以在這兒起作用。


結(jié)論

在Teehan+Lax,這個(gè)項(xiàng)目令我們興奮異常。在整個(gè)項(xiàng)目期間,我們學(xué)到了很多并且還在開發(fā)期間獲得了諸多樂趣。我想通過分享一些在項(xiàng)目期間的經(jīng)驗(yàn)教訓(xùn),開發(fā)者可以開發(fā)出屬于他們自己的了不起的應(yīng)用。請(qǐng)叫我雷鋒!

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容