category學(xué)習(xí)筆記

1.category 是什么职员?

首先拒贱,新建一個(gè)NcFood類瘾境,并添加兩個(gè)分類

#import <Foundation/Foundation.h>

@interface NcFood : NSObject

- (void)color;

@end

@interface NcFood (Apple)

@property(nonatomic,strong)NSString *name;

@end

@interface NcFood (Mango)

@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int amount;

@end
#import "NcFood.h"

@implementation NcFood

- (void)color {
    NSLog(@"color");
}

@end


@implementation NcFood (Apple)

- (void)color {
    NSLog(@"pink");
}

@end

@implementation NcFood (Mango)

- (void)color {
    NSLog(@"golden yellow");
}

@end

用Clang編譯成c++文件

終端輸入:
 clang -rewrite-objc Ncfood.m

忽略不用的信息铐刘,先查看編譯后的category的結(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;
};

再看編譯后的分類Apple與Mango

Apple:

// 實(shí)例方法列表
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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Apple_color}}
};

// 屬性列表
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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"name","T@\"NSString\",&,N"}}
};

// 分類結(jié)構(gòu)體
static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "NcFood",
    0, // &OBJC_CLASS_$_NcFood,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Apple,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Apple,
};

//  此處將_NcFood類的地址賦給Apple分類結(jié)構(gòu)體中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Apple(void ) {
    _OBJC_$_CATEGORY_NcFood_$_Apple.cls = &OBJC_CLASS_$_NcFood;
}

Mango:

// 實(shí)例方法列表
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_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Mango_color}}
};

// 屬性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"name","T@\"NSString\",&,N"},
    {"amount","Ti,N"}}
};

// 分類結(jié)構(gòu)體
static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "NcFood",
    0, // &OBJC_CLASS_$_NcFood,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Mango,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Mango,
};

//  此處將_NcFood類的地址賦給Mango分類結(jié)構(gòu)體中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Mango(void ) {
    _OBJC_$_CATEGORY_NcFood_$_Mango.cls = &OBJC_CLASS_$_NcFood;
}

另外還有一點(diǎn)需要關(guān)注的

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_NcFood_$_Apple,
    &_OBJC_$_CATEGORY_NcFood_$_Mango,
};

所有的分類都會被保存在這個(gè)表里陪每,在程序啟動運(yùn)行的時(shí)候會遍歷這個(gè)列表,將其中的category與它所屬的類關(guān)聯(lián)镰吵。

編譯后的category就是這個(gè)樣子了

關(guān)于category的屬性

以上兩個(gè)分類(Apple與Mango)都有自己的屬性檩禾,但在實(shí)例方法列表中并沒有看到相應(yīng)的set與get方法,分類的結(jié)構(gòu)體中也沒有用來存儲成員變量的地方疤祭,所以分類中的屬性沒有實(shí)現(xiàn)set與get方法盼产,也沒有自動生成成員變量。
用點(diǎn)語法訪問分類的屬性會導(dǎo)致程序崩潰画株。(點(diǎn)語法本質(zhì)就是調(diào)用set辆飘、get方法)

    NcFood *food = [[NcFood alloc] init];
    NSLog(@"---%@",food.name); // 此處崩潰

2018-11-20 17:10:45.419760+0800 Nunca[39382:6373286] -[NcFood name]: unrecognized selector sent to instance 0x600000008860

2. category的加載

category的加載發(fā)生在程序啟動后調(diào)用的map_images函數(shù)中(objc-runtime-new.mm類中)
在map_images里面啦辐,在加載category之前還會先加載好Class谓传、_Protocol。

以下是其中加載分類的代碼

// Discover categories. 
    for (EACH_HEADER) {
        //取出編譯時(shí)生成的一個(gè)包含程序中所有category的list
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) { // 遍歷每一個(gè)category
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls); // 獲取所屬的類

            if (!cls) { 
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                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) 
            {
               // 在此方法中將分類存到類對應(yīng)的其分類的list(類與類的分類可能是一對多的)
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    //  在此方法中將分類中的各種方法添加至類的各種方法列表
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }
            //處理類方法
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

主要需要關(guān)注兩個(gè)方法

addUnattachedCategoryForClass :將新遍歷到的category添加至還未與類進(jìn)行關(guān)聯(lián)的對應(yīng)的category_list

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertWriting();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    
   //cats是一個(gè)以cls為key芹关、以category_list為value的map
    NXMapTable *cats = unattachedCategories();
    category_list *list;
    // 根據(jù)cls獲取還未進(jìn)行關(guān)聯(lián)的分類列表
    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
     // 往未進(jìn)行關(guān)聯(lián)的分類列表中添加新的分類
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}

remethodizeClass :將分類中的各種方法整合至類的方法列表

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    // unattachedCategoriesForClass里面會返回類對應(yīng)的分類列表续挟,并將其從map中移除
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        // 此處才是真正進(jìn)行關(guān)聯(lián)
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

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
    // 都是二維數(shù)組
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    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;
    while (i--) {
        // 倒序遍歷,編譯越后的category侥衬,在list中越靠前
        auto& entry = cats->list[i];

        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;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

可以看到诗祸,將分類方法添加至類的方法列表是通過attachLists,下面摘錄attachLists方法中關(guān)鍵的一段

 void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 將原有的方法往后移
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // 將新加的方法添加至最前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
 }
關(guān)于分類的調(diào)用順序

可以看到轴总,新添加的分類的方法會在類的方法的最前部直颅。
越后編譯的分類的方法在類的方法列表中位置越靠前,因此怀樟,如果一個(gè)類與它的分類中存在相同的方法功偿,在調(diào)用此方法時(shí),會從它的類的方法列表去查找往堡,最后被編譯的在列表的最前面械荷,因此被調(diào)用的也是最后被編譯的那個(gè)分類中的那個(gè)方法(即使此分類的頭文件未被導(dǎo)入至方法調(diào)用的類)共耍。
(編譯的順序往往是我們不太關(guān)心確定的,因此最好不要在同一個(gè)類的多個(gè)分類中寫同名的方法)

主要參考:
深入理解Objective-C:Category

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吨瞎,一起剝皮案震驚了整個(gè)濱河市痹兜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颤诀,老刑警劉巖字旭,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異崖叫,居然都是意外死亡谐算,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門归露,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洲脂,“玉大人,你說我怎么就攤上這事剧包】纸酰” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵疆液,是天一觀的道長一铅。 經(jīng)常有香客問我,道長堕油,這世上最難降的妖魔是什么潘飘? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮掉缺,結(jié)果婚禮上卜录,老公的妹妹穿的比我還像新娘。我一直安慰自己眶明,他們只是感情好艰毒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搜囱,像睡著了一般丑瞧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜀肘,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天绊汹,我揣著相機(jī)與錄音,去河邊找鬼扮宠。 笑死西乖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浴栽,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荒叼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了典鸡?” 一聲冷哼從身側(cè)響起被廓,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萝玷,沒想到半個(gè)月后嫁乘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡球碉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年蜓斧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睁冬。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挎春,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豆拨,到底是詐尸還是另有隱情直奋,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布施禾,位于F島的核電站脚线,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弥搞。R本人自食惡果不足惜邮绿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攀例。 院中可真熱鬧船逮,春花似錦、人聲如沸肛度。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽承耿。三九已至,卻和暖如春伪煤,著一層夾襖步出監(jiān)牢的瞬間加袋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工抱既, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留职烧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蚀之,于是被迫代替她去往敵國和親蝗敢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,386評論 8 265
  • 文鳳老師說“知道做不到等于不知道”足删。我們都懂得很多寿谴,做到卻很少,因?yàn)樽龅胶茈y失受,堅(jiān)持做就更難讶泰! ...
    曾秋萍閱讀 226評論 1 1
  • 文/白芷 3歲的兒子對我說:“媽媽我長大要當(dāng)警察叔叔》鞯剑”我既欣喜又訝異痪署,不知道孩子長大后是否還會記得他人生中的第一...
    白芷茶舍閱讀 376評論 1 3
  • 以前也讀過如何快速閱讀之類的書,但沒有實(shí)踐兄旬,總以為一字一句的讀才能細(xì)嚼慢咽狼犯,快速閱讀是噱頭罷了。 我愛買書领铐,喜歡的...
    如意課堂閱讀 172評論 0 0