題目來自博客:面試百度的記錄郊酒,有些問題我能回答一下厢呵,不能回答的或有更好的回答我放個(gè)相關(guān)鏈接供參考缠劝。
唐巧前輩說這些都是 iOS 的基礎(chǔ)問題,應(yīng)該對(duì)此深入的了解驹沿。當(dāng)初看到時(shí)扇谣,大部分回答不上來,因?yàn)槠綍r(shí)沒有好好思考整理過刻炒。這里大部分的概念大多會(huì)在學(xué)習(xí) OC 的過程中遇到過,但還是得經(jīng)過寫代碼才能有更深的理解自沧。反正我當(dāng)初看那些設(shè)計(jì)模式是云里霧里坟奥,每個(gè)字都認(rèn)識(shí),就是不知道說的什么拇厢。即使現(xiàn)在爱谁,有些東西,我也不是很理解孝偎。
Objective-C 底層
Objective-C runtime library:Objective-C 的對(duì)象模型访敌,Block 的底層實(shí)現(xiàn)結(jié)構(gòu),消息發(fā)送邪媳,消息轉(zhuǎn)發(fā)捐顷,category荡陷,method 實(shí)現(xiàn)雨效,class load。
runtime 我在平時(shí)很少涉及到废赞,沒有系統(tǒng)學(xué)習(xí)過徽龟,而且很多次看了不久就忘了,所以這里給出一些不錯(cuò)的文章的鏈接供參考唉地。這幾個(gè)問題在《iOS 7 Programming Pushing the Limits》都有過深入的解讀(我有電子版据悔,是盜版传透,這里給出這本書在 Github 的地址,工作后我會(huì)把去年看過的盜版書全部補(bǔ)償買回极颓,沒有 iOS 8 的版本朱盐,不知道是不是由于盜版太多導(dǎo)致的)。另外菠隆,唐巧前輩撰文討論過前兩者:
-
《Objective-C 對(duì)象模型及應(yīng)用》
唐巧在后記中也提到了 iOS 64-bit 帶來的變化:
那么就來看看 Session 404 Advanced in Objective-C 兵琳,從36分起講相關(guān)的東西,喔骇径,看不懂躯肌,那還是看看這個(gè)吧,在《iOS 7 Programming Pushing the Limits》的 Further Reading: objc_explain_Non-pointer_isa 部分談?wù)摿诉@個(gè)問題破衔。 -
《談 Objective-C Block 的實(shí)現(xiàn)》
內(nèi)容非常翔實(shí)清女,特別是關(guān)于 Block 類型的部分,強(qiáng)烈建議做下文章開頭提到的測試:Objective-C Blocks Quiz晰筛。 - 消息發(fā)送和消息轉(zhuǎn)發(fā)
消息發(fā)送比較好理解嫡丙,先了解下 runtime 吧,可以查看官方文檔《Objective-C Runtime Guide》读第。之前學(xué)習(xí)其他語言的時(shí)候還沒有關(guān)注過調(diào)用函數(shù)的背后發(fā)生了什么迄沫,在 Objective-C 中,在對(duì)象上調(diào)用方法稱為發(fā)送消息卦方,比如[receiver message];
這行代碼羊瘩,編譯的時(shí)候編譯器將之轉(zhuǎn)換為對(duì) 底層 C 函數(shù)objc_msgSend
的調(diào)用:objc_msgSend(receiver, selector)
;在運(yùn)行時(shí)盼砍,調(diào)用哪個(gè)方法則完全由 runtime 決定尘吗,甚至在運(yùn)行時(shí)可以替換調(diào)用的方法,這是 Objective-C 被稱為動(dòng)態(tài)語言的根本原因浇坐。對(duì)于消息轉(zhuǎn)發(fā)睬捶,說實(shí)話我現(xiàn)在還不知道這個(gè)的應(yīng)用場景,看到的大部分博客都是說消息轉(zhuǎn)發(fā)給了你補(bǔ)救措施來應(yīng)對(duì)沒有沒有實(shí)現(xiàn)的方法防止 Crash 或者實(shí)現(xiàn)類似多繼承的機(jī)制近刘,我有個(gè)疑惑擒贸,干嘛不實(shí)現(xiàn)那個(gè)方法,而要在代價(jià)很大的轉(zhuǎn)發(fā)機(jī)制里處理呢觉渴。在《Effective Objective-C 2.0》一書第 12 條 tip 中用 @dynamic 演示了實(shí)現(xiàn)動(dòng)態(tài)方法解析的例子來說明消息轉(zhuǎn)發(fā)的意義介劫,老實(shí)說,我還是沒有理解這個(gè)的意義案淋。這里有個(gè)對(duì)官方文檔的中文翻譯和一些注解座韵。 - Implement of category and method
找到了來自這位比我厲害得多的90后:《刨根問底Objective-C Runtime(3)- 消息 和 Category》(文章原來的鏈接放進(jìn)來跟簡書的處理有沖突,這里給的是博客地址,而不是這篇文章的具體地址誉碴,不過很好找)宦棺。 - Class load
可以看這篇博客:《Objective-C Class Loading and Initialization》,看了下作者的 Github黔帕,原來是我以前 follow 過的國外程序員代咸,看人家的 repo 和星星,質(zhì)量有保障成黄,再看博客文章列表侣背,有很多深入底層的內(nèi)容,一座寶礦啊慨默。另外在《Effective Objective-C 2.0》書中第51節(jié)《精簡 initialize 與 load 的實(shí)現(xiàn)》中也討論了這個(gè)問題贩耐,當(dāng)初看完一頭霧水,如今終于能看懂啦厦取。
Core Data: 大量數(shù)據(jù)多線程同步
這個(gè)問題我已經(jīng)單獨(dú)成篇放到這里了潮太,添加了更多的基礎(chǔ)知識(shí)和介紹。
第一步:搭建 Core Data 多線程環(huán)境
這個(gè)問題首先要解決的是搭建 Core Data 多線程環(huán)境虾攻。NSManagedObjectContext
不是線程安全的铡买,你不能隨便地開啟一個(gè)后臺(tái)線程訪問 managed object context 進(jìn)行數(shù)據(jù)操作就管這叫支持多線程了。Core Data 對(duì)多線程的支持比較好霎箍,NSManagedObjectContext
在初始化時(shí)可以指定并發(fā)模式奇钞,有三種選項(xiàng):
1.NSConfinementConcurrencyType
這種模式是用于向后兼容的,使用這種模式時(shí)你應(yīng)該保證不能在其他線程使用 context漂坏,但這點(diǎn)很難保證景埃,不推薦使用。此模式在 iOS 9中已經(jīng)被廢棄顶别。
2.NSPrivateQueueConcurrencyType
在一個(gè)私有隊(duì)列中創(chuàng)建并管理 context谷徙。
3.NSMainQueueConcurrencyType
其實(shí)這種模式與第 2 種模式比較相似,只不過 context 與主隊(duì)列綁定驯绎,也因此與應(yīng)用的 event loop 很親密完慧。當(dāng) context 與 UI 更新相關(guān)的話就使用這種模式。
搭建多線程 Core Data 環(huán)境的方案一般如下剩失,創(chuàng)建一個(gè) NSMainQueueConcurrencyType
的 context 用于響應(yīng) UI 事件屈尼,其他涉及大量數(shù)據(jù)操作可能會(huì)阻塞 UI 的就使用 NSPrivateQueueConcurrencyType
的 context。環(huán)境搭建好了拴孤,如何實(shí)現(xiàn)多線程操作脾歧?官方文檔《Using a Private Queue to Support Concurrency》為我們做了示范,在 private queue 的 context 中進(jìn)行操作時(shí)乞巧,應(yīng)該使用以下方法:
func performBlock(_ block: () -> Void)//在私有隊(duì)列中異步地執(zhí)行 Blcok
func performBlockAndWait(_ block: () -> Void)//在私有隊(duì)列中執(zhí)行 Block 直至操作結(jié)束才返回
要在不同線程中使用 managed object context 時(shí)涨椒,不需要我們創(chuàng)建后臺(tái)線程然后訪問 managed object context 進(jìn)行操作摊鸡,而是交給 context 自身綁定的私有隊(duì)列去處理绽媒,我們只需要在上述兩個(gè)方法的 Block 中執(zhí)行操作即可蚕冬。而且,在 NSMainQueueConcurrencyType
的 context 中也應(yīng)該使用這種方法執(zhí)行操作是辕,這樣可以確保 context 本身在主線程中進(jìn)行操作囤热。
第二步:數(shù)據(jù)的同步操作
多 context 同步最簡單的方案如下:
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "backgroundContextDidSave:",
name: NSManagedObjectContextDidSaveNotification,
object: backgroundContext)
func backgroundContextDidSave(notification: NSNotification){
mainContext.performBlock(){
mainContext.mergeChangesFromContextDidSaveNotification(notification)
}
}
NSManagedObjectContext
在執(zhí)行保存操作后會(huì)發(fā)出 NSManagedObjectContextDidSaveNotification
,包含了 context 所有的變化信息获三,包括新增的旁蔼、更新的以及刪除的對(duì)象的信息;而 mergeChangesFromContextDidSaveNotification(_ notification)
方法則用于合并其他 context 中發(fā)生的變化疙教。
如果 context 并未觀察其他 context 的 NSManagedObjectContextDidSaveNotification
通知棺聊,且保存時(shí),persistent store 已經(jīng)被其他 context 更改過贞谓,那么很可能存在差異限佩,此時(shí)同步就有了以下幾種選擇:選擇保存 context 中的版本或者使用 persistent store 的版本替換 context 的版本,又或是將兩者的版本融合裸弦。這種同步方式由 NSManagedObjectContext
的 mergePolicy
屬性決定祟同。
1.NSErrorMergePolicy
默認(rèn)策略,有沖突時(shí)保存失敗理疙,persistent store 和 context 都維持原樣晕城,并返回錯(cuò)誤信息,是唯一反饋錯(cuò)誤信息的合并策略窖贤。
2.NSMergeByPropertyStoreTrumpMergePolicy
當(dāng) persistent store 和 context 里的版本有沖突砖顷,persistent store 里的版本有優(yōu)先權(quán), context 里使用 persistent store 里的版本替換赃梧,但是 context 里沒有沖突的變化則不會(huì)受到影響择吊。
3.NSMergeByPropertyObjectTrumpMergePolicy
與上面相反,context 里的版本有優(yōu)先權(quán)槽奕,persistent store 里使用 context 里的版本替換几睛,但是 persistent store 里沒有沖突的變化不受影響。
4.NSOverwriteMergePolicy
用 context 里的版本強(qiáng)制覆蓋 persistent store 里的版本粤攒。
5.NSRollbackMergePolicy
放棄 context 中的所有變化并使用 persistent store 中的版本進(jìn)行替換所森。
同步是件很復(fù)雜的事情,實(shí)際上還是需要根據(jù)實(shí)際需要來選擇同步方案夯接。上面兩種方案中第一種概念簡單實(shí)現(xiàn)容易焕济,第二種比較復(fù)雜相對(duì)危險(xiǎn),需要謹(jǐn)慎選擇同步策略盔几。還有一點(diǎn)需要注意晴弃,如果需要跨線程使用 managed object,那么不要直接在其他 context 里使用該 managed object,而應(yīng)該通過該對(duì)象的 objectID 將該對(duì)象 fetch 到 context 里上鞠。
最后际邻,搞定大量數(shù)據(jù)
多線程和同步問題解決,最后的難點(diǎn):大量數(shù)據(jù)芍阎。大量數(shù)據(jù)意味著需要我們關(guān)注內(nèi)存占用和性能世曾,寫代碼時(shí)需要記得以下規(guī)則:
1.盡可能緩存需要的數(shù)據(jù),不相關(guān)的數(shù)據(jù)保持 faults狀態(tài)谴咸。
2.fetch 時(shí)盡可能精準(zhǔn)轮听,少引入不相關(guān)的數(shù)據(jù)。
3.構(gòu)建多 context 時(shí)盡量將同類 managed object 集中岭佳,最大限度減少合并需求血巍。
4.提升操作效率,對(duì)Asynchronous Fetch, Batch Update珊随,Batch Delete 等新特性盡可能利用藻茂。
多線程編程
在 iOS 編程中,這幾種情況下需要處理多線程:UI 事件必須在主線程里進(jìn)行玫恳,其他的可以放在后臺(tái)進(jìn)行辨赐;而進(jìn)行一些耗時(shí)長或阻塞線程的任務(wù),最后放進(jìn)后臺(tái)線程里進(jìn)行京办。iOS 的多線程技術(shù)有這么幾種:線程掀序,GCD 和 NSOperationQueue。線程這種技術(shù)比較復(fù)雜惭婿,而多線程編程向來是「復(fù)雜必死」不恭,推薦盡可能使用后二者,但線程有個(gè)后二者沒有的優(yōu)勢:能夠精確保證任務(wù)執(zhí)行的時(shí)間财饥。GCD 全稱是 Grand Central Dispatch, 是 libdispatch 這個(gè)庫的外部代號(hào)换吧,基于 C 的底層來實(shí)現(xiàn);而NSOperationQueue钥星,通稱操作隊(duì)列沾瓦,是基于 GCD 實(shí)現(xiàn)的。GCD 能做的 NSOperationQueue 基本上都能做谦炒,而且還有些 GCD 中不易實(shí)現(xiàn)的特性贯莺,如掛起、取消任務(wù)宁改,雖然在 iOS 8 中缕探,GCD 也提供了取消任務(wù)的功能,但在 GCD 中任務(wù)的掛起和取消都有較大的局限性还蹲;雖然大多數(shù)情況下應(yīng)該使用抽象級(jí)別更高的 API爹耗,也就是 NSOperationQueue耙考,但處理一般的后臺(tái)任務(wù)我偏愛 GCD,主要是 GCD 搭配 Blcok 使用簡單潭兽,非常方便倦始。如何選擇,下面兩個(gè)鏈接對(duì)此問題的討論值得一看:
StackOverflow: NSOperation vs. Grand Central Dispatch
Blog: When to use NSOperation vs. GCD
另外讼溺,還推薦這些文章:objc 的并發(fā)編程專題《Concurrent Programming》 及中文翻譯版本楣号;雷純鋒的博客《iOS 并發(fā)編程之 Operation Queues》最易;NSHipster 的《NSOperation》怒坯。
設(shè)計(jì)模式
評(píng)價(jià) Delegate, Notification, KVO 幾種設(shè)計(jì)模式的優(yōu)缺點(diǎn)
我不覺得這個(gè)問題是個(gè)好問題,與其比較這幾個(gè)設(shè)計(jì)模式的優(yōu)缺點(diǎn)藻懒,不如談它們各自的特點(diǎn)比較好剔猿,因?yàn)樗鼈兪菫榱私鉀Q某類問題才設(shè)計(jì)出來的,有各自適合的使用場景嬉荆。另外归敬,給個(gè) iOS 中設(shè)計(jì)模式的介紹:iOS Design Patterns。
為什么出題目都喜歡把這三個(gè)設(shè)計(jì)模式拿來對(duì)比呢鄙早?Notification 和 KVO都是用于協(xié)助對(duì)象間的通信:某個(gè)對(duì)象監(jiān)聽某個(gè)事件的發(fā)生汪茧,當(dāng)某個(gè)事件發(fā)生時(shí),該對(duì)象會(huì)得到通知然后做出響應(yīng)限番。這幾句話大概是以前看過的書本上說的舱污。如果你以前沒接觸過設(shè)計(jì)模式,第一次學(xué)習(xí)時(shí)總是能夠看到事件弥虐、響應(yīng)這類模糊的詞匯扩灯,看得你云里霧里,好吧霜瘪,我說的是我珠插。 但 delegate,應(yīng)該說沒有監(jiān)聽的功能颖对,而是當(dāng)事件發(fā)生或時(shí)機(jī)到了捻撑,要求 delegate 對(duì)象做點(diǎn)什么。剛開始學(xué)習(xí) OC 的時(shí)候缤底,一本書中將 delegate比喻為助手布讹,那時(shí)候不怎么理解,現(xiàn)在覺得這個(gè)比喻十分恰當(dāng)训堆。雖然delegate 模式在 OC 中隨處可見描验,在UIViewController 類中廣泛存在,但在開發(fā) FaceAlbum 的過程中只遇到過一次自定義 protocol/delegate 的情況坑鱼,后來還是用 KVO 取代了膘流。相對(duì)于 Notification 和 KVO 模式絮缅,使用 delegate 模式你會(huì)明確知道對(duì)象的 delegate 能干什么,因?yàn)橐蔀?某個(gè)對(duì)象的delegate呼股,該對(duì)象得遵守指定的 protocol耕魄,protocol 指定了 delegate 對(duì)象需要實(shí)現(xiàn)的方法。
Notification和 KVO兩者都需要監(jiān)聽事件的對(duì)象(早期看見事件就犯暈彭谁,如今寫來覺得用這個(gè)詞挺順手的)去注冊(cè)吸奴,delegate則需要 delegate 對(duì)象遵守指定的 protocol;Notification 中監(jiān)聽者向一個(gè)單例對(duì)象NSNotificationCenter注冊(cè)缠局,NSNotificationCenter類似一個(gè)廣播中心则奥,接受任何對(duì)象的注冊(cè),后者則向要監(jiān)聽的對(duì)象注冊(cè)狭园,一對(duì)一读处,這兩者都不需要對(duì)象之間有聯(lián)系,而 delegate 則需要通信的對(duì)象通過變量聯(lián)系唱矛;NSNotification模式里監(jiān)聽的對(duì)象與被監(jiān)聽的對(duì)象通信是通過 NSNotificationCenter 這個(gè)中介罚舱,而KVO 里,不能說兩者是直接通信的绎谦,我沒有了解過過 KVO 是如何實(shí)現(xiàn)通信的管闷,從表面上看兩者就那么心靈感應(yīng)一般,這是系統(tǒng)替我們實(shí)現(xiàn)的窃肠,而delegate包个,由于通過變量連接,直接向 delegate 發(fā)送消息即可铭拧,在這點(diǎn)上赃蛛,NSNotification不需要通信雙方知道對(duì)方,而后兩者則不然搀菩;在響應(yīng)事件時(shí)呕臂,NSNotification和 KVO 模式里都是在注冊(cè)時(shí)指定響應(yīng)方法,而 delegate 則在 protocol 里預(yù)定義了響應(yīng)方法肪跋。
說了這么多歧蒋,不直觀,說個(gè)實(shí)際場景州既,比如在 UICollectionView 里選擇 cell 的時(shí)候谜洽,希望 title 能夠跟蹤選中 cell 的數(shù)量。這里用NSNotification和 KVO 都能實(shí)現(xiàn)吴叶,但是我更喜歡 KVO阐虚,感覺更優(yōu)雅,因?yàn)槭褂肗SNotification模式的話蚌卤,選中一個(gè) cell 的時(shí)候要在選擇的方法里手動(dòng)發(fā)布通知实束,而 KVO奥秆,只要對(duì)觀察的屬性實(shí)現(xiàn) KVO 兼容的方法就可以了;而delegate咸灿,自己做自己的 delegate构订,呃。而面對(duì)一些系統(tǒng)里的事件避矢,比如鍵盤的出現(xiàn)與消失悼瘾,圖片庫的變化,使用NSNotification更加自然审胸,因?yàn)?KVO 限于對(duì)對(duì)象屬性的跟蹤亥宿。
暫時(shí)寫這么多,推薦博客《When to use Delegation, Notification, or Observation in iOS》歹嘹,可能需要翻墻箩绍。