Effective Objective-C 2.0 Tips 總結 Chapter 1 & Chapter 2

noark9發表於2016-11-28

下面只是對讀到的所有 Tips 結合我平時開發中遇到的問題進行總結,每一個 Tips 和書中的每一條對應,本文的目的是去掉書中的大部分討論的內容,讓人能夠馬上使用這些 Tips,建議閱讀過原書後食用更佳。

Chapter 1 熟悉 Objective-C

  • Tips 1 Objective-C 的起源

    • Objective-C 是從 C 語言演化而來,有 C 的一些基礎會有很大幫助
  • Tips 2 標頭檔案中減少引用

    • 減少在類的標頭檔案中 import 其他標頭檔案,如果使用其他類,那麼使用@class ClassName;來進行Forward Declaring
    • 對於協議,每個協議放到對應的標頭檔案,使用時候引用
    • 對於委託協議(比如 UITableViewUITableViewDelegate)因為只有與委託類放在一起才有意義,所以就不用單獨分離標頭檔案,應該放到定義 UITableView 的標頭檔案中
  • Tips 3 使用字面量

    • 對於 NSStringNSNumberNSDictionaryNSArray 使用類似 @"String"@1@[]@{} 不要使用等價方法
  • Tips 4 使用型別常量

    • 定義常量時,使用型別常量不要使用 #define,比如: ``` // 使用如下的方式定義 static const NSInteger kInteger = 1; // 而不是

      define SOME_INTEGER 1

    ``` 這樣可以給編譯器型別資訊,在編譯時和開發時能夠進行型別檢查

    • 每一個 m 檔案都是一個編譯單元
    • 使用static 宣告表示在本編譯單元有效,若需要將變數放到全域性有效,那麼需要使用 extern
    • 使用 const 表示常量不會被修改
  • Tips 5 使用列舉表示狀態,選項,狀態碼

    • 使用 NS\_ENUM 巨集定義列舉,因為列舉是按順序的,也就是列舉值是1,2,3…… 這樣的
    • 使用 NS\_OPTION 定義選項,因為選項是按位的,也就是選項是通過 1 << 01 << 1 這樣來定義的,表示1右移

Chapter 2 物件,訊息,執行時

  • Tips 6 理解屬性

    • 使用屬性,而不是例項變數,在程式碼中使用點(.)操作符訪問屬性
    • 屬性會生成對應的例項變數,一般是屬性名前加下劃線,也可以在類的實現程式碼中通過 @synthesize 來指定,例如:@synthesize firstName = _firstName
    • 使用 @dynamic 告訴編譯器不需要生成對應的getter 和 setter
    • 屬性的 attribute 會影響編譯器生成的程式碼
    • atomic / nonatomic,原子性,一般我們都使用 nonatomic 因為 iOS 的屬性鎖開銷很大,另外 atomic 並不能保證執行緒安全
    • readwrite / readonly,讀寫或是隻讀
    • 記憶體管理要注意的
      • assign,簡單型別直接賦值
      • strong,表示持有
      • weak,不持有,在物件被釋放時屬性將會變成 nil
      • unsafe_unretained,不持有,在物件被釋放時屬性不會變成 nil
      • copy,設定屬性時會呼叫物件的 -copy 方法獲得新的物件,建議所有不可變的 NSStringNSArrayNSDictionary 都使用這個方法,可變的型別不可以使用這個方法
      • getter=name / setter=name,指定 getter 和 setter 方法的名字
  • Tips 7 物件內部直接訪問例項變數,設定時通過屬性方法

    • 直接訪問例項變數減少方法呼叫消耗
    • 設定通過屬性方法,呼叫實際的 setter,能夠保證寫入控制和 KVO 的觸發
    • 可以在 getter 和 setter 方法中加入斷點方便除錯
    • 惰性初始化的變數,因為需要需要重寫 getter 方法,所以不能使用直接訪問例項變數來訪問
  • Tips 8 物件相等

    • == 只是比較物件指標是否相等,深層的比較需要使用 -isEqual: 方法
    • 如果 -isEqual: 返回返回真,那他們的 -hash 方法要返回同一個值,但是 -hash 方法返回同一個值的兩個物件 -isEqual: 不一定為真
    • -hash 方法用作在集合型別中計算索引,如果所有物件的這個方法都返回同一個值,那麼在集合中檢索物件效能會很差,這個方法應該使用計算速度快,並且不容易碰撞的方法實現
    • 有特定相等判斷方法的物件,優先使用特定相等判斷方法,可以減少呼叫次數和對物件進行型別檢查(例如 NSString-isEqualToString: 方法)
    • 放入集合物件中的物件,需要保證 -hash 方法得到的值不會變,如果放入集合後,修改集合內物件導致 -hash 的值發生變化,那麼集合物件是不會知道 -hash 值有變化,並且將來會出現奇怪的錯誤
  • Tips 9 類簇

    • 類簇很有用,可以把實現細節隱藏在抽象的基類中
    • 對於使用到類簇的物件,需要使用 -isKindOfClass: 來判斷,不可使用 [object class] == [Class class] 或者 -isMemberOfClass: 來判斷
    • 若要繼承類簇中的類,那麼需要根據文件實現對應的方法
  • Tips 10 Associated Object

    • 可以為已有的物件建立新的屬性
    • 設定方法
    • void objc\_setAssociatedObject(id object, void *key, id value, objc\_AssociationPolicy policy)
    • id objc\_getAssociatedObject(id object, void *key)
    • id objc\_removeAssociatedObject(id object)
    • 關聯型別和屬性的對應(上面 policy 的值)
    • OBJC_ASSOCIATION_ASSIGN:assign
    • OBJC_ASSOCIATION_RETAIN_NONATOMIC:nonatomic, retain
    • OBJC_ASSOCIATION_RETAIN:retain
    • OBJC_ASSOCIATION_COPY_NONATOMIC:nonatomic, copy
    • OBJC_ASSOCIATION_COPY:copy
    • key 的值,使用一個 opaque pointer,一般來說使用靜態全域性變數
  • Tips 11 理解 objc\_msgSend 的作用

    • Objective-C 中所有的方法,都是 C 函式
    • 給某個物件的訊息全部都是動態傳送的,如下
    • id returnValue = [someObject messageName:parameter]
    • 編譯後,將會使用 objc\_msgSend 函式處理訊息傳送,得到 id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
    • objc\_msgSend 會去 someObject 的方法列表中對應的函式,如果找不到,那麼沿著繼承體系繼續找,還是找不到,那麼會進行訊息轉發操作(在 Tips 12 講解)
    • Objective-C 的執行時已經做了很多保證讓這套機制效能很好
    • 其中一個優化就是尾呼叫優化,Objective-C 的每個物件的方法都是 C 方法,並且有和 objc\_msgSend 一樣的原型,這樣在 objc\_msgSend 從物件的方法列表中找到對應函式時,可以直接跳轉過去,不需要重新在呼叫堆疊中插入新的棧幀
  • Tips 12 理解訊息轉發

    • 因為 Objective-C 使用執行時來決定具體呼叫的方法,所以在執行之前是不知道一個物件是否能響應特點方法的
    • 訊息轉發是在一個物件收到無法解讀的訊息時觸發的機制
    • 訊息轉發的過程
    • 第一步,進行動態方法解析——詢問接收者,所屬的類,看是否能動態新增方法,來處理未知的 selector
    • 第二步,第一步無法處理這個 selector 的話進行訊息轉發——首先,讓接收者看看是否有物件能處理這個訊息,如果有,那麼丟給他處理;如果沒有,那麼執行時會把所有和訊息相關的東西放到一個 NSInvocation 物件裡面,最後再給接收者一次處理的機會
    • 動態方法解析
    • 過程,下面兩個過程是漸進的,第一個失敗了,那麼執行第二個
      • 呼叫 + (BOOL)resolveInstanceMethod:(SEL)selector 詢問類是否能新增一個例項方法處理這個訊息
      • 呼叫 + (BOOL)resolveClassMethod:(SEL)selector 詢問類是否能增加一個類方法來處理這個訊息
    • 用法,相關方法實現已經寫好,只等著執行時去動態插入到類裡面就行了
      • 實現 @dynamic 屬性
    • 訊息轉發
    • 備選的訊息接收者
      • 呼叫 - (id)forwardingTargetForSelector:(SEL)selector 詢問接收者是否有另一個物件來處理這個訊息
      • 可以模擬多重繼承,由物件內的其他物件來處理這個訊息,但是從呼叫者看來,是被呼叫的物件處理的訊息
      • 這樣轉發的訊息,我們是無法進行操作或是修改訊息內容的
    • 完整的訊息轉發
      • 建立 NSInvocation 物件,把 selector,target 和引數都放進去
      • 訊息派發系統(message-dispatch system)呼叫 - (void)forwardInvocation:(NSInvocation *)invocation 方法吧訊息指派給目標物件
      • 這個方法可以簡單的只修改接收者讓另一個物件去接受處理這個方法,但是這樣做法和使用備選的訊息接收者做法是等效的,所以一般來說更多是先修改訊息內容,再觸發訊息
      • 這個方法的如果沒有實現,那麼就會呼叫超類的這個方法,類的繼承體系中的所有類都有機會處理直到 NSObject
      • NSObject- (void)forwardInvocation:(NSInvocation *)invocation 只是簡單的呼叫 - (void)doesNotRecognizeSelector: 方法丟擲異常,所以一般向物件傳送他沒有實現的方法都會通過這個方法丟擲異常
  • Tips 13 Method Swizzling(原書叫方法調配技術,看到這個名詞,第一句話是什麼鬼)

    • 很多時候,這個技術被大家稱為黑魔法,但是其實他做的只是在執行時交換方法的實現而已
    • 所有的方法都是在物件中是一個 IMP 指標,指標原型 id (*IMP)(id, SEL, ...)
    • 每個類通過一張對映表來對映可相應的 selector 和對應 IMP 指標的關係
    • 可以做 AOP
    • 對方法的操作
    • 使用 void method_exchangeImplementations(Method m1, Method m2) 來交換兩個方法
    • 使用 Method class_getInstanceMethod(Class aClass, SEL aSelector) 來獲取類的例項方法
    • 常規的 Method Swizzling 做法 ``` // 在 Category 的定義檔案中增加我們將要用來替換的方法 @interface NSString (MethodSwizzling)

      • (NSString *)ms_myLowercaseString; @end

      // 在 Category 的實現檔案中,進行交換 @implementation NSString (MethodSwizzling)

      • (NSString *)ms_myLowercaseString { // 在呼叫原方法前做點其他的事情 // 注意這裡並不是遞迴呼叫,而是因為我們交換了 lowercaseString 和 ms_myLowercaseString 的實現,所以這裡呼叫 ms_myLowercaseString 實際上是在呼叫 lowercaseString 方法 NSString *s = [self ms_myLowercaseString];

        // 在呼叫完原方法後做點其他的事情 return s; }

      • (void)load { Method m1 = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method m2 = class_getInstanceMethod([NSString class], @selector(ms_myLowercaseString)); method_exchangeImplementations(m1, m2); } @end ```

  • Tips 14 理解類物件

    • 每個物件結構體的首個成員變數是 Class 類的變數
    • Class 物件是一個 objc_class 的結構體,裡面儲存了類的後設資料
    • Class 類同樣有元類(metaclass)
    • 某個類如果有超類(super class)那麼他的 Class 物件的元類,也繼承於該類超類的元類
    • -isMemberOfClass: 可以判斷某個物件是否是某個類的例項
    • -isKindOfClass: 可以判斷某個物件是否是某個類或其子類的例項
    • 使用上面說到的兩個方法類判斷型別,不要直接比較類物件,因為某些物件可能實現了訊息裝發功能

相關文章