Objective-C的Category可以靈活的為已經存在的類增加方法,但是不能增加“存儲屬性”,如果想要擴展類的存儲空間,可以使用關聯對象來實現。比如擴展一個存儲 NSNumber * 類型的屬性,代碼如下:
static char kPropertyConstraintsKey;
- (void)setExternProperty:(NSNumber *)value {
objc_setAssociatedObject(self, &kPropertyConstraintsKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)externProperty {
return objc_getAssociatedObject(self, &kPropertyConstraintsKey);
}
幾乎所有的關聯對象的寫法都是這樣,增加一個屬性還好,如果多了,會有很多重復代碼。這種代碼已經模式化毫無技術含量,而且寫起來容易出錯,很可能把key放在了錯誤的屬性身上。能不能不寫這樣的重復代碼,而實現功能呢,下面提出一種思路。
Objective-C方法的最終實現體是IMP函數,它是特殊的c函數,前兩個參數必須為(id self, SEL _cmd),第一個是當前方法的調用者,第二個是與之綁定的SEL。c函數的入口地址必須在編譯時確定,所以無法動態(tài)增加。強大的運行時機制,可以為類新加方法,其實就是綁定新的SEL到一個確定的IMP函數,動態(tài)增加方法等同于動態(tài)綁定函數。上述設置屬性和獲取屬性的IMP函數分別為:
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方法,都可以使用上述兩個函數來實現的。為了完成動態(tài)生成關聯對象屬性的存取方法的目的,還有兩個重要的知識點要掌握,一個是指定屬性為@dynamic時,編譯器會無視屬性的Setter、Getter方法,直到運行時發(fā)現不能處理再拋出異常,這樣就可以不用寫一堆方法實現而順利通過編譯。另一個是Objective-C方法查找機制,由于并沒有真正的方法實現,通過SEL查找方法實現會失敗,但是這時還有兩次機會來處理消息。第一次是動態(tài)方法決議Dynamic Method Resolution,第二次是消息轉發(fā)Message Forwarding。我們選擇在動態(tài)方法決議里來處理消息,使用runtime的class_addMethod方法將該消息綁定到對應的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
當然上述的實現是簡單粗略的,判斷具體是什么消息時只用了簡單的字符串比較,而且只能正確處理對象類型的strong屬性。不同的屬性類型,甚至是不同的內存管理語義,對應的IMP都是不同的。寫好不同的IMP函數,并且能正確判斷是何種消息,然后綁定,就醬。