深入理解Objective-C:Category

最近在學(xué)習(xí)runtime菠齿,順便再深入理解一下OC的分類剥槐,做一下文字搬運(yùn)工损谦,完全照搬過來的。

原文出處:
深入理解Objective-C:Category

下面是正文:

1啸如、初入寶地-category簡介

category是Objective-C 2.0之后添加的語言特性侍匙,category的主要作用是為已經(jīng)存在的類添加方法氮惯。除此之外叮雳,apple還推薦了category的另外兩個(gè)使用場景1

  • 可以把類的實(shí)現(xiàn)分開在幾個(gè)不同的文件里面。這樣做有幾個(gè)顯而易見的好處帘不,a)可以減少單個(gè)文件的體積 b)可以把不同的功能組織到不同的category里 c)可以由多個(gè)開發(fā)者共同完成一個(gè)類 d)可以按需加載想要的category 等等。
  • 聲明私有方法

不過除了apple推薦的使用場景杨箭,廣大開發(fā)者腦洞大開,還衍生出了category的其他幾個(gè)使用場景:

  • 模擬多繼承
  • 把framework的私有方法公開

Objective-C的這個(gè)語言特性對于純動態(tài)語言來說可能不算什么,比如javascript,你可以隨時(shí)為一個(gè)“類”或者對象添加任意方法和實(shí)例變量。但是對于不是那么“動態(tài)”的語言而言猜绣,這確實(shí)是一個(gè)了不起的特性灰殴。

2、連類比事-category和extension

extension看起來很像一個(gè)匿名的category掰邢,但是extension和有名字的category幾乎完全是兩個(gè)東西牺陶。 extension在編譯期決議擅羞,它就是類的一部分,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類义图,它伴隨類的產(chǎn)生而產(chǎn)生减俏,亦隨之一起消亡。extension一般用來隱藏類的私有信息碱工,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension娃承,所以你無法為系統(tǒng)的類比如NSString添加extension。(詳見2
但是category則完全不一樣怕篷,它是在運(yùn)行期決議的历筝。就category和extension的區(qū)別來看,我們可以推導(dǎo)出一個(gè)明顯的事實(shí)廊谓,extension可以添加實(shí)例變量梳猪,而category是無法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期,對象的內(nèi)存布局已經(jīng)確定蒸痹,如果添加實(shí)例變量就會破壞類的內(nèi)部布局春弥,這對編譯型語言來說是災(zāi)難性的)。

3叠荠、挑燈細(xì)覽-category真面目

我們知道匿沛,所有的OC類和對象,在runtime層都是用struct表示的榛鼎,category也不例外逃呼,在runtime層,category用結(jié)構(gòu)體category_t(在objc-runtime-new.h中可以找到此定義)者娱,它包含了
1)抡笼、類的名字(name)
2)、類(cls)
3)黄鳍、category中所有給類添加的實(shí)例方法的列表(instanceMethods)
4)推姻、category中所有添加的類方法的列表(classMethods)
5)、category實(shí)現(xiàn)的所有協(xié)議的列表(protocols)
6)际起、category中添加的所有屬性(instanceProperties)

typedef struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
} category_t;

從category的定義也可以看出category的可為(可以添加實(shí)例方法拾碌,類方法,甚至可以實(shí)現(xiàn)協(xié)議街望,添加屬性)和不可為(無法添加實(shí)例變量)校翔。
ok,我們先去寫一個(gè)category看一下category到底為何物:

MyClass.h:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject

- (void)printName;

@end

@interface MyClass(MyAddition)

@property(nonatomic, copy) NSString *name;

- (void)printName;

@end

MyClass.m:

#import "MyClass.h"

@implementation MyClass

- (void)printName
{
    NSLog(@"%@",@"MyClass");
}

@end

@implementation MyClass(MyAddition)

- (void)printName
{
    NSLog(@"%@",@"MyAddition");
}

@end

我們使用clang的命令去看看category到底會變成什么:
clang -rewrite-objc MyClass.m
好吧灾前,我們得到了一個(gè)3M大小防症,10w多行的.cpp文件(這絕對是Apple值得吐槽的一點(diǎn)),我們忽略掉所有和我們無關(guān)的東西,在文件的最后蔫敲,我們找到了如下代碼片段:

static struct /*_method_list_t*/ {
unsigned int entsize;  // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}}
};

static struct /*_prop_list_t*/ {
unsigned int entsize;  // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"name","T@\"NSString\",C,N"}}
};

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;

static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClass",
0, // &OBJC_CLASS_$_MyClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_MyClass,
};
static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = {
&OBJC_CLASS_$_MyClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};

我們可以看到饲嗽,
1)、首先編譯器生成了實(shí)例方法列表OBJC$_CATEGORY_INSTANCE_METHODSMyClass$_MyAddition和屬性列表OBJC$_PROP_LISTMyClass$_MyAddition奈嘿,兩者的命名都遵循了公共前綴+類名+category名字的命名方式貌虾,而且實(shí)例方法列表里面填充的正是我們在MyAddition這個(gè)category里面寫的方法printName,而屬性列表里面填充的也正是我們在MyAddition里添加的name屬性裙犹。還有一個(gè)需要注意到的事實(shí)就是category的名字用來給各種列表以及后面的category結(jié)構(gòu)體本身命名尽狠,而且有static來修飾,所以在同一個(gè)編譯單元里我們的category名不能重復(fù)叶圃,否則會出現(xiàn)編譯錯誤袄膏。
2)、其次掺冠,編譯器生成了category本身OBJC$_CATEGORYMyClass$_MyAddition沉馆,并用前面生成的列表來初始化category本身。
3)德崭、最后斥黑,編譯器在DATA段下的objc_catlist section里保存了一個(gè)大小為1的category_t的數(shù)組L_OBJC_LABELCATEGORY$(當(dāng)然,如果有多個(gè)category接癌,會生成對應(yīng)長度的數(shù)組_)心赶,用于運(yùn)行期category的加載。
到這里缺猛,編譯器的工作就接近尾聲了,對于category在運(yùn)行期怎么加載椭符,我們下節(jié)揭曉荔燎。

4、追本溯源-category如何加載

我們知道销钝,Objective-C的運(yùn)行是依賴OC的runtime的有咨,而OC的runtime和其他系統(tǒng)庫一樣,是OS X和iOS通過dyld動態(tài)加載的蒸健。想了解更多dyld地同學(xué)可以移步這里(3)座享。
對于OC運(yùn)行時(shí),入口方法如下(在objc-os.mm文件中):

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();

    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

category被附加到類上面是在map_images的時(shí)候發(fā)生的似忧,在new-ABI的標(biāo)準(zhǔn)下渣叛,_objc_init里面的調(diào)用的map_images最終會調(diào)用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的結(jié)尾盯捌,有以下的代碼片段:

// Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            class_t *cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = NULL;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            BOOL classExists = NO;
            if (cat->instanceMethods ||  cat->protocols 
                ||  cat->instanceProperties)
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (isRealized(cls)) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s",
                                 getName(cls), cat->name,
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols 
                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->isa, hi);
                if (isRealized(cls->isa)) {
                    remethodizeClass(cls->isa);
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 getName(cls), cat->name);
                }
            }
        }
    }

首先淳衙,我們拿到的catlist就是上節(jié)中講到的編譯器為我們準(zhǔn)備的category_t數(shù)組,關(guān)于是如何加載catlist本身的,我們暫且不表箫攀,這和category本身的關(guān)系也不大肠牲,有興趣的同學(xué)可以去研究以下Apple的二進(jìn)制格式和load機(jī)制。
略去PrintConnecting這個(gè)用于log的東西靴跛,這段代碼很容易理解:
1)缀雳、把category的實(shí)例方法、協(xié)議以及屬性添加到類上
2)梢睛、把category的類方法和協(xié)議添加到類的metaclass上

值得注意的是俏险,在代碼中有一小段注釋 / || cat->classProperties /,看來蘋果有過給類添加屬性的計(jì)劃啊扬绪。
ok竖独,我們接著往里看,category的各種列表是怎么最終添加到類上的挤牛,就拿實(shí)例方法列表來說吧:
在上述的代碼片段里莹痢,addUnattachedCategoryForClass只是把類和category做一個(gè)關(guān)聯(lián)映射,而remethodizeClass才是真正去處理添加事宜的功臣墓赴。

static void remethodizeClass(class_t *cls)
{
    category_list *cats;
    BOOL isMeta;

    rwlock_assert_writing(&runtimeLock);

    isMeta = isMetaClass(cls);

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls))) {
        chained_property_list *newproperties;
        const protocol_list_t **newprotos;

        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s",
                         getName(cls), isMeta ? "(meta)" : "");
        }

        // Update methods, properties, protocols

        BOOL vtableAffected = NO;
        attachCategoryMethods(cls, cats, &vtableAffected);

        newproperties = buildPropertyList(NULL, cats, isMeta);
        if (newproperties) {
            newproperties->next = cls->data()->properties;
            cls->data()->properties = newproperties;
        }

        newprotos = buildProtocolList(cats, NULL, cls->data()->protocols);
        if (cls->data()->protocols  &&  cls->data()->protocols != newprotos) {
            _free_internal(cls->data()->protocols);
        }
        cls->data()->protocols = newprotos;

        _free_internal(cats);

        // Update method caches and vtables
        flushCaches(cls);
        if (vtableAffected) flushVtables(cls);
    }
}

而對于添加類的實(shí)例方法而言竞膳,又會去調(diào)用attachCategoryMethods這個(gè)方法,我們?nèi)タ聪耡ttachCategoryMethods:

static void 
attachCategoryMethods(class_t *cls, category_list *cats,
                      BOOL *inoutVtablesAffected)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    BOOL isMeta = isMetaClass(cls);
    method_list_t **mlists = (method_list_t **)
        _malloc_internal(cats->count * sizeof(*mlists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    BOOL fromBundle = NO;
    while (i--) {
        method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= cats->list[i].fromBundle;
        }
    }

    attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);

    _free_internal(mlists);

}

attachCategoryMethods做的工作相對比較簡單诫硕,它只是把所有category的實(shí)例方法列表拼成了一個(gè)大的實(shí)例方法列表坦辟,然后轉(zhuǎn)交給了attachMethodLists方法(我發(fā)誓,這是本節(jié)我們看的最后一段代碼了_)章办,這個(gè)方法有點(diǎn)長锉走,我們只看一小段:

for (uint32_t m = 0;
             (scanForCustomRR || scanForCustomAWZ)  &&  m < mlist->count;
             m++)
        {
            SEL sel = method_list_nth(mlist, m)->name;
            if (scanForCustomRR  &&  isRRSelector(sel)) {
                cls->setHasCustomRR();
                scanForCustomRR = false;
            } else if (scanForCustomAWZ  &&  isAWZSelector(sel)) {
                cls->setHasCustomAWZ();
                scanForCustomAWZ = false;
            }
        }

        // Fill method list array
        newLists[newCount++] = mlist;
    .
    .
    .

    // Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }

需要注意的有兩點(diǎn):
1)、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法藕届,也就是說如果category和原來類都有methodA挪蹭,那么category附加完成之后,類的方法列表里會有兩個(gè)methodA
2)休偶、category的方法被放到了新方法列表的前面梁厉,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法踏兜,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的词顾,它只要一找到對應(yīng)名字的方法,就會罷休_碱妆,殊不知后面可能還有一樣名字的方法肉盹。

5、旁枝末葉-category和+load方法

我們知道山橄,在類和category中都可以有+load方法垮媒,那么有兩個(gè)問題:
1)舍悯、在類的+load方法調(diào)用的時(shí)候,我們可以調(diào)用category中聲明的方法么睡雇?
2)萌衬、這么些個(gè)+load方法,調(diào)用順序是咋樣的呢它抱?
鑒于上述幾節(jié)我們看的代碼太多了秕豫,對于這兩個(gè)問題我們先來看一點(diǎn)直觀的:


project.png

我們的代碼里有MyClass和MyClass的兩個(gè)category (Category1和Category2),MyClass和兩個(gè)category都添加了+load方法观蓄,并且Category1和Category2都寫了MyClass的printName方法混移。
在Xcode中點(diǎn)擊Edit Scheme,添加如下兩個(gè)環(huán)境變量(可以在執(zhí)行l(wèi)oad方法以及加載category的時(shí)候打印log信息侮穿,更多的環(huán)境變量選項(xiàng)可參見objc-private.h):

environment_vars.png

運(yùn)行項(xiàng)目歌径,我們會看到控制臺打印很多東西出來,我們只找到我們想要的信息亲茅,順序如下:

objc[1187]: REPLACED: -[MyClass printName] by category Category1
objc[1187]: REPLACED: -[MyClass printName] by category Category2
.
.
.
objc[1187]: LOAD: class 'MyClass' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[1187]: LOAD: +[MyClass load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category1) load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category2) load]


所以回铛,對于上面兩個(gè)問題,答案是很明顯的:
1)克锣、可以調(diào)用茵肃,因?yàn)楦郊觕ategory到類的工作會先于+load方法的執(zhí)行
2)、+load的執(zhí)行順序是先類袭祟,后category验残,而category的+load執(zhí)行順序是根據(jù)編譯順序決定的。
目前的編譯順序是這樣的:

compile1.png

我們調(diào)整一個(gè)Category1和Category2的編譯順序巾乳,run您没。ok,我們可以看到控制臺的輸出順序變了:

compile2.png.jpeg
objc[1187]: REPLACED: -[MyClass printName] by category Category2
objc[1187]: REPLACED: -[MyClass printName] by category Category1
.
.
.
objc[1187]: LOAD: class 'MyClass' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load
objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load
objc[1187]: LOAD: +[MyClass load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category2) load]
.
.
.
objc[1187]: LOAD: +[MyClass(Category1) load]


雖然對于+load的執(zhí)行順序是這樣想鹰,但是對于“覆蓋”掉的方法紊婉,則會先找到最后一個(gè)編譯的category里的對應(yīng)方法。
這一節(jié)我們只是用很直觀的方式得到了問題的答案辑舷,有興趣的同學(xué)可以繼續(xù)去研究一下OC的運(yùn)行時(shí)代碼。

6槽片、觸類旁通-category和方法覆蓋

鑒于上面幾節(jié)我們已經(jīng)把原理都講了何缓,這一節(jié)只有一個(gè)問題:
怎么調(diào)用到原來類中被category覆蓋掉的方法?
對于這個(gè)問題还栓,我們已經(jīng)知道category其實(shí)并不是完全替換掉原來類的同名方法碌廓,只是category在方法列表的前面而已,所以我們只要順著方法列表找到最后一個(gè)對應(yīng)名字的方法剩盒,就可以調(diào)用原來類的方法:

Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];

if (currentClass) {
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    IMP lastImp = NULL;
    SEL lastSel = NULL;
    for (NSInteger i = 0; i < methodCount; i++) {
        Method method = methodList[i];
        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
                                        encoding:NSUTF8StringEncoding];
        if ([@"printName" isEqualToString:methodName]) {
            lastImp = method_getImplementation(method);
            lastSel = method_getName(method);
        }
    }
    typedef void (*fn)(id,SEL);

    if (lastImp != NULL) {
        fn f = (fn)lastImp;
        f(my,lastSel);
    }
    free(methodList);
}

7谷婆、更上一層-category和關(guān)聯(lián)對象

如上所見,我們知道在category里面是無法為category添加實(shí)例變量的。但是我們很多時(shí)候需要在category中添加和對象關(guān)聯(lián)的值纪挎,這個(gè)時(shí)候可以求助關(guān)聯(lián)對象來實(shí)現(xiàn)期贫。

MyClass+Category1.h:

#import "MyClass.h"

@interface MyClass (Category1)

@property(nonatomic,copy) NSString *name;

@end

MyClass+Category1.m:

#import "MyClass+Category1.h"
#import <objc/runtime.h>

@implementation MyClass (Category1)

+ (void)load
{
    NSLog(@"%@",@"load in Category1");
}

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

@end

但是關(guān)聯(lián)對象又是存在什么地方呢? 如何存儲异袄? 對象銷毀時(shí)候如何處理關(guān)聯(lián)對象呢通砍?
我們?nèi)シ幌聄untime的源碼,在objc-references.mm文件中有個(gè)方法_object_set_associative_reference:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                _class_setInstancesHaveAssociatedObjects(_object_getClass(object));
            }
        } else {
            // setting the association to nil breaks the association.
            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);
}

我們可以看到所有的關(guān)聯(lián)對象都由AssociationsManager管理烤蜕,而AssociationsManager定義如下:

class AssociationsManager {
    static OSSpinLock _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { OSSpinLockLock(&_lock); }
    ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager里面是由一個(gè)靜態(tài)AssociationsHashMap來存儲所有的關(guān)聯(lián)對象的封孙。這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個(gè)全局map里面。而map的的key是這個(gè)對象的指針地址(任意兩個(gè)不同對象的指針地址一定是不同的)讽营,而這個(gè)map的value又是另外一個(gè)AssociationsHashMap虎忌,里面保存了關(guān)聯(lián)對象的kv對。
而在對象的銷毀邏輯里面橱鹏,見objc-runtime-new.mm:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);

        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);

        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}

嗯膜蠢,runtime的銷毀對象函數(shù)objc_destructInstance里面會判斷這個(gè)對象有沒有關(guān)聯(lián)對象,如果有蚀瘸,會調(diào)用_object_remove_assocations做關(guān)聯(lián)對象的清理工作狡蝶。

后記

正如侯捷先生所講-“源碼面前,了無秘密”贮勃,Apple的Cocoa Touch框架雖然并不開源贪惹,但是Objective-C的runtime和Core Foundation卻是完全開放源碼的(在http://www.opensource.apple.com/tarballs/可以下載到全部的開源代碼)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寂嘉,一起剝皮案震驚了整個(gè)濱河市奏瞬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泉孩,老刑警劉巖硼端,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寓搬,居然都是意外死亡珍昨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門句喷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镣典,“玉大人,你說我怎么就攤上這事唾琼⌒执海” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵锡溯,是天一觀的道長赶舆。 經(jīng)常有香客問我哑姚,道長,這世上最難降的妖魔是什么芜茵? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任叙量,我火速辦了婚禮,結(jié)果婚禮上夕晓,老公的妹妹穿的比我還像新娘宛乃。我一直安慰自己,他們只是感情好蒸辆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布征炼。 她就那樣靜靜地躺著,像睡著了一般躬贡。 火紅的嫁衣襯著肌膚如雪谆奥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天拂玻,我揣著相機(jī)與錄音酸些,去河邊找鬼。 笑死檐蚜,一個(gè)胖子當(dāng)著我的面吹牛魄懂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闯第,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼市栗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咳短?” 一聲冷哼從身側(cè)響起填帽,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咙好,沒想到半個(gè)月后篡腌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勾效,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年嘹悼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片层宫。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绘迁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卒密,到底是詐尸還是另有隱情,我是刑警寧澤棠赛,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布哮奇,位于F島的核電站膛腐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鼎俘。R本人自食惡果不足惜哲身,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贸伐。 院中可真熱鬧勘天,春花似錦、人聲如沸捉邢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伏伐。三九已至宠进,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藐翎,已是汗流浹背材蹬。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吝镣,地道東北人堤器。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像末贾,于是被迫代替她去往敵國和親闸溃。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容