小碼哥底層原理筆記:Catgory關(guān)聯(lián)對象的原理

我們知道Catgory可以定義屬性,但是不能定義成員變量。比如我們這樣寫:

@interface Person (Test)
{
   int _weight;
}

@property (nonatomic, assign) int weight;//這句代碼只會(huì)做一件事
@end

編譯一下,是會(huì)報(bào)錯(cuò)的,提示分類不能添加成員變量。我們只能像下面這樣定義屬性,
我們先創(chuàng)建一個(gè)Person的分類Test:

@interface Person (Test)

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int weight;//這句代碼只會(huì)做一件事
//就是只會(huì)生成這兩句聲明
/*
 - (void)setWeight:(int)weight;
 - (int)weight;
 */

@end

#import "Person+Test.h"
#import <objc/runtime.h>

@implementation Person (Test)

@end

如上代碼所示,我們給這個(gè)Person分類Test定義了兩個(gè)屬性,一個(gè)是@property (nonatomic, copy) NSString *name;,一個(gè)是@property (nonatomic, assign) int weight;
現(xiàn)在我們執(zhí)行以下代碼:

Person *person = [[Person alloc] init];
person.weight = 20;

發(fā)現(xiàn)閃退了,報(bào)錯(cuò)是提示沒有找到setWeight:方法。這是因?yàn)橥ㄟ^@property (nonatomic, copy) NSString *name;定義的屬性只是聲明了一個(gè)get和set方法,但是并沒有實(shí)現(xiàn)get,set方法。那么我們要像Person類那樣正常去使用name和weight屬性,我們就必須在.m文件里面實(shí)現(xiàn)對應(yīng)的set和get方法。在set方法中保存weight的值20,然后在get方法中取出20。

那么我們之間跟之前那樣寫行不行呢,像這樣,

@implementation Person (Test)

- (void) setWeight:(int) weight{
   _weight = weight;
}

- (int)weight{
 return _weight;
}
@end

我們運(yùn)行發(fā)現(xiàn)這樣行不通,打印weight的值是0,并不是我們賦值的20。那是因?yàn)樵谡erson類里面是把值保存在成員變量里面的?,F(xiàn)在分類是沒有成員變量的,所以無法保存值20。

對此我們的主要問題就是在沒有成員變量的情況下如何把賦給weight的值20保存起來,我們有以下幾種方案,在.m里面定義一個(gè)全局變量保存賦給weight的值,或者在.m里面定義一個(gè)全局字典,通過key-value來保存對應(yīng)的屬性的賦值。還有就是通過runtime的API方法來實(shí)現(xiàn),如下:

#import "Person+Test.h"
#import <objc/runtime.h>

@implementation Person (Test)

- (void)setWeight:(int)weight{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight{
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

- (void)setName:(NSString *)name{//_cmd =@selector(setName:)
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{//_cmd代表當(dāng)前方法的selector,_cmd =@selector(name);,但是set方法不能這樣寫
    return objc_getAssociatedObject(self, _cmd);
}

//static const char MJNameKey;//定義一個(gè)只占一個(gè)字節(jié)的字符串。比下面這個(gè)方法要更好
//static const char MJWeightKey;
//
//- (void)setWeight:(int)weight{
//    objc_setAssociatedObject(self, &MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//}
//
//- (int)weight{
//    return [objc_getAssociatedObject(self, &MJWeightKey) intValue];
//}
//
//- (void)setName:(NSString *)name{
//    objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
//}
//
//- (NSString *)name{
//    return objc_getAssociatedObject(self, &MJNameKey);
//}

//static const void *MJNameKey = &MJNameKey;
//static const void *MJWeightKey = &MJWeightKey;
//
//- (void)setWeight:(int)weight{
//    objc_setAssociatedObject(self, MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//}
//
//- (int)weight{
//    return [objc_getAssociatedObject(self, MJWeightKey) intValue];
//}
//
//- (void)setName:(NSString *)name{
//    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
//}
//
//- (NSString *)name{
//    return objc_getAssociatedObject(self, MJNameKey);
//}

@end

這幾種方法都可以,其中第一種是最好的。還是回到上一個(gè)問題,這樣用runtime實(shí)現(xiàn)的話,那這個(gè)成員變量是保存在哪里呢?是合并到Person類里面嗎?答案不是的。runtime為這個(gè)分類的關(guān)聯(lián)對象單獨(dú)進(jìn)行管理,并不是合并到Person類里面了。

其實(shí)runtime是生成一個(gè)AssociationsManager類用來管理分類的關(guān)聯(lián)對象,讓分類能正常使用成員變量,有點(diǎn)類似于前面說到的用字典去保存的原理。通過查看源碼,我們可以知道AssociationsManager類有一個(gè)AssociationsHashMap屬性,這個(gè)屬性是相當(dāng)于一個(gè)字典,用來存儲(chǔ)對象-關(guān)聯(lián)對象的,也就是它的key是我們關(guān)聯(lián)對象時(shí)傳的self,也就是這個(gè)Person分類,以這個(gè)為key,然后value是一個(gè)ObjectAssociationMap,ObjectAssociationMap對象也相當(dāng)于是一個(gè)字典,這個(gè)字典的key是我們關(guān)聯(lián)對象時(shí)傳進(jìn)去的那個(gè)key,value是ObjcAssociationObjcAssociation對象里面有兩個(gè)屬性_value_policy,這兩個(gè)就是我們關(guān)聯(lián)對象的值和關(guān)聯(lián)策略了。

簡單說AssociationsHashMap存儲(chǔ)的是項(xiàng)目中所有分類的關(guān)聯(lián)對象,里面應(yīng)該是長這樣{Person分類: AssociationsHashMap,Student分類: AssociationsHashMap},我們項(xiàng)目中有幾個(gè)分類有關(guān)聯(lián)對象,那AssociationsHashMap里面就有多少個(gè)元素。而AssociationsHashMap里面存放的就是關(guān)聯(lián)對象的keyObjcAssociation,里面應(yīng)該長這樣{@selector(weight):@(weight),@selector(name):name},一個(gè)分類里面有多少個(gè)關(guān)聯(lián)對象AssociationsHashMap里面就有多少個(gè)元素。最后ObjcAssociation就是保存著關(guān)聯(lián)對象的值和關(guān)聯(lián)策略了。ObjcAssociation{unitptr_t _policy= OBJC_ASSOCIATION_RETAIN_NONATOMIC; id _value=@(weight)}。以下圖很好的展示了其中的原理。

分類關(guān)聯(lián)對象的原理

注意:關(guān)聯(lián)策略是沒有weak類型的。

對應(yīng)的關(guān)聯(lián)策略

面試題

1、Category能否添加成員變量?如果可以,如何給Category添加成員變量?
答:不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容