【原】Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法(Matt Galloway著)讀書筆記(二)

第14條:理解 “類對象” 的用意

對象類型並不是在編譯期就綁定好了,而是要在運行期查找。在運行期檢視對象類型的操作,叫做 “類型信息查詢(內省)”

元類

在運行期程序庫的頭文件中,id 類型的定義:

typedef struct objc_object {
    Class isa;
} *id;

每個對象結構體是首個成員是 Class 類的變量 isa,該變量定義了對象所屬的類。

在運行期程序庫的頭文件中,Class 類型的定義:

typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};

定義:isa 指針所指向的類。
作用:用來表述對象本身所具備的元數據。“類方法” 就定義在這裏。

在類繼承體系中查詢類型信息

isMemberOfClass: 判斷出對象是否爲某個特定類的實例。
isKindOfClass: 判斷出對象是否爲某類或者其派生類的實例。

說明代碼:

NSMutableDictionary *dict = [NSMutableDictionary dictionary]; // 底層對象爲:__NSDictionaryM
    NSLog(@"%d", [dict isMemberOfClass:[NSDictionary class]]); // NO
    NSLog(@"%d", [dict isMemberOfClass:[NSMutableDictionary class]]); // NO
    NSLog(@"%d", [dict isMemberOfClass:NSClassFromString(@"__NSDictionaryM")]); // YES
    NSLog(@"%d", [dict isKindOfClass:[NSDictionary class]]); // YES
    NSLog(@"%d", [dict isKindOfClass:[NSArray class]]); // NO

像這樣的類型信息查詢方法使用 isa 指針獲取對象所屬的類,然後通過 super_class 指針在繼承體系中游走。
系統對象創建後,其元類並未創建時使用的類,系統將其轉爲一些底層類,如上面的 __NSDictionaryM
自定義對象(繼承NSObject),還是創建時的對象。

類對象是單例,在用於程序範圍內,每個類的 Class 僅有一個實例。
自定義對象,通過 == 操作符也可判斷出對象是否爲某類的實例。

id object = [EOCSomeClass new];
if ([object class] == [EOCSomeClass class]) {
    ...
}

代理: 對象可能會把其受到的所有選擇子轉發給另一個對象,這個對象就是代理。這種對象均以 NSProxy 爲根類。
代理對象上調用 class 方法返回的是代理對象本身,而非接受代理的對象所屬的類。

總結: 儘量使用類型查詢方法來確定對象類型,而不是直接比較對象,因爲某些對象可能實現了消息轉發功能。

第三章:接口與 API 設計

第15條:用前綴避免命名空間衝突

問題:Objective-C 沒有其他語言那種內置的命名空間機制,容易產生命名衝突。

蘋果宣稱其保留使用所有 “兩字母前綴” 的權利,所以我們在開發中最好使用三個字母的。

若自己所開關的程序庫中用到了第三方庫,則應爲其中的名稱加上前綴。

第16條:提供 “全能初始化方法”

這種可爲對象提供必要信息以便其能完成工作的初始化方法叫做 “全能初始化方法”。

在類中提供一個全能初始化方法。其他初始化方法均應調用此方法。
若全能初始化方法與超類不同,則需要覆寫超類中的對應方法。

第17條:實現 description 方法

NSLog(@"object = %@", object)

在構建需要打印到日誌的字符串時,object 對象會收到 description 消息,該方法所返回的描述信息將取代 ”格式字符串“ 裏的 ”%@“

自定義對象在打印時,只能打印出對象的類名及地址,這些內容一般沒有什麼用。
覆寫 description 方法,可以打印自己所需要的內容。

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, %@>",
    [self class],
    self,
    @{@"參數1" : 參數1,
      @"參數2" : 參數2}
    ];
}

debugDescription 用於 debug 模式下 po 的打印。

第18條:儘量使用不可變(對外只讀)對象

不可變:只讀(read-only)
可變:即可讀又可寫(read-write)

儘量把對外公佈出來的屬性設置爲只讀,而且只在必要的時候纔將屬性對外公佈。

第19條:使用清晰而協調的命名方式

起名時應遵從標準的 Objective-C 命名規範,這樣創建出來的接口更容易爲開發者所理解。

第20條:爲私有方法名加前綴

好處:

  • 便於區分 公共方法私有方法
  • 便於修改 方法名方法簽名

建議:
不要單用一個 _ 做私有方法的前綴,蘋果公司就是這麼用的,可能會覆寫系統私有方法。建議使用 p_ 作爲私有方法的前綴

第21條:理解 Objective-C 錯誤模型

第22條:理解 NSCopying 協議

  • copy:返回的拷貝對象與當前一致
  • immutableCopy:返回不可變的拷貝對象
  • mutableCopy:返回可變的拷貝對象

第23條:通過委託與數據源協議進行對象間通信

緩存方法響應能力緩存的最佳途徑是使用 ”位段“ 數據類型。

位段:

struct data {
    unsigned int fieldA : 8; // 位段,佔8個二進制位
    unsigned int fieldB : 4; // 位段,佔4個二進制位
    unsigned int fieldC : 2; // 位段,佔2個二進制位
    unsigned int fieldD : 1; // 位段,佔1個二進制位
};

代理緩存:

// 用於緩存委託對象是否能響應特地的選擇子
struct {
    unsigned int delegateMethdo1 : 1;
    unsigned int delegateMethdo2 : 1;
    unsigned int delegateMethdo3 : 1;
} _delegateFlags;

...

// 實現緩存功能所有的代碼可以寫在 delegate 屬性所對應的設置方法裏面
- (void)setDelegate:(id<delegate類名>)delegate {
    _delegate = delegate;
    _delegateFlags.delegateMethdo1 = [delegate respondsToSelector:@selector(delegateMethdo1:)];
    _delegateFlags.delegateMethdo2 = [delegate respondsToSelector:@selector(delegateMethdo2:)];
    _delegateFlags.delegateMethdo3 = [delegate respondsToSelector:@selector(delegateMethdo3:)];
}

在以後調用代理方法的時候,直接使用結構體裏面的標誌進行判斷即可。

第24條:將類的實現代碼分散到便於管理的數個分類之中

第25條:總是爲第三方類的分類名稱加前綴

在運行期系統加載分類時,會將分類中所實現的方法都加入到類的方法列表中,就好比這個類的固有方法。如果類中本來就有此方法,而分類中有實現了一次,那麼分類中的方法會覆蓋原來那一份實現代碼。實際上可能會發生很多次覆蓋,多次覆蓋的結果以最後一個分類爲準。

爲防止覆蓋,將分類的名稱和其中的方法名稱加上前綴

第26條:勿在分類中聲明屬性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章