第一章 熟悉Objective - C
第一條:了解Objective-C語言的起源
要點:
1、Objective-C為C語言添加了面向對象特性,是其超集。Objective-C使用動態綁定的消息結構,也就是說,在運行時才會檢查對象類型。接受一條消息之后,究竟應該執行何種代碼,有運行時環境而非編譯器來決定。
2、理解C語言的核心理念有助于寫好Objective-C程序。尤其要掌握內存模型與指針。
第二條:在類的頭文件中盡量少引入其他頭文件
要點:
1、除非的確有必要,否則不要引入頭文件(沒必要我為什么要引入?)。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類,并在實現文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合(coupling)。向前聲明@class。將頭文件的引入時機盡量延后,只有在確有需要時才引入,這樣可以減少類的使用者所需引入的頭文件的數量。還會減少編譯時間。向前聲明也解決了兩個類相互引用的問題。
2、有時無法向前聲明,比如要聲明某個類遵循一項協議。這種情況下,盡量把“該類遵循某協議”的這條聲明移動到“class-continuation分類”中。如果不行的話,就把協議單獨放在一個頭文件中,然后將其引入。
第三條:多用字面量,少用與之等價的方法
要點:
1、應該使用字面量語法來創建字符串、數值、數組、字典。與創建此類對象的常規方法相比,這么做更加簡明扼要。
2、應該通過取下標操作來訪問數組下標或字典中的鍵所對應的元素。
3、用字面量語法創建數組或字典時,若值中有nil,則會拋出異常。因此,務必確保值里不含nil。通過實踐可知,字面量創建數組(或是字典)的效果等于是先創建了一個數組(或是字典),然后把方括號內的所有對象都加到這個數組中。
NSString * obj1 = @"0";
NSString * obj2 = nil;
NSString * obj3 = @"2";
NSArray * arrayA = [NSArray arrayWithObjects:obj1,obj2,obj3, nil];
NSArray * arrayB = @[obj1,obj2,obj3];
// 按字面量語法創建數組arrayB時會拋出異常。arrayA雖然能創建出來,但是其中卻只含有object1一個對象,原因在于,“arrayWithObjects:”方法會一次處理各個參數,直道發現nil為止,由于object2是nil,所以該方法會提前結束,不會拋出異常。
// 這個微妙的差別表明,使用字面量語法更為安全。拋出異常令應用程序終止執行,這比創建好數組之后才發現元素個數少了要好。向數組中插入nil通常說明程序有錯,而通過異常可以更快的發現這個錯誤。
第四條:多用類型常量,少用#define預處理指令
要點:
1、不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前據此執行查找與替換操作。即使有人重新定義了常量值,編譯也不會產生警告信息,這將導致應用程序中的常量值不一致。
2、在實現文件中使用static const來定義“只在編譯單元內可見的常量”。由于此類常量不在全局符號表中,所以無須為其名稱加前綴。
3、在頭文件中使用extern來聲明全局常量,并在相關實現文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應該加以區隔,通常用與之相關的類名做前綴。
第五條:用枚舉表示狀態、選項、狀態碼
要點:
1、應該用枚舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起個易懂的名字。
2、如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那么就將各選項值定義為2的冪,以便通過按位或操作將其組合起來。
3、用NS_ENUM與NS_OPTIONS宏來定義枚舉類型,并指明其底層數據類型,這樣做可以確保枚舉是用開發者所選的底層數據類型實現出來的,而不會采用編譯器所選的類型。
4、在處理枚舉類型的switch語句中不要實現default分支,這樣的話,加入新枚舉之后,編譯器就會提示開發者:switch語句并未處理所有枚舉。
第二章 對象、消息、運行時
第六條:理解“屬性”這一概念
要點:
1、可以用@property語法來定義對象中所封裝的數據。
2、通過“特質”來指定存儲數據所需的正確語義。
3、在設置屬性所對應的實例變量時,一定要遵從該屬性所聲明的語義。
4、開發iOS程序時應該使用nonatomic屬性,因為atomic屬性會嚴重影響性能,但在開發Mac OS X程序時,使用atomic屬性通常都不會有性能瓶頸。如果開發過iOS程序,你就會發現,其中所有屬性都聲明為nonatomic。這樣做的歷史原因是:在iOS中使用同步鎖的開銷較大,這會帶來性能問題,一般情況下并不要求屬性必須是“原子的”,因為這并不能保證“線程安全”,若要實現“線程安全”的操作,還需要采用更為深層的鎖定機制才行。
第七條:在對象內部盡量直接訪問實例變量
要點:
1、在對象內部讀取數據時,應該直接通過實例變量來讀,而寫入數據時,則應該通過屬性來寫。
2、在初始化方法及dealloc方法中,總是應該直接通過實例變量來讀寫數據。
3、有時會使用惰性初始化技術配置某份數據,這種情況下,需要通過屬性來讀取數據。
我們使用點語法,通過存取(getter、setter)方法來訪問實例變量,和直接訪問實例變量(下劃線調用)這兩種寫法有幾個區別:由于不經過Objective-C的“方法派發”步驟,所以直接訪問實例變量的速度當然比較快。在這種情況下,編譯器所生成的代碼會直接訪問保存對象實例變量的那塊內存。直接訪問實例變量時,不會調用其“設置方法”,這就繞過了為相關屬性所定義的“內存管理語義”。比方說,如果在ARC下直接訪問一個聲明為copy的屬性,那么并不會拷貝該屬性,只會保留新值并釋放舊值。如果直接訪問實例變量,那么不會觸發“鍵值觀測”通知。這樣做是否會產生問題,還取決于具體的對象行為。通過屬性來訪問有助于排查與之相關的錯誤,因為可以給setter、getter方法中下斷點,監控該屬性的調用者及其訪問時機。
第八條:理解“對象等同性”這一概念
要點:
1、若想檢測對象的等同性,請提供“isEqual:”與hash方法。
2、相同的對象必須具有相同的哈希碼,但是兩個哈希碼相同的對象卻未必相同。
3、不要盲目地逐個檢查每條屬性,而是應該依照具體需求制定檢測方案。
4、編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法。
NSObject協議中有兩個用于判斷等同性的關鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
NSObject類對這個兩個方法的默認實現是:當且僅當其“指針值”完全相等時,這兩個對象才相等。若想在自定義的對象中正確覆寫這些方法,就必須先理解其約定。如果“isEqual:”方法判定啷個對象相等,那么其hash方法也必須返回同一個值。但是如果兩個對象的hash方法返回同一個值,那么“isEqual”方法未必會認為兩者相等。
比如有下面這個類:
@interface TestClass : NSObject
@property (nonatomic , copy) NSString * firstName;
@property (nonatomic , copy) NSString * lastName;
@property (nonatomic , assign) NSUInteger age;
@end
我們認為,如果兩個TestClass的所有字段均相等,那么這兩個對象就相等,于是“isEqual:”方法可以寫成:
-(BOOL)isEqual:(id)object
{
if (self == object)
return YES;
if ([self class] != [object class])
return NO;
TestClass * subClass = (TestClass *)object;
if (![_firstName isEqualToString:subClass.firstName]) {
return NO;
}
if (![_lastName isEqualToString:subClass.lastName]) {
return NO;
}
if (_age != subClass.age) {
return NO;
}
return YES;
}
首先,直接判斷兩個指針是否相等。若相等,則其均指向同一對象,所以受測的對象也必定相等。接下來,比較兩對象所屬的類。若不屬于同一個類,則兩個對象不相等。不過,有時候我們可能認為:一個TestClass實例可以與其子類實例相等。在繼承體系中判斷等同性時,經常遭遇此類問題。所以實現“isEqual:”方法時要考慮到這種情況。最后,檢測每個屬性是否相等,只要其中有不相等的屬性,就判定兩個對象不等,否則兩個對象相等。
第九條:以“類族模式”隱藏實現細節
要點:
1、類族模式可以把實現細節隱藏在一套簡單的公共接口后面。
2、系統框架中經常使用類族。
3、從類族的公共抽象基類中繼承子類時要當心,若有開發文檔,則應首先閱讀。
“類族(class cluster)”是一種很有用的模式,可以隱藏“抽象基類”背后的實現細節。Objective-C系統框架中普遍使用此模式。比如,iOS用戶界面框架UIKit中就有一個名為UIButton的類。想創建按鈕,需要調用下面這個“類方法”: +(UIButton*)buttonWithType:(UIButtonType)type;該方法返回的對象,其類型取決于傳入的按鈕類型(button type)。然而,若是需要依按鈕類型來切換的繪制方法有很多種,那么就會變得很麻煩了。優秀的程序員會將這種代碼重構為多個子類,把各種按鈕所用的繪制方法放到相關子類中去。不過,這么做需要用戶知道各種子類才行。此時應該使用“類族模式”,該模式可以靈活對應多個類,將他們的實現細節隱藏在抽象基類后面,以保持接口簡潔。用戶無須自己創建子類實例,只需調用基類方法來創建即可。
第十條:在既有類中使用關聯對象存放自定義數據
要點:
1、可以通過“關聯對象”機制來把兩個對象連接起來。
2、定義關聯對象時可指定內存管理語義,用以模仿定義屬性時所采用的“擁有”與“非擁有關系”。
3、只有在其他做法不可行時才應選用關聯對象,因為這種做法通常會引入難以查找的bug。
第十一條:理解objc_msgSend的作用
要點:
1、消息由接受者、選擇器及參數構成。給某對象“發送消息”也就相當于在該對象上“調用方法”。
2、發送某對象的全部消息都要由“動態消息派發系統”來處理,該系統會查出對應的方法,并執行其代碼。
在Objective-C中,如果向某對象傳遞消息,那就會使用動態綁定機制來決定需要用的方法。在底層,所有方法都是普通的C語言韓束,然而對象收到消息之后,究竟該調用哪個方法則完全于運行時決定,甚至可以在程序運行時改變,這些特性使得Objective-C成為一門真正的動態語言。給對象發送消息可以這樣來寫 ** id returnValue =[someObject messageName:paremeter]; someObject叫做“接收者”,messageName叫做“選擇器”。選擇器于參數結合起來稱為“消息”。編譯器看到此消息后,將其轉換為一條標準的C語言函數調用,所調用的函數乃是消息傳遞機制中的核心函數,叫做objc_msgSend,其原型為:void objc_msgSend(id self , SEL cmd,...) objc_msgSend函數會依據接收者于選擇器的類型來調用適當的方法。為了完成此操作,該方法需要在接收者所屬的類中搜尋“方法列表”,如果能找到與選擇器名稱相符的方法,就跳轉至實現代碼。若是招不到,那就沿著繼承體系繼續向上找,等找到合適的方法之后再跳轉。如果最終還是招不到相符的方法,那就執行“消息轉發”操作。這么說來,想調用一個方法似乎需要很多步驟,所幸objc_msgSend會將匹配結果緩存在“快速映射表”里面,每個類都有這樣一塊緩存,若是稍后還向該類發送與選擇器相同的消息,那么執行起來就很快了。當然了,這種“快速執行路徑”還是不如“靜態綁定的函數調用操作”那樣迅速,不過只要把選擇器緩存起來了,也就不會慢上很多。
第十二條:理解消息轉發機制
要點:
1、若對象無法響應某個選擇器,則進入消息轉發流程。
2、通過運行期的動態方法解析功能,我們可以在需要用到某個方法時再將其加入類中。
3、對象可以把其無法解讀的某些選擇器轉交給其他對象來處理。
4、經過上述兩步之后,如果還是沒辦法處理選擇器,那就啟動完整的消息轉發機制。
第十三條:用“方法調配技術”調試“黑盒方法”
要點:
1、使用另一份實現來替換原有的方法實現,這道工序叫做“方法調配”,開發者常用此技術向原有實現中添加新功能。
2、一般來說,只有在調試程序的時候才需要在運行時修改方法實現,這種做法不宜濫用。
3、在運行期,可以向類中新增或替換選擇器所對應的方法實現。
NSString * string = @"small BIG";
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
NSLog(@"%@",[string uppercaseString]);
NSLog(@"%@",[string lowercaseString]);
// output small big
// output SMALL BIG
第十四條:理解“類對象”的用意
要點:
1、每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的繼承體系。
2、如果對象類型無法在編譯期確定,那么就應該使用類型信息查詢方法來探知。
3、盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現了消息轉發功能。
第三章 接口與API設計
第十五條:用前綴避免命名空間沖突
要點:
1、選擇與你的公司、應用程序或二者皆有關聯之名稱作為類名的前綴,并在所有代碼中均使用這一前綴。
2、若自己所開發的程序庫中用到了第三方庫,則應為其中的名稱加上前綴。
第十六條:提供“全能初始化方法”
要點:
1、在類中提供一個全能初始化方法,并于文檔里指明。其他初始化方法均應調用此方法。
2、若全能初始化方法與超類不同,則需覆寫超類中的對應方法。
3、如果超類的初始化方法不適用于子類,那么應該覆寫這個超類方法,并在其中拋出異常。
第十七條:實現description方法
要點:
1、實現description方法返回一個有意義的字符串,用以描述該實例。
2、若想在調試時打印出更詳盡的對象描述信息,則應實現debugDescription。
第十八條:盡量使用不可變對象
要點:
1、盡量創建不可變的對象。
2、若某屬性僅可于對象內部修改,則在“class-continuation分類”中將其由readonly屬性擴展為readwrite屬性。
3、不要把可變的collection作為屬性公開,而應提供相關的方法,以此修改對象中的可變collection。
第十九條:使用清晰而協調的命名方式
要點:
1、起名時要遵從標準的Objective-C命名規范,這樣創建出來的接口更容易為開發者所理解。
2、方法名要言簡意賅,從左至右讀起來要像個日常用語中的句子才好。
3、方法名里不要使用縮略后的類型名稱。
4、給方法起名時的第一要務就是確保其風格與你自己的代碼或要集成的框架相符。
第二十條:為私有方法名加前綴
要點:
1、給私有方法的名稱加上前綴,這樣可以很容易地將其他公共方法區分開。
2、不要單用一個下劃線做私有方法的前綴,因為這種做法是預留給蘋果公司的。
第二十一條:理解Objective-C錯誤模型
要點:
1、只有發生了可使整個應用程序崩潰的嚴重錯誤時,才應使用異常。
2、在錯誤不那么嚴重的情況下,可以指派“委托方法”來處理錯誤,也可以把錯誤信息放在NSError對象里,經由“輸出參數”返回給調用者。
第二十二條:理解NSCopying協議
要點:
1、若想令自己所寫的對象具有拷貝功能,則需實現NSCopying協議。
2、如果自定義的對象分為可變版本與不可變版本,那么就要同時實現NSCopying與NSMutableCopying協議。
3、復制對象時需決定采用淺拷貝還是深拷貝,一般情況下應該盡量執行淺拷貝。
4、如果你所寫的對象需要深拷貝,那么可考慮新增一個專門執行深拷貝的方法。
第四章 協議與分類
第二十三條:通過委托與數據源協議進行對象間通信
要點:
1、委托模式為對象提供了一套接口,使其可由此將相關事件告知其他對象。
2、將委托對象應該支持的接口定義成協議,在協議中把可能需要處理的事件定義成方法。
3、當某對象需要從另外一個對象中獲取數據時,可以使用委托模式。這種情景下,該模式亦稱“數據源協議”。
4、若有必要,可實現含有位段的結構體,將委托對象是否能響應相關協議方法這一信息緩存至其中。
第二十四條:將類的實現代碼分散到便于管理的數個分類之中
要點:
1、使用分類機制把類的實現代碼分成易于管理的小塊。
2、將應該視為“私有”的方法歸入名叫Private的分類中,以隱藏實現細節。
第二十五條:總是為第三方類的分類名稱加前綴
要點:
1、向第三方類中添加分類時,總應給其名稱加上你專用的前綴。
第二十六條:勿在分類中聲明屬性
要點:
1、把封裝數據所用的全部屬性都定義在主接口里。
2、在“class-continuation分類”之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。
第二十七條:使用“class-continuation分類”隱藏實現細節
要點:
1、通過“class-continuation分類”向類中新增實例變量。
2、如果某屬性在主接口中聲明為“只讀”,而類的內部又要用設置方法修改此屬性,那么就在“class-continuation分類”中將其擴展為“可讀寫”。
3、把私有方法的原型聲明在“class-continuation分類”里面。
4、若想使所類所遵循的協議不為人所知,則可于“class-continuation分類”中聲明。
第二十八條:通過協議提供匿名對象
要點:
1、協議可在某種程度上提供匿名類型,具體的對象類型可以淡化成遵從某協議的id類型,協議里規定了對象所應實現的方法。
2、使用匿名對象來隱藏類型名稱(或類名)。
3、如果具體類型不重要,重要的是對象能夠響應(定義在協議里的)特定方法,那么可使用匿名對象來表示。
第五章:內存管理
第二十九條:理解引用計數
要點:
1、引用計數機制通過可以遞增遞減的計數器來管理內存。對象創建好之后,其保留計數至少為1。若保留計數為正,則對象繼續存活。當保留計數降為0時,對象就被銷毀了。
2、在對象生命期中,其余對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增及遞減保留計數。
第三十條:以ARC簡化引用計數
要點:
1、有ARC之后,程序員就無須擔心內存管理的問題了。使用ARC來編程,可省去類中的許多“樣板代碼”。
2、ARC管理對象生命期的辦法基本上就是:在合適的地方插入“保留”及“釋放”操作。在ARC環境下,變量的內存管理語義可以通過修飾符指明,而原來則需要手工執行“保留”及“釋放”操作。
3、由方法所返回的對象,其內存管理語義總是通過方法名來提現,ARC將此確定為開發者必須遵守的規則。
4、ARC只負責管理Objective-C對象的內存。尤其要注意:CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRelease。
第三十一條:在dealloc方法中只釋放引用并解除監聽
要點:
1、在dealloc方法里,應該做的事情就是釋放指向其他對象的引用,并取消原來訂閱的“鍵值觀測”(KVO)或NSNotificationCenter等通知,不要做其他事情。
2、如果對象持有文件描述符等系統資源,那么應該專門編寫一個方法來釋放此種資源。這樣的類要和其使用者約定:用完資源后必須調用close方法。
3、執行異步任務的方法不應再dealloc里調用;只能在正常狀態下執行的那些方法也不應該在dealloc里調用,因為此時對象已經處于正在回收的狀態了。
第三十二條:編寫“異常安全代碼”時留意內存管理問題
要點:
1、捕獲異常時,一定要注意將try塊內所創立的對象清理干凈。
2、在默認情況下,ARC不生成安全處理異常所需的清理代碼,開啟編譯器標志后,可生成這種代碼,不過會導致應用程序變大,而且降低運行效率。
第三十三條:以弱引用避免保留環
要點:
1、將某些引用設為weak,可避免出現“保留環”。
2、weak引用可以自動清空,也可以不自動清空。自動清空是隨著ARC而引入的新特性,由運行期系統來實現。在具備自動清空功能的弱引用上,可以隨意讀取其數據,因為這種引用不會指向已經回收過的對象。
第三十四條:以“自動釋放池塊”降低內存峰值
要點:
1、自動釋放池排布在棧中,對象收到autorelease消息后,系統將其放入最頂端的池里。
2、合理運用自動釋放池,可降低應用程序的內存峰值。
3、@autoreleasepool這種新式寫法能創建出更為輕便的自動釋放池。
第三十五條:用“僵尸對象”調試內存管理問題
要點:
1、系統在回收對象時,可以不將其真的回收,而是把它轉化為僵尸對象。通過環境變量NSZombieEnabled可開啟此功能。
2、系統會修改對象的isa指針,令其指向特殊的僵尸類,從而使該對象變為僵尸對象。僵尸類能夠響應所有的選擇器,響應方式為:打印一條包含消息內容及其接受者的消息,然后終止應用程序。
第三十六條:不要使用retainCount
要點:
1、對象的保留計數看似有用,實則不然,因為任何給定時間點上的“絕對保留計數”都無法反映對象生命期的全貌。
2、引入ARC之后,retainCount方法就正式廢止了,在ARC下調用該方法會導致編譯器報錯。
第六章:塊與大中樞派發
第三十七條:理解“塊”這一概念
要點:
1、塊是C、C++、Objective-C中的詞法閉包。
2、塊可接受參數,也可返回值。
3、塊可以分配在棧或堆上,也可以是全局的。分配在棧上的塊可以拷貝到堆里,這樣的話,就和標準的Objective-C對象一樣,具備引用計數了。
第三十八條:為常用的塊類型創建typedef
要點:
1、以typedef重新定義塊類型,可令塊變量用起來更加簡單。
2、定義新類型時應遵從現有的命名習慣,勿使其名稱與別的類型相沖突。
3、不妨為同一個塊簽名定義多個類型別名。如果要重構的代碼使用了塊類型的某個別名,那么只需修改相應typedef中的塊簽名即可,無須改動其他typedef。
第三十九條:用handler塊降低代碼分散程度
要點:
1、在創建對象時,可以使用內聯的handler塊將相關業務邏輯一并聲明。
2、在有多個實例需要監控時,如果采用委托模式,那么經常需要根據傳入的對象來切換,而若改用handler塊來實現,則可直接將塊與相關對象放在一起。
3、設計API時如果用到了handler塊,那么可以增加一個參數,使調用者可通過此參數來決定應該把塊安排在哪個隊列上執行。
第四十條:用塊引用其所屬對象時不要出現保留環
要點:
1、如果塊所捕獲的對象直接或間接地保留了塊本身,那么就得當心保留環問題。
2、一定要找個適當的時機解除保留環,而不能把責任推給API的調用者。
第四十一條:多用派發隊列,少用同步鎖
要點:
1、派發隊列可用來表述同步語義,這種做法要比使用@synchronized塊或NSLock對象更簡單。
2、將同步與異步派發結合起來,可以實現與普通加鎖機制一樣的同步行為,而這么做卻不會阻塞執行異步派發的線程。
3、使用同步隊列及柵欄塊,可以令同步行為更加高效。
第四十二條:多用GCD,少用performSelector系列方法
要點:
1、performSelector系列方法在內存管理方面容易有疏忽。它無法確定將來要執行的選擇器具體是什么,因而ARC編譯器也就無法插入適當的內存管理方法。
2、performSelector系列方法所能處理的選擇器太過局限了,選擇器的返回值類型及發送給方法的參數個數都受到限制。
3、如果向把任務放在另一個線程上執行,那么最好不要用performSelector系列方法,而是應該把任務封裝到塊里,然后調用大中樞派發機制的相關方法來實現。
第四十三條:掌握GCD及操作隊列的使用時機
要點:
1、在解決多線程與任務管理問題時,派發隊列并非唯一方案。
2、操作隊列提供了一套高層的Objective-C API,能實現純GCD所具備的絕大部分功能,而且還能完成一些更為復雜的操作,那些操作若改用GCD來實現,則需要另編寫代碼。
第四十四條:通過Diapatch Group機制,根據系統資源狀況來執行任務
要點:
1、一系列任務可歸入一個dispatch group之中。開發者可以在這組任務執行完畢時獲得通知。
2、通過dispatch group,可以在并發式派發隊列里同時執行多項任務。此時GCD會根據系統資源狀況來調度這些并發執行的任務。開發者若自己來實現此功能,則需編寫大量代碼。
第四十五條:使用dispatch_once來執行只需運行一次的線程安全代碼
要點:
1、經常需要編寫“只需執行一次的線程安全代碼”。通過GCD所提供的dispatch_once函數,很容易就能實現此功能。
2、標記應該聲明在static或global作用域中,這樣的話,在把只需執行一次的塊傳給dispatch_once函數時,傳進去的標記也是相同的。
第四十六條:不要使用dispatch_get_current_queue
要點:
1、dispatch_get_current_queue函數的行為常常與開發者所預期的不同。此函數已經廢棄,只應做調試之用。
2、由于派發隊列是按層級來組織的,所以無法單用某個隊列對象來描述“當前隊列”這一概念。
3、dispatch_get_current_queue函數用于解決由不可重入的代碼引發的死鎖,然而能用此函數解決的問題,通常也能改用“隊列特定數據”來解決。
第七章:系統框架
第四十七條:熟悉系統框架
要點:
1、許多系統框架都可以直接使用。其中最重要的是Foundation與CoreFoundation,這兩個框架提供了構建應用程序所需的許多核心功能。
2、很多常見任務都能用框架來做,例如音頻與視頻處理、網絡通信、數據管理等。
3、請記住:用純C寫成的框架與用Objective-C寫成的一樣重要,若想成為優秀的Objective-C開發者,應該掌握C語言的核心概念。
第四十八條:多用塊枚舉,少用for循環
要點:
1、遍歷collection有四種方式,最基本的辦法是for循環,其次是NSEnumerator遍歷法及快速遍歷法,最新、最先進的方式則是“塊枚舉法”。
2、“塊枚舉法”本身就能通過GCD來并發執行遍歷操作,無須另行編寫代碼。而采用其他遍歷方式則無法輕易實現這一點。
3、若提前知道待遍歷的collection含有何種對象,則應該修改塊簽名,指出對象的具體類型。
第四十九條:對自定義其內存管理語義的collection使用無縫橋接
要點:
1、通過無縫橋接技術,可以在Foundation框架中的Objective-C對象與CoreFoundation框架中的C語言數據結構之間來回轉換。
2、在CoreFoundation層面創建collection時,可以指定許多回調函數,這些函數表示此collection應如何處理其元素,然后,可運用無縫橋接技術,將其轉換成具備特殊內存管理語義的Objective-C collection。
第五十條:構建緩存時選用NSCache而非NSDictionary
要點:
1、實現緩存時應選用NSCache而非NSDictionary對象。因為NSCache可以提供優雅的自動刪減功能,而且是“線程安全的”,此外,它與字典不同,并不會拷貝鍵。
2、可以給NSCache對象設置上限,用以限制緩存中的對象總個數及“總成本”,而這些尺度則定義了緩存刪減其中對象的時機。但是絕對不要把這些尺度當成可靠的“硬限制”,它們僅對NSCache起指導作用。
3、將NSPurgeableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPurgeableData對象所占內存為系統所丟棄時,該對象自身也會從緩存中移除。
4、如果緩存使用得當,那么應用程序的響應速度就能提高。只有那種“重新計算起來很費事的”數據,才值得放入緩存,比如那些需要從網絡獲取或從磁盤讀取的數據。
第五十一條:精簡initialize與load的實現代碼
要點:
1、在加載階段,如果類實現了load方法,那么系統就會調用它。分類里也可以定義此方法,類的load方法要比分類中的先調用。與其他方法不同,load方法不參與覆寫機制。
2、首次使用某個類之前,系統會向其發送initialize消息。由于此方法遵從普通的覆寫規則,所以通常應該在里面判斷當前要初始化的是哪個類。
3、load與initialize方法都應該實現的精簡一些,這有助于保持應用程序的響應能力,也能減少引入“依賴環”的幾率。
4、無法在編譯器設定的全局常量,可以放在initialize方法里初始化。
第五十二條:別忘了NSTimer會保留其目標對象
要點:
1、NSTimer對象會保留其目標,直到計時器本身失效為止,調用invalidate方法可令計時器失效,另外,一次性的計時器在觸發完任務之后也會失效。
2、反復執行任務的計時器,很容易引入保留環,如果這種計時器的目標對象又保留了計時器本身,那肯定會導致保留環。這種環狀保留關系,可能是直接發生的,也可能是通過對象圖里的其他對象間接發生的。
3、可以擴充NSTimer的功能,用“塊”來打破保留環。不過,除非NSTimer將來在公共接口里提供此功能,否則必須創建分類,將相關實現代碼加入其中。