Category、load肤无、initialize先蒋、Associated源碼解讀

本文主要通過OC的源碼剖析了與Category相關(guān)的原理。其中包括了Category的運(yùn)行時(shí)方法屬性管理宛渐、load方法原理竞漾、initialize方法原理眯搭、關(guān)聯(lián)對(duì)象的原理。

先幾個(gè)我們熟知的幾個(gè)知識(shí)點(diǎn)

  • 查找一個(gè)方法的時(shí)候先從Category列表里找业岁,依次找到主類的方法列表鳞仙。
  • Category列表的方法順序是有編譯順序決定的
  • 在程序運(yùn)行的時(shí)候,runtime會(huì)將Category的數(shù)據(jù)笔时,合并到類信息中(類對(duì)象棍好、元類對(duì)象中)
  • Category的load方法在runtime加載類、分類的時(shí)候調(diào)用
  • 不能直接給Category添加成員變量允耿,但是可以間接實(shí)現(xiàn)Category有成員變量的效果

以上這幾個(gè)知識(shí)點(diǎn)是我們面試中常見到的借笙,今天我們就通過源碼去了解背后的原因。

在開始剖析之前需要大概了解一下objc_class的內(nèi)存結(jié)構(gòu)较锡,附上一張圖和一篇之前寫的帖子Objective-C的內(nèi)存結(jié)構(gòu)业稼。

Category方法列表

編寫一段oc代碼

先定義一個(gè)Person類和一個(gè)Person+Test的分類,然后編譯成C++代碼蚂蕴,在程序啟動(dòng)之前低散,編譯器會(huì)先將各個(gè)屬性參數(shù)、方法列表等信息準(zhǔn)備好骡楼。

// 定義一個(gè)Person類
#import "Person.h"
@implementation Person
- (void)abc {}
- (void)run {
    NSLog(@"Person - run");
}
+ (void)run2 {}
@end
// 定義一個(gè)Person+test分類
#import "Person+Test.h"
@implementation Person (Test)
- (void)run {
    NSLog(@"Person (Test) - run");
}
- (void)test {
    NSLog(@"test");
}
+ (void)test2 {}
@end

編譯成C++代碼

執(zhí)行命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o Person.cpp 將person編譯成c++文件谦纱。

  • Person的方法結(jié)構(gòu)體
//實(shí)例方法abc 和 run 的結(jié)構(gòu)體,包含了2個(gè)元素的方法數(shù)組
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"abc", "v16@0:8", (void *)_I_Person_abc},
    {(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_run}}
};

// 類方法run2結(jié)構(gòu)體君编,包含了1個(gè)元素的方法數(shù)組
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"run2", "v16@0:8", (void *)_C_Person_run2}}
};

// _class_ro_t 類方法結(jié)構(gòu)體,包含了上邊的類方法列表數(shù)據(jù)
static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1, sizeof(struct _class_t), sizeof(struct _class_t), 
    0, 
    "Person",
    (const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Person,
    0, 
    0, 
    0, 
    0, 
};

// _class_ro_t 實(shí)例方法結(jié)構(gòu)體川慌,包含了上邊實(shí)例方法列表數(shù)據(jù)
static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, sizeof(struct Person_IMPL), sizeof(struct Person_IMPL), 
    0, 
    "Person",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
    0, 
    0, 
    0, 
    0, 
};

// 元類
extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_Person,
};

// 類
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_Person,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_Person,
};
static void OBJC_CLASS_SETUP_$_Person(void ) {
// 元類的地址
    OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
// 類的地址
    OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
    OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
}

  • Person+Test.cpp
- 分類的結(jié)構(gòu)體
struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

// 分類實(shí)例方法結(jié)構(gòu)體吃嘿,包含實(shí)例方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_Test_run},
    {(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_Test_test}}
};

// 分類類方法結(jié)構(gòu)體,包含類方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_Person_Test_test2}}
};

// category 結(jié)構(gòu)體
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
    0,
    0,
};

通過C++文件梦重,我們能大概看出編譯后的類和分類的代碼結(jié)構(gòu)兑燥,這是一個(gè)靜態(tài)編譯的結(jié)果,為了之后分析運(yùn)行時(shí)做的儲(chǔ)備琴拧。

  • 方法是一個(gè)結(jié)構(gòu)體指針降瞳,存放著方法列表
  • 實(shí)例方法和類方法存放在兩個(gè)不同的結(jié)構(gòu)體里
  • 存在兩個(gè)結(jié)構(gòu)體變量,一個(gè)是類的結(jié)構(gòu)體蚓胸,一個(gè)是元類的結(jié)構(gòu)體
  • category的結(jié)構(gòu)體里存放著實(shí)例方法和類方法的結(jié)構(gòu)體地址

分析運(yùn)行時(shí)

我們這里使用的是最新版本的oc代碼來(lái)分析挣饥,下載代碼時(shí)候,對(duì)應(yīng)的iOS13.3版本沛膳,oc代碼版本號(hào)objc4-756.2扔枫,可以去apple的opensource網(wǎng)站下載。

這是源碼中category結(jié)構(gòu)體锹安,跟我們上邊的編譯后的C++代碼是一樣的

struct category_t {
    const char *name; // 類名 Psrson
    classref_t cls; // 類結(jié)構(gòu)體地址
    struct method_list_t *instanceMethods; // 實(shí)例方法
    struct method_list_t *classMethods; // 類方法
    struct protocol_list_t *protocols; // 協(xié)議
    struct property_list_t *instanceProperties; // 實(shí)例屬性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties; // 
   // 元類方法還是實(shí)例方法
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

源碼解讀順序
objc-os.mm 調(diào)用順序

  1. _objc_init
    是OC運(yùn)行時(shí)的入口函數(shù)
  2. map_images
  3. map_images_nolock

objc-runtime-new.mm 調(diào)用順序

  1. _read_images 加載鏡像
  2. remethodizeClass 重新組裝類方法
  3. attachCategories
  4. attachLists
  5. realloc倚舀、memmove忍宋、 memcpy
    // 下邊的代碼中,為了方便查看源代碼有刪減糠排,只保留的關(guān)鍵邏輯
// 加載鏡像
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
    // 開始處理category.
    for (EACH_HEADER) {
        //獲取到所有的category列表舵稠,category放在二維數(shù)組里,每個(gè)數(shù)組存放著一個(gè)類的所有category
        //這個(gè)數(shù)據(jù)存放在代碼段的__objc_catlist中乳讥,感興趣的話可以查看Meth-O文件柱查。
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        //遍歷二維數(shù)組
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            // 判斷是否是實(shí)例方法,這個(gè)對(duì)應(yīng)了我們上邊編譯好的C++代碼
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 將對(duì)應(yīng)的category的方法列表放入到NXMap里
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    // 對(duì)類方法進(jìn)行重組
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }
            // 是否是類方法
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                      // 給元類對(duì)象重組方法 
                     remethodizeClass(cls->ISA());
                }
            }
        }
    }
}

//重組方法
static void remethodizeClass(Class cls)
{
    attachCategories(cls, cats, true /*flush caches*/);        
}

// 給類云石、元類附加方法
// 參數(shù):比如上邊的代碼示例
// cls = Person.class
// cats = [category_t(Test)]
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    // 申請(qǐng)二維數(shù)組結(jié)構(gòu)體空間唉工,空間大小是一個(gè)類的所有分類的個(gè)數(shù)
    /**方法數(shù)組
     [
        [method_t, method_t],
        [method_t, method_t],
     ]
     */
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    /**屬性數(shù)組
     [
        [property_t, property_t],
        [property_t, property_t],
     ]
     */
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    /**協(xié)議數(shù)組
     [
        [protocol_t, protocol_t],
        [protocol_t, protocol_t],
     ]
     */
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    // 這里使用了i--,將方法列表倒序放入數(shù)組中,所以最后面編譯的category在數(shù)組的最前邊
    while (i--) {
        // 取出某個(gè)分類
        auto& entry = cats->list[i];
        // 取出分類中的類方法或者元類方法汹忠,插入數(shù)組中
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    // 得到類對(duì)象數(shù)據(jù)
    auto rw = cls->data();

    // 將所有分類的對(duì)象方法淋硝,附加到類對(duì)象的方法列表中
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    // 將所有分類的屬性列表,附加到類對(duì)象的屬性列表中
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    // 將所有分類的協(xié)議列表宽菜,附加到類對(duì)象的協(xié)議列表中
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

    /** 將一個(gè)數(shù)組的數(shù)據(jù)插入到另一個(gè)數(shù)組的前邊
     addedLists
    [
       [method_t, method_t],
       [method_t, method_t],
    ]
     addedCount
    */
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        // 如果原來(lái)的列表中有數(shù)據(jù)
        if (hasArray()) {
            // many lists -> many lists
            // 原來(lái)方法列表的數(shù)量
            uint32_t oldCount = array()->count;
            // 插入方法列表之后的數(shù)量
            uint32_t newCount = oldCount + addedCount;
            // 重新分配內(nèi)存
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 先將老數(shù)據(jù)向后移動(dòng)addedCount位置
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // 將新數(shù)據(jù)copy進(jìn)來(lái)
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        // 如果原來(lái)的數(shù)據(jù)為空谣膳,并且新數(shù)據(jù)只有一個(gè)元素,那么直接復(fù)制
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        }
        
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

總結(jié)一下铅乡,以Person為例:

  • 程序啟動(dòng)時(shí)继谚,將Person的category分類列表載入內(nèi)存,順序?yàn)榫幾g順序阵幸,[method_t(test01),method_t(test02)]
  • 處理數(shù)組順序花履,將最后編譯的category數(shù)據(jù),放在數(shù)組的最前邊挚赊。
  • 處理類對(duì)象诡壁、元類對(duì)象數(shù)據(jù),將category數(shù)據(jù)列表荠割,插入到類對(duì)象妹卿、元類對(duì)象數(shù)據(jù)列表的最前邊。

+load方法的調(diào)用時(shí)機(jī)

上邊我們分析源碼的時(shí)候蔑鹦,提到了_objc_init 入口函數(shù)夺克。
這個(gè)函數(shù)里調(diào)用了load_images方法,接下來(lái)調(diào)用了call_load_methods方法嚎朽,我們看下源碼

void call_load_methods(void)
{
    do {
        // 1. 加載類的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        // 2.加載分類的load方法
        more_categories = call_category_loads();
    } while (loadable_classes_used > 0  ||  more_categories);
}
//1.加載類的load方法
static void call_class_loads(void)
{
    // 遍歷所有class懊直,通過函數(shù)指針直接調(diào)用load方法
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        (*load_method)(cls, SEL_load);
    }
}
// 2.加載分類的load方法
static bool call_category_loads(void)
{
// 遍歷所有category
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        // 獲取分類的class火鼻,通過函數(shù)指針直接調(diào)用load方法
        cls = _category_getClass(cat);
    }
    return new_categories_added;
}

通過源碼可以看到

  • +load方法會(huì)在runtime加載類、分類時(shí)調(diào)用
  • 每個(gè)類融撞、分類的+load尝偎,在程序運(yùn)行過程中只調(diào)用一次
  • 調(diào)用順序
    先調(diào)用類的+load,按照編譯先后順序調(diào)用(先編譯肤寝,先調(diào)用抖僵,調(diào)用子類的+load之前會(huì)先調(diào)用父類的+load
    再調(diào)用分類的+load耍群,按照編譯先后順序調(diào)用(先編譯蹈垢,先調(diào)用)

+ initialize方法的調(diào)用時(shí)機(jī)

initialize調(diào)用過程
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
initializeAndLeaveLocked
initializeAndMaybeRelock
initializeNonMetaClass
callInitialize
objc_msgSend(cls, SEL_initialize)

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
// 沒有初始化過曹抬,才進(jìn)行初始化
    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

//初始化class
void initializeNonMetaClass(Class cls)
{
    supercls = cls->superclass;
//    遞歸調(diào)用父類的初始化
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    if (reallyInitialize) {
//            初始化函數(shù)
            callInitialize(cls); //    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
           if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    }
}

總結(jié)一下

load堰酿、initialize方法的區(qū)別
  1. 調(diào)用方式
    1赖临、load是根據(jù)函數(shù)地址直接調(diào)用
    2兢榨、initialize是通過objc_msgSend調(diào)用

  2. 調(diào)用時(shí)刻
    1吵聪、load是runtime加載類吟逝、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次)
    2块攒、initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會(huì)initialize一次(父類的initialize方法可能會(huì)被調(diào)用多次)

load驹尼、initialize的調(diào)用順序
  • load
    1庞呕、 先調(diào)用類的load
    a) 先編譯的類住练,優(yōu)先調(diào)用load
    b) 調(diào)用子類的load之前讲逛,會(huì)先調(diào)用父類的load
    2、再調(diào)用分類的load
    a) 先編譯的分類顺呕,優(yōu)先調(diào)用load
  1. initialize
    1株茶、 先初始化父類
    2图焰、再初始化子類(可能最終調(diào)用的是父類的initialize方法)

關(guān)聯(lián)對(duì)象

開發(fā)過程中技羔,給category屬性賦值,通常會(huì)用到objc_setAssociatedObject 和 objc_getAssociatedObject方法鳖粟,接下來(lái)看看關(guān)聯(lián)對(duì)象在源碼中是怎么實(shí)現(xiàn)的向图。
大概的調(diào)用流程是這樣的


//objc_setAssociatedObject 
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    if (!object && !value) return;
    
    assert(object);
    
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 單利榄攀,管理所有的關(guān)聯(lián)數(shù)據(jù)
        AssociationsManager manager;
        // 獲取所有的關(guān)聯(lián)對(duì)象的map
        AssociationsHashMap &associations(manager.associations());
        // 通過傳進(jìn)來(lái)的對(duì)象生成一個(gè)key
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            // 通過key獲取一個(gè)value檩赢,這個(gè)value是這個(gè)對(duì)象的所有關(guān)聯(lián)屬性的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                // 通過外邊傳進(jìn)來(lái)的key查找到與這個(gè)對(duì)象相關(guān)聯(lián)的屬性map
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    // 更新map中的值贞瞒,包含了內(nèi)存管理方式和具體的value
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // 第一次賦值
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            // 如果傳進(jìn)來(lái)的value是nil,則擦除key對(duì)應(yīng)的value
            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;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        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()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

總結(jié)

通過category我們了解到了類的方法列表相關(guān)的內(nèi)存結(jié)構(gòu)蝴悉,load拍冠、initialize方法的調(diào)用時(shí)機(jī)簇抵,關(guān)聯(lián)對(duì)象的調(diào)用原理碟摆,再結(jié)合之前的一篇oc的Objective-C的內(nèi)存結(jié)構(gòu)文章典蜕,我們大概整體了解了OC對(duì)象的內(nèi)存結(jié)構(gòu)。
其實(shí)關(guān)于OC語(yǔ)法的相關(guān)內(nèi)容钢猛,源碼中都能找到答案命迈。只要靜下心來(lái)火的,用心讀源碼馏鹤,很多問題都會(huì)迎刃而解。

如果你覺得有用勃救,給個(gè)點(diǎn)贊吧剪芥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琴许,一起剝皮案震驚了整個(gè)濱河市榜田,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌净捅,老刑警劉巖蛔六,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件国章,死亡現(xiàn)場(chǎng)離奇詭異液兽,居然都是意外死亡掌动,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)适滓,“玉大人,你說我怎么就攤上這事罚屋⌒岢瘢” “怎么了鱼鸠?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蚀狰,是天一觀的道長(zhǎng)麻蹋。 經(jīng)常有香客問我,道長(zhǎng)芳室,這世上最難降的妖魔是什么堪侯? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任伍宦,我火速辦了婚禮次洼,結(jié)果婚禮上滓玖,老公的妹妹穿的比我還像新娘。我一直安慰自己翩肌,他們只是感情好念祭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布粱坤。 她就那樣靜靜地躺著站玄,像睡著了一般株旷。 火紅的嫁衣襯著肌膚如雪尔邓。 梳的紋絲不亂的頭發(fā)上梯嗽,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天灯节,我揣著相機(jī)與錄音缠俺,去河邊找鬼。 笑死磷雇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的螟蒸。 我是一名探鬼主播七嫌,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诵原,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了辑畦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚯妇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后暂筝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箩言,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年焕襟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了分扎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胧洒,死狀恐怖畏吓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卫漫,我是刑警寧澤菲饼,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布宏悦,位于F島的核電站,受9級(jí)特大地震影響息堂,放射性物質(zhì)發(fā)生泄漏荣堰。R本人自食惡果不足惜渡八,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一损离、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祖乳,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吧兔。三九已至冬竟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人七问。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親籍琳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355