本書(shū)很適合初中級(jí) iOS 工程師,內(nèi)容比較簡(jiǎn)單也很實(shí)用。
傳送門:點(diǎn)擊下載原書(shū) PDF 版本
下面是整本書(shū)的目錄結(jié)構(gòu):

Effective Objective-C 2.0.png
1. 了解 Objective-C 的起源
- Objective-C 為 C 語(yǔ)言添加了面向?qū)ο筇匦?,是其超集。Objective-C 使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),也就是說(shuō),在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型。接收一條消息之后,空間應(yīng)執(zhí)行何種代碼,由運(yùn)行時(shí)環(huán)境而非編譯器來(lái)決定。
- 理解 C 語(yǔ)言的核心概念有助于寫好 Objective-C 程序。尤其要掌握內(nèi)存模型與指針。
- Objective-C 的對(duì)象所占內(nèi)存總是分配在“堆空間”(heap space)中,而絕不會(huì)分配在“棧”(stack)上。
- 與創(chuàng)建結(jié)構(gòu)體相比,創(chuàng)建對(duì)象還需要額外的開(kāi)銷,例如分配內(nèi)存和釋放堆內(nèi)存等。如果只需要保存 int、float、double、char 等非對(duì)象類型,那么通常使用 CGRect 這種結(jié)構(gòu)體就可以了。
2. 在類的頭文件中盡量少引入其他頭文件
- 除非確有必要,否則不要引入頭文件。一般來(lái)說(shuō),應(yīng)在某個(gè)類的頭文件中使用向前聲明(@class)來(lái)提及別的類,并在實(shí)現(xiàn)文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合,解決了兩個(gè)類相互引用的問(wèn)題。
- 有時(shí)無(wú)法使用向前聲明,比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議。這種情況下,盡量把“該類遵循某協(xié)議”的聲明移至“clss-continuation分類”(參見(jiàn)第27條)中。如果不行的話,就把協(xié)議單獨(dú)放在一個(gè)頭文件中,然后將其引入。
3. 多用字面量語(yǔ)法,少用與之等價(jià)的方法
字面量語(yǔ)法也叫語(yǔ)法糖,可以使程序更易讀,減少代碼出錯(cuò)的概率。通過(guò)加一個(gè) @ 符號(hào)即可實(shí)現(xiàn)字符串、數(shù)值、數(shù)組、字典的創(chuàng)建,如:@“hello”、@3.14、@[@“one”,@“two”,nil]、@{@“key”:@"value"}。
- 應(yīng)該使用字面量語(yǔ)法來(lái)創(chuàng)建字符串、數(shù)值、數(shù)組、字典與創(chuàng)建此類的常規(guī)方法相比,這么做更加簡(jiǎn)明扼要。
- 應(yīng)該通過(guò)取下標(biāo)操作來(lái)訪問(wèn)數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素。
- 用字面量語(yǔ)法創(chuàng)建數(shù)組或字典時(shí),或值中有 nil,則會(huì)拋出異常。因此,務(wù)必確保值里不含 nil。
- 應(yīng)該使用字面量語(yǔ)法來(lái)創(chuàng)建字符串、數(shù)值、數(shù)組、字典對(duì)象都是不可變的,若想要可變版本的對(duì)象,則需要復(fù)制一份(mutableCopy)。
4. 多用類型常量,少用 #define 預(yù)處理指令
- 不要用預(yù)處理指令定義常量。這要定義出來(lái)的常量不含類型信息,編譯器只是會(huì)在編譯前據(jù)此執(zhí)行查找與替換操作。即便有人重新定義了常量值,編譯器也不會(huì)產(chǎn)生警告信息,這將導(dǎo)致應(yīng)用程序中的常量值不一致。
- 在實(shí)現(xiàn)文件中使用 static const 來(lái)定義“只在編譯單元內(nèi)可見(jiàn)的常量”(translation-unit-specific constant)。由于此類常量不在全局符號(hào)表中,所以無(wú)須為其名稱加前綴。這種方式定義的常量帶有類型信息。
- 在頭文件中使用 extern 來(lái)聲明全局常量,并在相關(guān)實(shí)現(xiàn)文件中定義其值。這種常量要出現(xiàn)在全局符號(hào)表中,所以其名稱應(yīng)加以區(qū)隔,通常用與之相關(guān)的類名做前綴。
5. 用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)碼
- 應(yīng)該用枚舉來(lái)表示狀態(tài)機(jī)的狀態(tài)、傳遞給方法的選項(xiàng)及狀態(tài)碼等值,給這些值起個(gè)易懂的名字。
- 如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型,而多個(gè)選項(xiàng)又可同時(shí)使用,那么就將各選項(xiàng)值定義為2的冪,以便通過(guò)按位或操作將期組合起來(lái)。
- 用 NS_ENUM 與 NS_OPTIONS 宏來(lái)定義枚舉類型,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開(kāi)發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來(lái)的,而不會(huì)采用編譯器所選的類型。
- 凡是需要以按位或操作來(lái)組合的枚舉都應(yīng)使用 NS_OPTIONS 定義,若是枚舉不需要互相組合,則應(yīng)該使用 NS_ENUM 來(lái)定義。
- 在處理枚舉類型的 switch 語(yǔ)句中不要實(shí)現(xiàn) default 分支。這樣的話,加入新枚舉之后,編譯器就會(huì)提示開(kāi)發(fā)者:switch 語(yǔ)句并未處理所有枚舉。
6. 理解“屬性”這一概念
- 可以用 @property 語(yǔ)法來(lái)定義對(duì)象中所封裝的數(shù)據(jù)
- 通過(guò)“特質(zhì)”來(lái)指定存儲(chǔ)數(shù)據(jù)所需的正確語(yǔ)義,包括:原子性(atomic nonatomic)、讀/寫權(quán)限(readwrite readonly)、內(nèi)存管理語(yǔ)義(assign strong weak unsafe_unretained copy)、方法名(getter=<name> setter=<name>)。
- 在設(shè)置屬性所對(duì)應(yīng)的實(shí)例變量時(shí),一定要遵從該屬性所聲明的語(yǔ)義。
- 開(kāi)發(fā) iOS 程序時(shí)應(yīng)該使用 nonatomic 屬性,因?yàn)?atomic 屬性會(huì)嚴(yán)重影響性能。(說(shuō)明:具備 atomic 特質(zhì)的獲取方法會(huì)通過(guò)鎖定機(jī)制來(lái)確保其操作的原子性,但是在 iOS 中所有屬性都聲明為 nonatomic,這樣做的歷史原因是在 iOS 中使用同步鎖的開(kāi)鎖較大,這會(huì)帶來(lái)性能問(wèn)題。一般情況下并不要求屬性必須是“原子的”,因?yàn)檫@并不能保證“線程安全”(thread safety),若要實(shí)現(xiàn)“線程安全”的操作,還需采用更為深層的鎖定機(jī)制才行。但是在開(kāi)發(fā) Mac OS X 程序時(shí),使用 atomic 屬性通常都不會(huì)有性能瓶頸。)
7. 在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
- 在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí),應(yīng)該直接通過(guò)實(shí)例變量來(lái)讀,而寫入數(shù)據(jù)時(shí),則應(yīng)通過(guò)屬性來(lái)寫。
- 在初始化方法及 dealloc 方法中,總是應(yīng)該直接通過(guò)實(shí)例變量來(lái)讀寫數(shù)據(jù)。如:_someKey = nil
- 有時(shí)會(huì)使用惰性初始化技術(shù)配置某份數(shù)據(jù),這種情況下,需要通過(guò)屬性來(lái)讀取數(shù)據(jù)。
8. 理解“對(duì)象等同性”這一概念
- 若想檢測(cè)對(duì)象的等同性,請(qǐng)?zhí)峁癷sEqual:”與 hash 方法。
- 相同的對(duì)象必具有相同的哈希碼,但是兩個(gè)哈希碼相同的對(duì)象卻未必相同。
- 不要盲目地逐個(gè)檢測(cè)每條屬性,而是應(yīng)該依照具體需求來(lái)制定檢測(cè)方案。
- 編寫 hash 方法時(shí),應(yīng)該使用計(jì)算速度快而且哈希碼碰撞幾率低的算法。
9. 以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
- “工廠模式”是創(chuàng)建類族的辦法之一。
- 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面。
- 系統(tǒng)框架中經(jīng)常使用類族,大部分的 collection 類都是類族。
- 從類族的公共抽象基類中繼承子類要當(dāng)心,若有開(kāi)發(fā)文檔,則應(yīng)首先閱讀。
10. 在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
- 可以通過(guò)“關(guān)聯(lián)對(duì)象”機(jī)制來(lái)把兩個(gè)對(duì)象連起來(lái)。
- 定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語(yǔ)義,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”。
- 只有在其他做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象,因?yàn)檫@種做法通常會(huì)引入難于查找的 bug。
11. 理解 objc_msgSend 的作用
- 消息由接收者、選擇子及參數(shù)構(gòu)成。給某對(duì)象“發(fā)送消息”(invoke a message)也就相當(dāng)于在該對(duì)象上“調(diào)用方法”(call a method)。
- 發(fā)給某對(duì)象的全部消息都要由“動(dòng)態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)來(lái)處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,并執(zhí)行其代碼。
12. 理解消息轉(zhuǎn)發(fā)機(jī)制
- 若對(duì)象無(wú)法響應(yīng)某個(gè)選擇子,則進(jìn)入消息轉(zhuǎn)發(fā)流程。
- 通過(guò)運(yùn)行期的動(dòng)態(tài)方法解析功能,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中。
- 對(duì)象可以把其無(wú)法解讀的某些選擇子轉(zhuǎn)交給其他對(duì)象來(lái)處理。
- 經(jīng)過(guò)上述兩步之后,如果還是沒(méi)辦法處理選擇子,那就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制。
消息轉(zhuǎn)發(fā)流程圖如下:

消息轉(zhuǎn)發(fā).jpeg
步驟越往后,消息處理的代價(jià)越大,最好能在第一步就處理完,這樣的話,運(yùn)行時(shí)系統(tǒng)就可以將此方法緩存起來(lái)了。
13. 用“方法調(diào)配技術(shù)”(method swizzling)調(diào)試“黑盒方法”
- 在運(yùn)行期,可以向類中新增或替換選擇子所對(duì)應(yīng)的方法實(shí)現(xiàn)。
- 使用另一份實(shí)現(xiàn)來(lái)替換原有的方法實(shí)現(xiàn),這道工序叫做“方法調(diào)配”,開(kāi)發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)中添加新功能。
- 一般來(lái)說(shuō),只有調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn),這種做法不宜濫用。
14. 理解“類對(duì)象”的用意
- 每個(gè)實(shí)例都有一個(gè)指向 Class 對(duì)象的指針,用以表明其類型,而這些 Class 對(duì)象則構(gòu)成了類的繼承體系。
- 如果對(duì)象類型無(wú)法在編譯期確定,那么就應(yīng)該使用類型信息查詢方法來(lái)探知。
- 盡量使用類型信息查詢方法來(lái)確定對(duì)象類型,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。
-
每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類。
類對(duì)象與元類.jpeg
15. 用前綴避免命名空間沖突
- 選擇與你公司、應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱作為類名的前綴,并在所有代碼中均使用這一前綴。
- 若自己所開(kāi)發(fā)的程序庫(kù)中用到了第三方庫(kù),則應(yīng)為其中的名稱加上前綴。
16. 提供“全能初始化方法”
- 在類中的時(shí)候一個(gè)全能初始化方法,并于文檔里指明。其他初始化方法均應(yīng)調(diào)用此方法。
- 若全能初始化方法與超類不同,則需覆寫超類中的對(duì)應(yīng)方法。
-
如果超類的初始化方法不適用于子類,那么應(yīng)該覆寫這個(gè)超類方法,并在其中拋出異常。
NSDate 是個(gè)不錯(cuò)的例子:
NSDate.jpeg
17. 實(shí)現(xiàn) description 方法
- 實(shí)現(xiàn) description 方法返回一個(gè)有意義的字符串,用以描述該實(shí)例。
- 若想在調(diào)試時(shí)打印出更詳盡的對(duì)象描述信息,則應(yīng)實(shí)現(xiàn) debugDescription 方法。
18. 盡量使用不可變對(duì)象
- 盡量創(chuàng)建不可變的對(duì)象。
- 若某屬性僅可于對(duì)象內(nèi)部修改,則在“class-continuation分類”中將其由 readonly 屬性擴(kuò)展為 readwrite 屬性。
- 不要把可變的 collection 作為屬性公開(kāi),而應(yīng)提供相關(guān)方法,以此修改對(duì)象中的可變 collection。
19. 使用清晰而協(xié)調(diào)的命名方式
- 起名時(shí)應(yīng)遵從標(biāo)準(zhǔn)的 Obective-C 命名規(guī)范,這樣創(chuàng)建出來(lái)的接口更容易為開(kāi)發(fā)者所理解。
- 方法名要言簡(jiǎn)意賅,從左至右讀起來(lái)要像個(gè)日常用語(yǔ)中的句子才好。
- 方法名里不要使用縮略后的類型名稱。
- 給方法起名時(shí)的第一要?jiǎng)?wù)就是確保其風(fēng)格與你自己的代碼或所要集成的框架相符。
20. 為私有方法名加前綴
- 給私有方法名稱加上前綴,這樣可以很容易地將其同公共方法區(qū)分開(kāi),而且有助于調(diào)試。
- 不要單用一個(gè)下劃線做私有方法的前綴,因?yàn)檫@種做法是預(yù)留給蘋果公司用的。
21. 理解 Objective-C 錯(cuò)誤類型
- 只有發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí),才應(yīng)使用異常。而且異常拋出之后無(wú)須考慮恢復(fù)問(wèn)題,應(yīng)用程序此時(shí)應(yīng)該直接退出。
- 在錯(cuò)誤不那么嚴(yán)重的情況下,可以指派“委托方法”(delegate method)來(lái)處理錯(cuò)誤,也可以把錯(cuò)誤信息放在 NSError 對(duì)象里,經(jīng)由“輸出參數(shù)”返回給調(diào)用者。
- NSError 對(duì)象里封裝了三條信息:
- Error domain(錯(cuò)誤范圍,其類型為字符串)
- Error code(錯(cuò)誤碼,其類型為整數(shù)),用枚舉類型來(lái)表示錯(cuò)誤碼是個(gè)明智之舉
- User info(用戶信息,其類型為字典)
22. 理解 NSCopying 協(xié)議
- 若想令自己所寫的對(duì)象具有拷貝功能,則需實(shí)現(xiàn) NSCopying 協(xié)議。
- 如果自定義的對(duì)象分為可變版本與不可變版本,那么就要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議。
- 復(fù)制對(duì)象時(shí)需決定采用淺拷貝還是深拷貝,一般情況下應(yīng)該盡量執(zhí)行淺拷貝。
- 如果你所寫的對(duì)象需要深拷貝,那么可考慮新增一個(gè)專門執(zhí)行深拷貝的方法。
23. 通過(guò)委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間通信
- 委托模式為對(duì)象提供了一套接口,使其可由此將相關(guān)事件告知其他對(duì)象。
- 將委托對(duì)象應(yīng)該支持的接口定義成協(xié)議,在協(xié)議中把可能需要處理的事件定義成方法。
- 當(dāng)某對(duì)象需要從另外一個(gè)對(duì)象中獲取數(shù)據(jù)時(shí),可以使用委托模式。這種情境下,該模式變稱“數(shù)據(jù)源協(xié)議”。
- 若有必要,可實(shí)現(xiàn)含有位段的結(jié)構(gòu)體,將委托對(duì)象是否能響應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中。
24. 將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類之中
- 使用分類機(jī)制把類的實(shí)現(xiàn)代碼劃分成易于管理的小塊。
- 將應(yīng)該視為“私有”的方法歸入名叫 Private 的分類中,以隱藏實(shí)現(xiàn)細(xì)節(jié)。
- 分類一方面易于檢視,另一方面也更方便調(diào)試。
25. 總是為第三方類的分類名稱加前綴
- 向第三方類中添加分類時(shí),總應(yīng)給其名稱加上你專用的前綴。
- 向第三方類中添加分類時(shí),總應(yīng)給其中的方法名加上你專用的前綴。
26. 勿在分類中聲明屬性
- 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里。
- 在“class-continuation 分類”之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。
27. 使用“class-continuation 分類”隱藏實(shí)現(xiàn)細(xì)節(jié)
- 通過(guò)“class-continuation 分類”向類中新增實(shí)例變量。
- 如果某屬性在主接口中聲明為“只讀”,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在“class-continuation 分類”中將其擴(kuò)展為“可讀寫”。
- 把私有方法的原型聲明在“class-continuation 分類”里面。
- 若想使類所遵循的協(xié)議不為人所知,則可于“class-continuation 分類”中聲明。
28. 通過(guò)協(xié)議提供匿名對(duì)象
- 協(xié)議可在某種程度上提供匿名類型。具體的對(duì)象類型可以淡化成遵從某協(xié)議的 id 類型,協(xié)議里規(guī)定了對(duì)象所應(yīng)實(shí)現(xiàn)的方法。
- 使用匿名對(duì)象來(lái)隱藏類型名稱(或類名)。
- 如果具體類型不重要,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里的)特定方法,那么可使用匿名對(duì)象來(lái)表示。
29. 理解引用計(jì)數(shù)
- 引用計(jì)數(shù)機(jī)制通過(guò)可以遞增遞減的計(jì)數(shù)器來(lái)管理內(nèi)存。對(duì)象創(chuàng)建好之后,其保留計(jì)數(shù)至少為1。若保留計(jì)數(shù)為正,則對(duì)象繼續(xù)存活。當(dāng)保留計(jì)數(shù)降為0時(shí),對(duì)象就被銷毀了。
- 在對(duì)象生命期中,其余對(duì)象通過(guò)引用來(lái)保留或釋放此對(duì)象。保留與釋放操作分別會(huì)遞增遞減保留計(jì)數(shù)。
30. 以 ARC 簡(jiǎn)化引用計(jì)數(shù)
- 有 ARC 之后,程序員就無(wú)須擔(dān)心內(nèi)存管理問(wèn)題了。使用 ARC 來(lái)編程,可省去類中的許多“樣板代碼”。
- ARC 管理對(duì)象生命期的辦法基本上就是:在合適的地方插入“保留”及“釋放”操作。在 ARC 環(huán)境下,變量的內(nèi)存管理語(yǔ)義可以通過(guò)修飾符指明,而原來(lái)則需要手工執(zhí)行“保留”及“釋放”操作。
- 由方法所返回的對(duì)象,其內(nèi)存管理語(yǔ)義總是通過(guò)方法名來(lái)實(shí)現(xiàn)。ARC 將此確定為開(kāi)發(fā)者必須遵守的規(guī)則。
- ARC 只負(fù)責(zé)管理 Objective-C 對(duì)象的內(nèi)存。尤其要注意:CoreFoundation 對(duì)象不歸 ARC 管理,開(kāi)發(fā)者必須適時(shí)調(diào)用 CFRetain/CFRelease。
31. 在 dealloc 方法中只釋放引用并解除監(jiān)聽(tīng)
- 在 dealloc 方法里,應(yīng)該做的事情就是釋放指向其他對(duì)象的引用,并取消原來(lái)訂閱的“鍵值觀測(cè)”(KVO)或 NSNotificationCenter 等通知,不要做其他事情。
- 如果對(duì)象持有文件描述符等系統(tǒng)資源,那么應(yīng)該專門編寫一個(gè)方法來(lái)釋放此種資源。這樣的類要和其使用者約定:用完資源后必須調(diào)用 close 方法。
- 執(zhí)行異步任務(wù)的方法不應(yīng)在 dealloc 里調(diào)用;只能在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在 dealloc 里調(diào)用,因?yàn)榇藭r(shí)對(duì)象已正在回收的狀態(tài)了。
32. 編寫“異常安全代碼”時(shí)留意內(nèi)存管理問(wèn)題
- 捕獲異常時(shí),一定要注意將 try 塊內(nèi)所創(chuàng)立的對(duì)象清理干凈。
- 在默認(rèn)情況下,ARC 不生成安全處理異常所需的清理代碼。開(kāi)啟編譯器標(biāo)志后,可生成這種代碼,不過(guò)會(huì)導(dǎo)致應(yīng)用程序變大,而且會(huì)降低運(yùn)行效率。
33. 以弱引用避免保留環(huán)
- 將某些引用設(shè)為 weak,可避免出現(xiàn)“保留環(huán)”。
- weak 引用可以自動(dòng)清空,也可以不自動(dòng)清空。自動(dòng)清空是隨著 ARC 而引入的新特性,由運(yùn)行期系統(tǒng)來(lái)實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上,可以隨意讀取其數(shù)據(jù),因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過(guò)的對(duì)象。
34. 以“自動(dòng)釋放池塊”降低內(nèi)存峰值
- 自動(dòng)釋放池排布在棧中,對(duì)象收到 autorelease 消息后,系統(tǒng)將其放入最頂端的池里。
- 合理運(yùn)用自動(dòng)釋放池,可降低應(yīng)用程序的內(nèi)存峰值。
- @autoreleasepool 這種新式寫法能創(chuàng)建出更為輕便的自動(dòng)釋放池。
35. 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問(wèn)題
- 系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收,而是把它轉(zhuǎn)化為僵尸對(duì)象。通過(guò)環(huán)境變量 NSZombieEnabled 可開(kāi)啟此功能。
- 系統(tǒng)會(huì)修改對(duì)象的 isa 指針,令其指向特殊的僵尸類,從而使該對(duì)象變?yōu)榻┦瑢?duì)象。僵尸類能夠響應(yīng)所有的選擇子,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接收者的消息,然后終止應(yīng)用程序。
36. 不要使用 retainCount
- 對(duì)象的保留計(jì)數(shù)看似有用,實(shí)則不然,因?yàn)槿魏谓o定時(shí)間點(diǎn)上的“絕對(duì)你試計(jì)數(shù)”(absolute retain count)都無(wú)法反映對(duì)象生命的全貌。
- 引入 ARC 之后,retainCount 方法就正式廢止了,在 ARC 下調(diào)用該方法會(huì)導(dǎo)致編譯器報(bào)錯(cuò)。
37. 理解“塊”這一概念
塊,即 block;大中樞派發(fā),即 Grand Central Dispatch, GCD。
- 塊是 C、C++、Objective-C 中的詞法閉包。
- 塊可接受參數(shù),也可返回值。
- 塊可以分配在?;蚨焉希部梢允侨值?。分配在棧上的塊可拷貝到堆里,這樣的話,就和標(biāo)準(zhǔn)的 Objective-C 對(duì)象一樣,具備引用計(jì)數(shù)了。
38. 為常用的塊類型創(chuàng)建 typedef
- 以 typedef 重新定義塊類型,可令塊變量用起來(lái)更加簡(jiǎn)單。
- 定義新類型時(shí)應(yīng)遵從現(xiàn)有的命名習(xí)慣,勿使其名稱與別的類型相沖突。
- 不妨為同一個(gè)塊簽名定義多個(gè)類型別名。如果要重構(gòu)的代碼使用了塊類型的某個(gè)別名,那么只需修改相應(yīng) typedef 中的塊簽名即可,無(wú)須改動(dòng)其他 typedef。
39. 用 handler 塊降低代碼分散程度
- 在創(chuàng)建對(duì)象時(shí),可以使用內(nèi)聯(lián)的 handler 塊將業(yè)務(wù)邏輯一并聲明。
- 在有多個(gè)實(shí)例需要監(jiān)控時(shí),如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對(duì)象來(lái)切換,而若改用 handler 塊來(lái)實(shí)現(xiàn),則可直接將塊與相關(guān)對(duì)象放在一起。
- 設(shè)計(jì) API 時(shí)如果用到了 handler 塊,那么可以增加一個(gè)參數(shù),使調(diào)用者可通過(guò)此參數(shù)來(lái)決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行。
40. 用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)
- 如果塊所捕獲的對(duì)象直接或間接的保留了塊本身,那么就得當(dāng)心保留環(huán)問(wèn)題。
- 一定要找個(gè)適當(dāng)?shù)臅r(shí)機(jī)解除保留環(huán),而不能把責(zé)任推給 API 的調(diào)用者。
41. 多用派發(fā)隊(duì)列,少用同步鎖
派發(fā)隊(duì)列,即 GCD;同步鎖,即 @synchronized.
- 派發(fā)隊(duì)列可用來(lái)表述同步語(yǔ)義(synchronization semantic),這種做法要比使用 @synchronized 塊或 NSLock 對(duì)象更簡(jiǎn)單。
- 將同步與異步派發(fā)結(jié)合起來(lái),可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為,而這么做卻不會(huì)阻塞執(zhí)行異步派發(fā)的線程。
- 使用同步隊(duì)列及柵欄塊(dispatch_barrier_async),可以令同步行為更加高效。
42. 多用 GCD,少用 performSelector 系列方法
- performSelector 系統(tǒng)方法在內(nèi)存管理方面容易有疏失。它無(wú)法確定將要執(zhí)行的選擇子具體是什么,因而 ARC 編譯器也就無(wú)法插入適當(dāng)?shù)膬?nèi)存管理方法。
- performSelector 系列方法所能處理的選擇子太過(guò)局限了,選擇子的返回值類型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受到限制。
- 如果想把任務(wù)放在另一個(gè)線程上執(zhí)行,那么最好不要用 performSelector 系列方法,而是應(yīng)該把任務(wù)封裝到塊里,然后調(diào)用 GCD 的相關(guān)方法來(lái)實(shí)現(xiàn)。
43. 掌握 GCD 及操作隊(duì)列的使用時(shí)機(jī)
操作隊(duì)列,即 NSOperationQueue
- 在解決多線程與任務(wù)管理問(wèn)題時(shí),派發(fā)隊(duì)列并非唯一文案。
- 操作隊(duì)列提供了一套高層的 Objective-C API,能實(shí)現(xiàn)純 GCD 所具備的絕大部分功能,而且還能完成一些更為復(fù)雜的操作,那些操作若改用 GCD 來(lái)實(shí)現(xiàn),則需另外編寫代碼。
使用 NSOperation 及 NSOperationQueue 的好處:
- 取消某個(gè)操作;
- 指定操作間的依賴關(guān)系;
- 通過(guò)鍵值觀察機(jī)制監(jiān)控 NSOperation 對(duì)象的屬性;
- 指定操作的優(yōu)先級(jí);
- 重用 NSOperation 對(duì)象。
44. 通過(guò) Dispatch Group 機(jī)制,根據(jù)系統(tǒng)資源狀況來(lái)執(zhí)行任務(wù)
- 一系列任務(wù)可歸入一個(gè) dispatch group 之中。開(kāi)發(fā)者可以在這組任務(wù)執(zhí)行完畢時(shí)獲得通知。
- 通過(guò) dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)。此時(shí) GCD 會(huì)根據(jù)系統(tǒng)資源狀況來(lái)調(diào)度這些并發(fā)執(zhí)行的任務(wù)。開(kāi)發(fā)者若自己來(lái)實(shí)現(xiàn)此功能,則需編寫大量代碼。
45. 使用 dispatch_one 來(lái)執(zhí)行只需運(yùn)行一次的線程安全代碼
- 經(jīng)常需要編寫“只需執(zhí)行一次的線程安全代碼”。通過(guò) GCD 所提供的 dispatch_once 函數(shù),很容易就能實(shí)現(xiàn)此功能。
- 標(biāo)記應(yīng)該聲明在 static 或 global 作用域中,這樣的話,在把只需執(zhí)行一次的塊傳給 dispatch_once 函數(shù)時(shí),傳進(jìn)去的標(biāo)記也是相同的。
46. 不要使用 dispatch_get_current_queue
- dispatch_get_current_queue 函數(shù)的行為常常與開(kāi)發(fā)者所預(yù)期的不同。此函數(shù)已經(jīng)廢棄,只應(yīng)做調(diào)試之用。
- 由于派發(fā)隊(duì)列是按層級(jí)來(lái)組織的,所以無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述“當(dāng)前隊(duì)列”這一概念。
- dispatch_get_current_queue 函數(shù)用于解決由不可重入的代碼所引發(fā)的死鎖,然而能用此函數(shù)解決的問(wèn)題,通常也能改用“隊(duì)列特定數(shù)據(jù)”來(lái)解決。
47. 熟悉系統(tǒng)框架
- 許多系統(tǒng)框架都可以直接使用。其中最重要的是 Foundation 和 CoreFoundation,這兩個(gè)框架提供了構(gòu)建應(yīng)用程序所需的許多核心功能。
- 很多常見(jiàn)任務(wù)都能用框架來(lái)做,例如音頻與視頻處理、網(wǎng)絡(luò)通信、數(shù)據(jù)管理等。
- 請(qǐng)記?。河眉?C 寫成的框架與用 Objective-C 寫成的一樣重要,若想成為優(yōu)秀的 Objective-C 開(kāi)發(fā)者,應(yīng)該掌握 C 語(yǔ)言的核心概念。
48 . 多用塊枚舉,少用 for 循環(huán)
- 遍歷 collection 有四種方式。最基本的辦法是 for 循環(huán),其次是 NSEnumerator 遍歷法及快速遍歷法,最新、最先進(jìn)的方式是“塊枚舉法”。
- “塊枚舉法”本身就能通過(guò) GCD 來(lái)并發(fā)執(zhí)行遍歷操作,無(wú)須另行編寫代碼。而采用其他遍歷方式則無(wú)法輕易實(shí)現(xiàn)這一點(diǎn)。
- 若提前知道待遍歷的 collection 含有何種對(duì)象,則應(yīng)修改塊簽名,指出對(duì)象的具體類型。
49. 對(duì)自定義其內(nèi)存管理語(yǔ)義的 collection 使用無(wú)縫橋接
- 通過(guò)無(wú)縫橋接技術(shù),可以在 Foundation 框架中的 Objective-C 對(duì)象與 CoreFoundation 框架中的 C 語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之間來(lái)回轉(zhuǎn)換。
- 在 CoreFoundation 層面創(chuàng)建 collection 時(shí),可以指定許多回調(diào)函數(shù),這些函數(shù)表示此 collection 應(yīng)如何處理其元素。然后,可運(yùn)用無(wú)縫橋接技術(shù),將其轉(zhuǎn)換成具備特殊內(nèi)存語(yǔ)義的 Objective-C collection。
50. 構(gòu)建緩存時(shí)選用 NSCache 而非 NSDictionary
- 實(shí)現(xiàn)緩存時(shí)應(yīng)選用 NSCache 而非 NSDictionary 對(duì)象。因?yàn)?NSCache 可以提供優(yōu)雅的自動(dòng)刪減功能,而且是“線程安全的”,此外,它與字典不同,并不會(huì)拷貝鍵。
- 可以給 NSCache 對(duì)象設(shè)置上限,用以限制緩存中的對(duì)象總個(gè)數(shù)及“總成本”,而這些尺度則定義了緩存刪減其中對(duì)象的時(shí)機(jī)。但是絕對(duì)不要把這些尺度當(dāng)成可靠的“硬限制”,它們僅對(duì) NSCache 起指導(dǎo)作用。
- 將 NSPurgeableData 與 NSCache 搭配使用,可實(shí)現(xiàn)自動(dòng)清除數(shù)據(jù)的功能,也就是說(shuō),當(dāng) NSPurgeableData 對(duì)象所占內(nèi)存為系統(tǒng)所丟棄時(shí),該對(duì)象自身也會(huì)從緩存中移除。
- 如果緩存使用得當(dāng),那么應(yīng)用程序的響應(yīng)速度就能提高。只有那種“重新計(jì)算起來(lái)很費(fèi)事的”數(shù)據(jù),才值得放入緩存,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤讀取的數(shù)據(jù)。
51. 精簡(jiǎn) initialize 與 load 的實(shí)現(xiàn)代碼
- 在加載階段,如果類實(shí)現(xiàn)了 load 方法,那么系統(tǒng)就會(huì)調(diào)用它。分類里也可以定義此方法,類的 load 方法要比分類中的先調(diào)用。與其他方法不同,load 方法不參與覆寫機(jī)制。
- 首次使用某個(gè)類之前,系統(tǒng)會(huì)向其發(fā)送 initialize 消息。由于此方法遵從普通的覆寫規(guī)則,所以通常應(yīng)該在里面判斷當(dāng)前要初始化的是哪個(gè)類。
- load 與 initialize 方法都應(yīng)該實(shí)現(xiàn)的精簡(jiǎn)一些,這有助于保持應(yīng)用程序的響應(yīng)能力,也能減少引入“依賴環(huán)”的幾率。
- 無(wú)法在編譯期設(shè)定的全局變量,可以放在initialize 方法里初始化。
52. 別忘了 NSTimer 會(huì)保留其他目標(biāo)對(duì)象
- NSTimer 對(duì)象會(huì)保留其目標(biāo),直到計(jì)時(shí)器本身失效為止,調(diào)用 invalidate 方法可令計(jì)時(shí)器失效,另外,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)之后也會(huì)失效。
- 反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器,很容易引入保留環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身,那肯定會(huì)導(dǎo)致保留環(huán)。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的,也可能是通過(guò)對(duì)象圖里的其他對(duì)象間接發(fā)生的。
- 可以擴(kuò)充 NSTimer 的功能,用“塊”來(lái)打破保留環(huán)。不過(guò),除非 NSTimer 將來(lái)在公共接口里提供此功能,否則必須創(chuàng)建分類,將相關(guān)實(shí)現(xiàn)代碼加入其中。

