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

為避免撕逼嗽交,提前聲明:本文純屬翻譯花颗,僅僅是為了學(xué)習(xí)卖哎,加上水平有限,見(jiàn)諒录淡!

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

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

在Teehan+Lax公司捌木,到目前為止,我們已經(jīng)著手開(kāi)發(fā)Krush項(xiàng)目好幾個(gè)月了赁咙。從iOS架構(gòu)上講Krush是一個(gè)非常有趣的應(yīng)用钮莲,因
為它會(huì)涉及到很多iOS新手都會(huì)遇見(jiàn)的常見(jiàn)誤區(qū)。特別是彼水,需要訪問(wèn)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ī)不同玄柠,你可以通過(guò)這個(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ù)次礼预,如果還是無(wú)法連接,那就徹底失敗了虏劲。

每個(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扮休,通過(guò)使用熟知的方法,我們可以快速的把產(chǎn)品推出來(lái)拴鸵。

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


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

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

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

Core Data不是一個(gè)對(duì)象持久化庫(kù)萨醒,而是一個(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)用沒(méi)有在一個(gè)合理的時(shí)間內(nèi)啟動(dòng)的話,用戶可能會(huì)放棄這個(gè)應(yīng)用的晓褪。至于Krush堵漱,我們正在收集來(lái)自用戶的反饋,并且客戶端應(yīng)用程序啟動(dòng)很慢辞州。(⊙o⊙)…

我打開(kāi)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)求不那么樂(lè)觀岛杀,這很容易改變。然而崭孤,這個(gè)改變導(dǎo)致了大量的界面卡頓类嗤。之后,我又測(cè)試了一遍辨宠。

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

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

我們來(lái)看一個(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)上下問(wèn)的改變合并到主上下文中。相應(yīng)的視圖也會(huì)通過(guò)KVO進(jìn)行更新遮咖。呼~滩字。

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

理論上,我們做的所有改變都應(yīng)該在后臺(tái)管理對(duì)象向下文中進(jìn)行陶珠。如果我必須重做的個(gè)方案的話挟裂,我會(huì)把主管理對(duì)象上下文模型實(shí)例設(shè)置為只讀(語(yǔ)義上的)并且只能在后臺(tái)上下文中執(zhí)行更新。通過(guò)這種方式揍诽,可以避免在我需要在后臺(tái)訪問(wèn)對(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ì)的角度還是從編碼的角度來(lái)看,正確的處理是很重要的根资。我們看到的設(shè)計(jì)中有三個(gè)tabKrushes架专,InfluenceNetwork

image

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

我們可以使用子視圖控制器,但是我更想嘗試一下數(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ì)重新加載⊥現(xiàn)在,當(dāng)table view詢問(wèn)控制器應(yīng)該展示什么的時(shí)候戳吝,它則會(huì)去查詢數(shù)據(jù)源浩销。

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

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

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


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

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

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

簡(jiǎn)而言之炊苫,我們把視圖控制器中展示所有內(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)很有效。使用相同的視圖控制器僅僅是視圖模型和展示邏輯不同插龄。通過(guò)讓不同的視圖模型遵守視圖控制器可以依賴的相同協(xié)議愿棋,我們能夠讓視圖控制器不需要知道它展示什么東西,以及如何展示的均牢。

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


結(jié)論

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

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邮府,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子溉奕,更是在濱河造成了極大的恐慌褂傀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腐宋,死亡現(xiàn)場(chǎng)離奇詭異紊服,居然都是意外死亡檀轨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門欺嗤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)参萄,“玉大人,你說(shuō)我怎么就攤上這事煎饼《锟妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吆玖,是天一觀的道長(zhǎng)筒溃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)沾乘,這世上最難降的妖魔是什么怜奖? 我笑而不...
    開(kāi)封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮翅阵,結(jié)果婚禮上歪玲,老公的妹妹穿的比我還像新娘。我一直安慰自己掷匠,他們只是感情好滥崩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著讹语,像睡著了一般钙皮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顽决,一...
    開(kāi)封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天短条,我揣著相機(jī)與錄音,去河邊找鬼擎值。 笑死慌烧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸠儿。 我是一名探鬼主播屹蚊,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼进每!你這毒婦竟也來(lái)了汹粤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤田晚,失蹤者是張志新(化名)和其女友劉穎嘱兼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贤徒,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芹壕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年汇四,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踢涌。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡通孽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睁壁,到底是詐尸還是另有隱情背苦,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布潘明,位于F島的核電站行剂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钳降。R本人自食惡果不足惜厚宰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遂填。 院中可真熱鬧固阁,春花似錦、人聲如沸城菊。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凌唬。三九已至,卻和暖如春漏麦,著一層夾襖步出監(jiān)牢的瞬間客税,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工撕贞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留更耻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓捏膨,卻偏偏與公主長(zhǎng)得像秧均,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子号涯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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