EffectiveObjective-C2.0
本書是iOS開發進階的必讀書籍之一。文中部分名詞的中文翻譯略坑,比如對block和GCD的翻譯。其他整體還好,原作者寫的比較用心。程式碼規範講了不少,底層原理講了一點點,且主要集中在第二章。另第六章對GCD的講解還算不錯。作者原文寫了52條編碼建議,不過本人在整理讀書筆記時並未按照原來的條數來做區分,只是把自己認為比較重要的做了標記,並記錄了下來。
第一章
1.對於NSString *someString = @“the string”; someString是一個分配在棧上的指標,@“the string”是分配在堆上的記憶體塊。Objective-C的物件所佔記憶體總是分配在堆上。 一個指標在32位架構的手機上佔4個位元組,在64位架構的手機上佔8個位元組。
2.在類的標頭檔案中儘量少引入其他類的標頭檔案,可以使用@class進行前向宣告。協議如果在多個類裡使用,則最好單獨放在一個檔案。目的都是為了解耦。
3.用字面量語法建立陣列時,若陣列元素中有物件為nil則會丟擲異常,反而有助於排查bug,程式更安全。字典中的鍵和值都必須是Objective-C物件。使用字面量建立出來的字串、陣列、字典都是不可變的。
4.多用型別常量,少用#define預處理指令。且變數一定要同時用static和const宣告,避免同名衝突的同時還可避免被無意修改。
5.凡是需要以按位或操作來組合的列舉都應使用NS_OPTIONS定義,若是列舉不需要互相結合,則應使用NS_ENUM來定義。如果用列舉來定義狀態機,比如switch-case,則最好不要有default分支,避免加一個列舉值時漏掉狀態機,若沒有default則編譯器會給出警告。
第二章
1.屬性的getter和setter方法是由編譯器在編譯期合成的。@dynamic關鍵字告訴編譯器:不要自動建立實現屬性所用的例項變數,也不要為其建立存取方法。
2.atomic並不能保證執行緒安全,若要實現執行緒安全的操作,還需要採用更為深層的鎖定機制才行。例如,一個執行緒在連續多次讀取某屬性值的過程中有別的執行緒在同時改寫該值,那麼即便將屬性宣告為atomic,也還是會讀到不同的屬性值。——用dispatch_queue可以解決
3.在初始化方法和dealloc方法中,總是應該直接通過例項變數來讀寫資料。
——具體原因見這裡
4.關於NSObject的isEqual:方法和hash方法,NSObject類對這兩個方法的預設實現是:當且僅當其指標值完全相等時,這兩個物件才相等。如果“isEqual:”方法判定兩個物件相等,那麼其hash方法也必須返回同一個值。但是,如果兩個物件的hash方法返回同一個值,那麼“isEqual:”方法未必會認為兩者相等。
5.當物件接收到無法解讀的訊息後,就會啟動訊息轉發機制。整個的流程為:動態方法解析-》備援接收者-》完整的訊息轉發。 resolveInstanceMethod-》forwardingTargetForSelector-》forwardInvocation-》訊息未能處理。
6.“isMemberOfClass:”能夠判斷出物件是否為某個特定類的例項,而“isKindOfClass:”則能夠判斷出物件是否為某類或其派生類的例項。
——兩個方法的具體實現細節見這裡
第三章
1.用字首避免名稱空間衝突,尤其是要打包成庫給第三方使用的程式碼。——可參考SDWebImage這個庫,所有方法均新增sd_字首。
2.description和debugDescription方法在NSLog列印資料時會呼叫類的這兩個方法,如果想列印時輸出更多細節,可自己實現這兩個方法。
3.應該儘量把對外公佈出來的屬性設為只讀,而且只在確有必要時才將屬性對外公佈。當然即使變數宣告為只讀,也可以通過KVC或計算指標偏移量後更改指標值的方式來改變變數。
4.方法名裡不要使用縮略後的型別名稱,給方法起名時的第一要務就是確保其風格與你自己的程式碼或所要整合的框架相符。
5.在ARC下使用try-catch捕獲異常要注意記憶體洩露的問題,開啟-fobjc-arc-exceptions編譯器標誌可以確保編譯器新增異常安全的程式碼,以避免記憶體洩露。
6.無論當前例項是否可變,若需獲取其可變版本的拷貝,均應該呼叫mutableCopy方法。同理,若需要不可變的拷貝,則總應通過copy方法來獲取。
7.Foundation框架中的所有collection類在預設情況下都執行淺拷貝,也就是說,只拷貝容器物件本身,而不復制其中的資料。
第四章
1.將類的實現程式碼分散到便於管理的多個類別中去。——這一點在一個類功能較為複雜時常用,把某個功能點抽出來作為一個類別方便管理,也方便閱讀。
2.為第三方類的類別名稱加字首。將類別方法加入類中這一操作是在執行期系統載入類別時完成的。如果類中本來就有該方法的實現,而類別又實現了一次,那麼類別中的方法會覆蓋原來類中的那一份程式碼實現。如果多個類別都實現了同一個方法,多次覆蓋的結果以最後一個載入的類別為準。
3.一般說來,類別無法把實現屬性所需的例項變數合成出來,但是使用關聯物件可以解決這個問題,不過除非不得已,儘量避免這樣設計。
4.使用類擴充套件是隱藏類的實現細節,包括一些方法和屬性,例項變數,能夠不暴露給外界的儘量隱藏在類擴充套件裡。
5.Objective-C動態訊息系統的工作方式決定了其不可能實現真正的私有方法或私有例項變數。因為在執行期總可以呼叫某些方法繞過此限制(比如KVC)。不過從一般意義上來說,它們還是私有的。
6.我們通常不直接訪問例項變數,而是通過設定訪問方法來做,因為這樣能夠觸發KVO通知。
7.若觀察者正在讀取屬性值而內部又在寫入該屬性時,則有可能引發競爭條件,合理使用同步機制能緩解此問題(dispatch_queue)。
第五章
1.ARC幾乎把所有記憶體管理事宜都交給編譯器來決定,記憶體管理的程式碼也是編譯的時候由編譯器在合適的地方插入retain和release等操作。
2.使用ARC時一定要記住,引用計數實際上還是要執行的,只不過保留與釋放操作現在是由ARC自動為你新增。ARC在呼叫release、retain這些方法時,並不通過普通的Objective-C訊息派發機制,而是直接呼叫其底層C語言版本。
3.__autoreleasing符號表示把物件“按引用傳遞”給方法時,使用這個特殊的修飾符。此值在方法返回時自動釋放。
4.使用ARC則不必要再編寫dealloc方法了(僅指Objective-C物件,CoreFoundation物件還是需要自己管理記憶體的),ARC會藉助C++物件的解構函式在.cxx_destruct方法中生成清理記憶體所需的程式碼。
5.ARC只負責管理Objective-C物件的記憶體,CoreFoundation等非Objective-C物件(比如CoreText,CoreGraphics)的記憶體需要自己管理。
6.在dealloc方法中移除通知以及KVO觀察。還需注意,不要在dealloc裡隨意呼叫其他方法,包括getter和setter。
7.自動釋放池可以巢狀使用,巢狀的好處在於可以控制程式的記憶體峰值。
8.殭屍物件(Zombie Object)是除錯記憶體管理問題的最佳方式。將NSZombieEnabled環境變數設定為YES即可開啟此功能。——通過選擇edit scheme->run->Diagnostics,然後勾選Enable Zombie Objects即可。
9.單例物件的引用計數值不會改變,這種物件的retain和release操作都是空操作。儘量避免依賴物件的引用計數值來編碼,因為其不可靠(autorelease)。
第六章
1.在block的記憶體佈局中,最重要的就是invoke變數,這是個函式指標,指向block的實現程式碼。GCD是純C語言的API。
2.block會把它所捕獲的所有變數都拷貝一份。不過拷貝的並不是物件本身,而是指向這些物件的指標變數。
3.block一旦複製到堆上,就成了帶引用計數的物件了。後續的複製操作都不會真的執行復制,而只是遞增block物件的引用計數。
4.全域性的block(NSConcreteGlobalBlock)的拷貝操作是個空操作,因為全域性block不可能為系統所回收。這種block實際上相當於單例。
5.對於通知來說,若沒有指定佇列,則按預設方式執行,也就是說,將由發出通知的那個執行緒來執行。
6.儘量多的使用dispatch_queue,少用同步鎖,包括@synchronized和NSLock。
7.dispatch_barrier_async稱之為柵欄方法,柵欄block必須單獨執行,不能與其他block並行執行。這支隊併發佇列有意義,因為序列佇列的塊總是按順序逐個來執行的。併發佇列如果發現接下來要處理的塊是個柵欄塊,那麼就一直要等當前所有併發塊都執行完畢,才會單獨執行這個柵欄塊。
8.dispatch_group機制可以把任務分組,等組內任務全部完成後再做其他操作。如果是網路請求,比如需要等待好幾個網路請求回撥之後才做重新整理UI的操作,可以使用dispatch_group_notify函式,需要注意的是,dispatch_group_notify的block裡的程式碼的執行時機是等group裡的所有任務都完成之後才執行,但是網路請求又是非同步的,GCD預設網路請求發出後該任務即執行完成,所以如果是需要等網路回撥之後才算任務完成的話,可以使用dispatch_group_enter和dispatch_group_leave來對分組裡要執行的任務數進行遞增和遞減操作(在網路發出之前遞增,在網路回撥後遞減)。
9.不要使用dispatch_get_current_queue,因為容易出現死鎖。
第七章
1.Cocoa本身並不是框架,但是裡面整合了一批建立應用程式時經常會用到的框架。
2.iOS8以後應用程式可以使用動態庫,也就是說,如果你的app僅支援iOS8及以上系統,可以新增自己的動態庫。——注:書裡說iOS程式不能包含動態庫,這一規則已經在2014年WWDC過時。
3.關於橋接技術。__bridge本身的意思是:ARC仍然具備這個Objective-C物件的所有權。而__bridge_retained則與之相反,意味著ARC將交出物件的所有權。與此相似,反向轉換可以通過__bridge_transfer來實現。
4.構建快取時選用NSCache而不是NSDictionary。NSCache勝過NSDictionary之處在於,當系統資源將要耗盡時,它可以自動刪減快取。另外NSCache是執行緒安全的,多個執行緒可以同時訪問NSCache。此外,它與字典不同,並不會拷貝鍵。
5.對於類的+load方法,必定會呼叫而且只呼叫一次。如果類別和類裡都定義了load方法,則先呼叫類裡的,再呼叫類別裡的。
6.因為無法判斷出各個類的載入順序,所以在load方法裡使用其他類是不安全的,因為很可能此時其他類還未載入。
7.如果某個類本身沒有實現load方法,那麼不管其各級父類是否實現了此方法,系統都不會呼叫。此外,類別和其所屬的類裡,都可能出現load方法。此時兩種實現程式碼都會呼叫,類的實現要比類別的實現先執行。
8.load方法務必實現的精簡一些,因為整個應用程式在執行load方法時都會阻塞。
9.+initialize方法:對每個類來說,該方法會在程式首次使用該類之前呼叫,且只呼叫一次。
10.+initialize方法只應該用來設定內部資料。不應該在其中呼叫其他方法,即便是本類自己的方法,也最好別呼叫。
11.只有把NSTimer放在NSRunLoop裡,它才能正常出發任務。
12.NSTimer會保留其目標物件,直到自身失效時再釋放此物件。呼叫invalidate方法可令計時器失效。