動態(tài)生成關聯對象屬性的存取方法

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函數,并且能正確判斷是何種消息,然后綁定,就醬。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容