第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條:總是爲第三方類的分類名稱加前綴
在運行期系統加載分類時,會將分類中所實現的方法都加入到類的方法列表中,就好比這個類的固有方法。如果類中本來就有此方法,而分類中有實現了一次,那麼分類中的方法會覆蓋原來那一份實現代碼。實際上可能會發生很多次覆蓋,多次覆蓋的結果以最後一個分類爲準。
爲防止覆蓋,將分類的名稱和其中的方法名稱加上前綴