通過(guò)Runtime源碼了解關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)

原文鏈接

在iOS開(kāi)發(fā)中,Category是經(jīng)常使用到的一個(gè)特性,合理的使用Category能夠減少繁瑣代碼,提高開(kāi)發(fā)效率。在使用Category時(shí),有經(jīng)驗(yàn)的開(kāi)發(fā)者應(yīng)該都知道,在Category中是無(wú)法添加屬性的,如果想在Category中實(shí)現(xiàn)屬性的效果,需要使用關(guān)聯(lián)對(duì)象。關(guān)聯(lián)對(duì)象屬于Runtime的范疇,本篇文章結(jié)合Runtime源碼,分析下關(guān)聯(lián)對(duì)象的內(nèi)部實(shí)現(xiàn)。

Category中使用@property

上面提到了在Category中無(wú)法添加屬性,來(lái)驗(yàn)證一下。倘若在Category中添加屬性,是會(huì)直接編譯錯(cuò)誤?還是會(huì)警告?

定義一個(gè)Person類,代碼如下:

@interface Person : NSObject{
    NSString *_age;
}

- (void)printName;

@end

實(shí)現(xiàn)文件

@implementation Person

- (void)printName
{
    NSLog(@"my name is Person");
}

@end

為Person 添加一個(gè)Category MyPerson,Category中定義一個(gè)屬性 personName,代碼如下:

@interface Person (MyPerson)

@property (nonatomic, copy) NSString *personName;

@end

實(shí)現(xiàn)文件中暫時(shí)為空。

現(xiàn)在我們?cè)贑ategory中添加了@property,編譯一下,沒(méi)有問(wèn)題,可以編譯成功。也就是說(shuō),Category中使用@property不會(huì)引起編譯錯(cuò)誤。但是呢,Xcode會(huì)提示警告,警告信息如下:

Property 'personName' requires method 'personName' to be defined - use @dynamic or provide a method implementation in this category

Property 'personName' requires method 'setPersonName:' to be defined - use @dynamic or provide a method implementation in this category

大意就是需要為屬性personName實(shí)現(xiàn)get方法和set方法。

在繼續(xù)下一步之前,首先需要了解Objective-C中的@property到底是什么:

@property = 實(shí)例變量 + get方法 + set方法

關(guān)于@property的更詳細(xì)介紹,可以參考這篇文章。

也就是說(shuō),在普通文件中,定義一個(gè)屬性,編譯器會(huì)自動(dòng)生成實(shí)例變量,以及該實(shí)例變量對(duì)應(yīng)的get/set方法。但是在Category中,根據(jù)Xcode的警告信息,是沒(méi)有生成get/set方法的。

既然Xcode沒(méi)有自動(dòng)生成get/set方法,那么我們來(lái)手動(dòng)實(shí)現(xiàn)一下get/set方法。

在Category的實(shí)現(xiàn)文件中加入以下代碼:

- (NSString *)personName
{
    return _personName;
}

- (void)setPersonName:(NSString *)personName
{
    _personName = personName;
}

警告信息確實(shí)沒(méi)了,直接提示error,編譯不通過(guò),錯(cuò)誤信息如下:

Use of undeclared identifier '_personName'

_personName沒(méi)有定義。看來(lái)在Category中使用@property,編譯器不僅不會(huì)自動(dòng)生成set/get方法,連實(shí)例變量也不會(huì)生成。話說(shuō)回來(lái),沒(méi)有實(shí)例變量,自然也不會(huì)有set/get方法。

正是因?yàn)镃ategory中的@property不會(huì)生成實(shí)例變量,get/set方法,所以如果在程序中使用Category的屬性,編譯不會(huì)有問(wèn)題,但是在運(yùn)行期間會(huì)直接崩潰。

Person *p = [[Person alloc] init];
[p printName];
    
p.personName = @"haha"; // 這里會(huì)直接崩潰

崩潰信息如下:

-[Person setPersonName:]: unrecognized selector sent to instance 0x60000300ab80

崩潰原因也是容易理解的,因?yàn)楦緵](méi)有setPersonName方法。

@property和關(guān)聯(lián)對(duì)象結(jié)合使用

既然在Category中無(wú)法直接使用@property,那有沒(méi)有什么辦法解決呢?答案就是關(guān)聯(lián)對(duì)象。

關(guān)聯(lián)對(duì)象其實(shí)是AssociatedObject的翻譯。需要注意的是,關(guān)聯(lián)對(duì)象并不是代替了Category中的屬性,而是在Category中@property和關(guān)聯(lián)對(duì)象結(jié)合使用,以達(dá)到正常使用@property的目的

文章開(kāi)頭也提到了,關(guān)聯(lián)對(duì)象屬于Runtime的范疇,因此使用關(guān)聯(lián)對(duì)象之前,首先導(dǎo)入runtime頭文件

#import <objc/runtime.h>

然后在實(shí)現(xiàn)屬性的get/set方法,get/set方法中使用關(guān)聯(lián)對(duì)象,代碼如下:

- (NSString *)personName
{
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setPersonName:(NSString *)personName
{
    objc_setAssociatedObject(self, @selector(personName), personName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

現(xiàn)在在程序中使用Category中的屬性,可以正常使用:

Person *p = [[Person alloc] init];
[p printName];
    
p.personName = @"haha";
NSLog(@"p.personName = %@",p.personName);

輸出:

my name is Person
p.personName = haha

這就是關(guān)聯(lián)對(duì)象的作用。Category中關(guān)聯(lián)對(duì)象和@property結(jié)合使用,能夠達(dá)到在主程序中正常使用Category中屬性的目的。

關(guān)聯(lián)對(duì)象在Runtime中的實(shí)現(xiàn)

來(lái)看一下關(guān)聯(lián)對(duì)象在Runtime中到底是怎么實(shí)現(xiàn)的。我們主要通過(guò)追蹤Runtime開(kāi)放給我們的接口來(lái)探索。上面已經(jīng)用到了兩個(gè)接口,分別是:

objc_getAssociatedObject
objc_setAssociatedObject

除了這兩個(gè)接口外,還有一個(gè)接口:

objc_removeAssociatedObjects

也就是說(shuō),Runtime主要提供了三個(gè)方法供我們使用關(guān)聯(lián)對(duì)象:

// 根據(jù)key獲取關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(id object, const void *key);
// 以key、value的形式設(shè)置關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
// 移出對(duì)象所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object);

接下來(lái)依次分析每個(gè)方法。

objc_setAssociatedObject

objc_setAssociatedObject方法位于objc-runtime.mm文件中,該方法的實(shí)現(xiàn)比較簡(jiǎn)單,調(diào)用了_object_set_associative_reference函數(shù)。

// 設(shè)置關(guān)聯(lián)對(duì)象的方法
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

_object_set_associative_reference函數(shù)完成了設(shè)置關(guān)聯(lián)對(duì)象的操作。在看_object_set_associative_reference函數(shù)源碼之前,先了解幾個(gè)結(jié)構(gòu)體代表的含義。

ObjcAssociation

ObjcAssociation就是關(guān)聯(lián)對(duì)象,在應(yīng)用層設(shè)置、獲取關(guān)聯(lián)對(duì)象,在Runtime中都被表示成了ObjcAssociation。看一下ObjcAssociation的定義:

// ObjcAssociation就是關(guān)聯(lián)對(duì)象類
class ObjcAssociation {
    uintptr_t _policy;
    // 值
    id _value;
public:
    // 構(gòu)造函數(shù)
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    // 默認(rèn)構(gòu)造函數(shù),參數(shù)分別為0和nil
    ObjcAssociation() : _policy(0), _value(nil) {}
};

關(guān)聯(lián)對(duì)象中定義了_value和_policy兩個(gè)變量。_policy之后再說(shuō),_value就是關(guān)聯(lián)對(duì)象的值,比如上面賦值為@"haha"。

AssociationsManager

AssociationsManager可以理解成一個(gè)Manager類,看一下AssociationsManager的實(shí)現(xiàn)

class AssociationsManager {
    // AssociationsManager中只有一個(gè)變量AssociationsHashMap
    static AssociationsHashMap *_map;
public:
    // 構(gòu)造函數(shù)中加鎖
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    // 析構(gòu)函數(shù)中釋放鎖
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    // 構(gòu)造函數(shù)、析構(gòu)函數(shù)中加鎖、釋放鎖的操作,保證了AssociationsManager是線程安全的
    
    AssociationsHashMap &associations() {
        // AssociationsHashMap 的實(shí)現(xiàn)可以理解成單例對(duì)象
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager中只有一個(gè)變量,AssociationsHashMap,通過(guò)源碼可以看到,AssociationsManager中的AssociationsHashMap的實(shí)現(xiàn)可以理解成是單例的。而且AssociationsManager的構(gòu)造函數(shù)和析構(gòu)函數(shù)分別做了加鎖、釋放鎖的操作。也就是說(shuō),同一時(shí)刻,只能有一個(gè)線程操作AssociationsManager中的AssociationsHashMap。

AssociationsHashMap

AssociationsHashMap,看名字可以猜到是hashMap類型,那么里面的key、value到底是什么呢?看下AssociationsHashMap的定義:

// AssociationsHashMap是字典,key是對(duì)象的disguised_ptr_t值,value是ObjectAssociationMap
    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

key是對(duì)象的DISGUISE()值,value是ObjectAssociationMap。DISGUISE()可以是一個(gè)函數(shù),每個(gè)對(duì)象的DISGUISE()值不同,作為了AssociationsHashMap的key。

ObjectAssociationMap

ObjectAssociationMap是map類型,里面也是以key、value的形式存儲(chǔ)??匆幌翺bjectAssociationMap的定義

// ObjectAssociationMap是字典,key是從外面?zhèn)鬟^(guò)來(lái)的key,例如@selector(hello),value是關(guān)聯(lián)對(duì)象,也就是
    // ObjectAssociation
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

key是從外面?zhèn)鬟^(guò)來(lái)的,比如我們上面用到的@selector(personName),value是上面提到的ObjcAssociation對(duì)象,也就是關(guān)聯(lián)對(duì)象。終于看到了關(guān)聯(lián)對(duì)象,通過(guò)下面一整圖看一下整個(gè)是如何存儲(chǔ)的

[圖片上傳失敗...(image-bb1f1f-1554867497085)]

_object_set_associative_reference源碼

_object_set_associative_reference函數(shù)中根據(jù)所傳的參數(shù)value是否為nil,分成了不同的邏輯。value為nil的邏輯比較簡(jiǎn)單,我們首先看一下value為nil所做的處理。

value = nil

value為nil時(shí)的代碼:

// 初始化一個(gè)manager
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 獲取對(duì)象的DISGUISE值,作為AssociationsHashMap的key
disguised_ptr_t disguised_object = DISGUISE(object);

// value無(wú)值,也就是釋放一個(gè)key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i !=  associations.end()) {
    ObjectAssociationMap *refs = i->second;
    ObjectAssociationMap::iterator j = refs->find(key);
    if (j != refs->end()) {
        old_association = j->second;
        // 調(diào)用erase()方法刪除對(duì)應(yīng)的關(guān)聯(lián)對(duì)象
        refs->erase(j);
    }
}

// 釋放舊的關(guān)聯(lián)對(duì)象
if (old_association.hasValue()) ReleaseValue()(old_association);

通過(guò)代碼可以看到,當(dāng)value'為nil時(shí),Runtime做的操作就是找到原來(lái)該key所對(duì)應(yīng)的關(guān)聯(lián)對(duì)象,并且將該關(guān)聯(lián)對(duì)象刪除。也就是說(shuō),value為nil,實(shí)際上就是釋放一個(gè)key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象。

value != nil

value不為nil,實(shí)際上就是為某個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象。為某個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象,又分為該對(duì)象之前已經(jīng)添加過(guò)關(guān)聯(lián)對(duì)象和該對(duì)象是第一次添加關(guān)聯(lián)對(duì)象的邏輯。

  1. 該對(duì)象第一次添加關(guān)聯(lián)對(duì)象
    看一下該對(duì)象第一次添加關(guān)聯(lián)對(duì)象的代碼:
// 初始化一個(gè)manager
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 獲取對(duì)象的DISGUISE值,作為AssociationsHashMap的key
disguised_ptr_t disguised_object = DISGUISE(object);

// AssociationsHashMap::iterator 類型的迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);

// 執(zhí)行到這里,說(shuō)明該對(duì)象是第一次添加關(guān)聯(lián)對(duì)象
 // 初始化ObjectAssociationMap
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
// 賦值
(*refs)[key] = ObjcAssociation(policy, new_value);
// 設(shè)置該對(duì)象的有關(guān)聯(lián)對(duì)象,調(diào)用的是setHasAssociatedObjects()方法
object->setHasAssociatedObjects();

通過(guò)代碼可以看到,若該對(duì)象是第一次添加關(guān)聯(lián)對(duì)象,則先生成新的ObjectAssociationMap,并根據(jù)policy、value初始化ObjcAssociation對(duì)象,以外部傳的key、生成的ObjcAssociation分別作為ObjectAssociationMap的key、value。以DISGUISE(object)、生成的ObjectAssociationMap分別作為AssociationsHashMap的key、value。

  1. 該對(duì)象不是第一次添加關(guān)聯(lián)對(duì)象
    若該對(duì)象不是第一次添加關(guān)聯(lián)對(duì)象,根據(jù)原來(lái)是否有該key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象進(jìn)行邏輯區(qū)分。
    1. 原來(lái)有該key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象
      代碼如下:
    // 初始化一個(gè)manager
    AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
    // 獲取對(duì)象的DISGUISE值,作為AssociationsHashMap的key
    disguised_ptr_t disguised_object = DISGUISE(object);
    
    // AssociationsHashMap::iterator 類型的迭代器
    AssociationsHashMap::iterator i = associations.find(disguised_object);
    
    // 獲取到ObjectAssociationMap(key是外部傳來(lái)的key,value是關(guān)聯(lián)對(duì)象類ObjcAssociation)
    ObjectAssociationMap *refs = i->second;
    // ObjectAssociationMap::iterator 類型的迭代器
    ObjectAssociationMap::iterator j = refs->find(key);
    
    // 原來(lái)該key對(duì)應(yīng)的有關(guān)聯(lián)對(duì)象
    // 將原關(guān)聯(lián)對(duì)象的值存起來(lái),并且賦新值
    old_association = j->second;
    j->second = ObjcAssociation(policy, new_value);
    
    // 釋放舊的關(guān)聯(lián)對(duì)象
    if (old_association.hasValue()) ReleaseValue()(old_association);
    
    原來(lái)有該key所對(duì)應(yīng)的關(guān)聯(lián)對(duì)象,所做的處理就是將原來(lái)的值存下來(lái),并且賦新的值。最后將原來(lái)的值釋放。
    1. 原來(lái)沒(méi)有該key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象
      代碼如下:
    // 初始化一個(gè)manager
    AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
    // 獲取對(duì)象的DISGUISE值,作為AssociationsHashMap的key
    disguised_ptr_t disguised_object = DISGUISE(object);
    
    // AssociationsHashMap::iterator 類型的迭代器
    AssociationsHashMap::iterator i = associations.find(disguised_object);
    
    // 獲取到ObjectAssociationMap(key是外部傳來(lái)的key,value是關(guān)聯(lián)對(duì)象類ObjcAssociation)
    ObjectAssociationMap *refs = i->second;
    // ObjectAssociationMap::iterator 類型的迭代器
    ObjectAssociationMap::iterator j = refs->find(key);
    
    // 無(wú)該key對(duì)應(yīng)的關(guān)聯(lián)對(duì)象,直接賦值即可
    // ObjcAssociation(policy, new_value)提供了這樣的構(gòu)造函數(shù)
    (*refs)[key] = ObjcAssociation(policy, new_value);
    
    原來(lái)沒(méi)有該key所對(duì)應(yīng)的關(guān)聯(lián)對(duì)象,直接賦值即可。
_object_set_associative_reference流程

看完了_object_set_associative_reference的源碼,介紹的比較復(fù)雜,其實(shí)流程相對(duì)來(lái)說(shuō)是比較簡(jiǎn)單的,整個(gè)流程可以用下面的流程圖來(lái)表示:

[圖片上傳失敗...(image-b69509-1554867497085)]

policy參數(shù)

上面已經(jīng)多次看到了policy參數(shù),policy參數(shù)到底代表什么呢?通過(guò)上面的介紹,應(yīng)該可以猜到了policy的作用。在定義一個(gè)屬性時(shí),需要使用各種各樣的修飾符,如nonatomic,copy,strong等,既然關(guān)聯(lián)對(duì)象是為了達(dá)到和屬性相同的效果,那么關(guān)聯(lián)對(duì)象是否也應(yīng)該有對(duì)應(yīng)的修飾符呢?

正是如此,構(gòu)造關(guān)聯(lián)對(duì)象的policy參數(shù),就是類似于屬性的修飾符。

我們?cè)趹?yīng)用層設(shè)置關(guān)聯(lián)對(duì)象時(shí),之前代碼用到的值是OBJC_ASSOCIATION_COPY_NONATOMIC,OBJC_ASSOCIATION_COPY_NONATOMIC是枚舉類型,其取值有以下幾種:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

根據(jù)其注釋,可以得出objc_AssociationPolicy與屬性修飾符之間的一個(gè)對(duì)應(yīng)關(guān)系,如下:

[圖片上傳失敗...(image-61610-1554867497085)]

這也是為何我們之前的代碼,設(shè)置關(guān)聯(lián)對(duì)象時(shí),使用OBJC_ASSOCIATION_COPY_NONATOMIC的原因。

關(guān)于各種屬性修飾符之間的區(qū)別,以及什么情景下使用哪種修飾符,可以參考這篇文章。

objc_getAssociatedObject

objc_getAssociatedObject方法位于objc-runtime.mm文件中,該方法的實(shí)現(xiàn)比較簡(jiǎn)單,內(nèi)部直接調(diào)用了_object_get_associative_reference函數(shù),代碼如下:

// 獲取關(guān)聯(lián)對(duì)象的方法
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
_object_get_associative_reference函數(shù)

獲取關(guān)聯(lián)對(duì)象的操作都在函數(shù)_object_get_associative_reference中。其主要流程是,獲取對(duì)象的DISGUISE()值,根據(jù)該值獲取到ObjectAssociationMap。根據(jù)外部所傳的key,在ObjectAssociationMap中找到key所對(duì)應(yīng)的ObjcAssociation對(duì)象,然后得到ObjcAssociation的value。代碼如下:

id value = nil;
AssociationsManager manager;
// 獲取到manager中的AssociationsHashMap
AssociationsHashMap &associations(manager.associations());
// 獲取對(duì)象的DISGUISE值
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);

// 獲取ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);

// 獲取到關(guān)聯(lián)對(duì)象ObjcAssociation
ObjcAssociation &entry = j->second;
// 獲取到value
value = entry.value();

// 返回關(guān)聯(lián)對(duì)像的值
return value;

objc_removeAssociatedObject

objc_removeAssociatedObject位于objc-runtime.mm文件中。注意,objc_removeAssociatedObject函數(shù)的作用是移除某個(gè)對(duì)象的所有關(guān)聯(lián)對(duì)象。倘若想要移除對(duì)象某個(gè)key所對(duì)應(yīng)的關(guān)聯(lián)對(duì)象,需要使用objc_setAssociatedObject函數(shù),value傳nil。

objc_removeAssociatedObject的實(shí)現(xiàn)比較簡(jiǎn)單,內(nèi)部調(diào)用了_object_remove_associations函數(shù),代碼如下:

// 移除對(duì)象object的所有關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
_object_remove_associations函數(shù)

_object_remove_associations函數(shù)的邏輯也比較簡(jiǎn)單,根據(jù)對(duì)象的DISGUISE()值找到ObjectAssociationMap,然后將該map中的所有值刪除。刪除時(shí)需要先將值存起來(lái),然后再刪除,_object_remove_associations函數(shù)中使用了vector來(lái)存儲(chǔ)值。之后再將找到的ObjectAssociationMap刪除,代碼如下:

// 聲明了一個(gè)vector
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;

AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 獲取對(duì)象的DISGUISE值
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);

ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
    elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);

for_each(elements.begin(), elements.end(), ReleaseValue());

總結(jié)

至此,關(guān)于關(guān)聯(lián)對(duì)象的使用、在Runtime源碼中的實(shí)現(xiàn)已經(jīng)全部介紹完畢。實(shí)際上,日常的工作中是很難涉及到關(guān)聯(lián)對(duì)象的內(nèi)部實(shí)現(xiàn)的。只要掌握Runtime提供給我們的三個(gè)接口,使用Category以及關(guān)聯(lián)對(duì)象就足以勝任工作項(xiàng)目。不過(guò),對(duì)于想要了解Runtime源碼的同學(xué)來(lái)說(shuō),掌握關(guān)聯(lián)對(duì)象在Runtime源碼中的實(shí)現(xiàn),是有很大幫助的。

參考文章

關(guān)聯(lián)對(duì)象 AssociatedObject 完全解析

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

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

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