Objective-C的Category可以靈活的為已經(jīng)存在的類增加方法德迹,但是不能增加“存儲屬性”署尤,如果想要擴展類的存儲空間胃碾,可以使用關(guān)聯(lián)對象來實現(xiàn)易稠。比如擴展一個存儲 NSNumber * 類型的屬性拿霉,代碼如下:
static char kPropertyConstraintsKey;
- (void)setExternProperty:(NSNumber *)value {
objc_setAssociatedObject(self, &kPropertyConstraintsKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)externProperty {
return objc_getAssociatedObject(self, &kPropertyConstraintsKey);
}
幾乎所有的關(guān)聯(lián)對象的寫法都是這樣吟秩,增加一個屬性還好,如果多了绽淘,會有很多重復(fù)代碼涵防。這種代碼已經(jīng)模式化毫無技術(shù)含量,而且寫起來容易出錯沪铭,很可能把key放在了錯誤的屬性身上壮池。能不能不寫這樣的重復(fù)代碼,而實現(xiàn)功能呢杀怠,下面提出一種思路椰憋。
Objective-C方法的最終實現(xiàn)體是IMP函數(shù),它是特殊的c函數(shù)赔退,前兩個參數(shù)必須為(id self, SEL _cmd)橙依,第一個是當前方法的調(diào)用者,第二個是與之綁定的SEL硕旗。c函數(shù)的入口地址必須在編譯時確定窗骑,所以無法動態(tài)增加。強大的運行時機制漆枚,可以為類新加方法创译,其實就是綁定新的SEL到一個確定的IMP函數(shù),動態(tài)增加方法等同于動態(tài)綁定函數(shù)墙基。上述設(shè)置屬性和獲取屬性的IMP函數(shù)分別為:
void objSetterIMP(id self, SEL _cmd, id obj) {
objc_setAssociatedObject(self, _cmd, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
id objGetterIMP(id self, SEL _cmd) {
return objc_getAssociatedObject(self, _cmd);
}
所有Category對象屬性的Setter软族、Getter方法刷喜,都可以使用上述兩個函數(shù)來實現(xiàn)的。為了完成動態(tài)生成關(guān)聯(lián)對象屬性的存取方法的目的互订,還有兩個重要的知識點要掌握吱肌,一個是指定屬性為@dynamic時,編譯器會無視屬性的Setter仰禽、Getter方法氮墨,直到運行時發(fā)現(xiàn)不能處理再拋出異常,這樣就可以不用寫一堆方法實現(xiàn)而順利通過編譯吐葵。另一個是Objective-C方法查找機制规揪,由于并沒有真正的方法實現(xiàn),通過SEL查找方法實現(xiàn)會失敗温峭,但是這時還有兩次機會來處理消息猛铅。第一次是動態(tài)方法決議Dynamic Method Resolution,第二次是消息轉(zhuǎn)發(fā)Message Forwarding凤藏。我們選擇在動態(tài)方法決議里來處理消息奸忽,使用runtime的class_addMethod方法將該消息綁定到對應(yīng)的IMP上,重新進行消息傳遞揖庄。
@interface SomeClass(someCategory)
@property(noatomic, strong) NSNumber *object1;
@property(noatomic, strong) NSNumber *object2;
@property(noatomic, strong) NSNumber *object3;
@property(noatomic, strong) NSNumber *object4;
@end
@implementation SomeClass (someCategory)
@dynamic object1;
@dynamic object2;
@dynamic object3;
@dynamic object4;
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *msg = NSStringFromSelector(sel);
if ([msg hasPrefix:@"setObject"]) {
class_addMethod(self, sel, (IMP)objSetterIMP, "v@:@");
return YES;
}
else if([msg hasPrefix:@"object"]) {
class_addMethod(self, sel, (IMP)objGetterIMP, "@@:");
return YES;
}
else {
return [super resolveInstanceMethod:sel];
}
}
@end
當然上述的實現(xiàn)是簡單粗略的栗菜,判斷具體是什么消息時只用了簡單的字符串比較,而且只能正確處理對象類型的strong屬性蹄梢。不同的屬性類型疙筹,甚至是不同的內(nèi)存管理語義,對應(yīng)的IMP都是不同的禁炒。寫好不同的IMP函數(shù)而咆,并且能正確判斷是何種消息,然后綁定幕袱,就醬暴备。