category学习笔记

1.category 是什么?

首先,新建一个NcFood类,并添加两个分类

#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的结构

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:

// 实例方法列表
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"}}
};

// 分类结构体
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分类结构体中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Apple(void ) {
    _OBJC_$_CATEGORY_NcFood_$_Apple.cls = &OBJC_CLASS_$_NcFood;
}

Mango:

// 实例方法列表
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"}}
};

// 分类结构体
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分类结构体中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Mango(void ) {
    _OBJC_$_CATEGORY_NcFood_$_Mango.cls = &OBJC_CLASS_$_NcFood;
}

另外还有一点需要关注的

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

所有的分类都会被保存在这个表里,在程序启动运行的时候会遍历这个列表,将其中的category与它所属的类关联。

编译后的category就是这个样子了

关于category的属性

以上两个分类(Apple与Mango)都有自己的属性,但在实例方法列表中并没有看到相应的set与get方法,分类的结构体中也没有用来存储成员变量的地方,所以分类中的属性没有实现set与get方法,也没有自动生成成员变量。
用点语法访问分类的属性会导致程序崩溃。(点语法本质就是调用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的加载发生在程序启动后调用的map_images函数中(objc-runtime-new.mm类中)
在map_images里面,在加载category之前还会先加载好Class、_Protocol。

以下是其中加载分类的代码

// Discover categories. 
    for (EACH_HEADER) {
        //取出编译时生成的一个包含程序中所有category的list
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) { // 遍历每一个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) 
            {
               // 在此方法中将分类存到类对应的其分类的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);
                }
            }
        }
    }

主要需要关注两个方法

addUnattachedCategoryForClass :将新遍历到的category添加至还未与类进行关联的对应的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是一个以cls为key、以category_list为value的map
    NXMapTable *cats = unattachedCategories();
    category_list *list;
    // 根据cls获取还未进行关联的分类列表
    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));
    }
     // 往未进行关联的分类列表中添加新的分类
    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里面会返回类对应的分类列表,并将其从map中移除
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        // 此处才是真正进行关联
        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
    // 都是二维数组
    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方法中关键的一段

 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]));
        }
 }
关于分类的调用顺序

可以看到,新添加的分类的方法会在类的方法的最前部。
越后编译的分类的方法在类的方法列表中位置越靠前,因此,如果一个类与它的分类中存在相同的方法,在调用此方法时,会从它的类的方法列表去查找,最后被编译的在列表的最前面,因此被调用的也是最后被编译的那个分类中的那个方法(即使此分类的头文件未被导入至方法调用的类)。
(编译的顺序往往是我们不太关心确定的,因此最好不要在同一个类的多个分类中写同名的方法)

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,096评论 1 32
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,371评论 8 265
  • 文凤老师说“知道做不到等于不知道”。我们都懂得很多,做到却很少,因为做到很难,坚持做就更难! ...
    曾秋萍阅读 226评论 1 1
  • 文/白芷 3岁的儿子对我说:“妈妈我长大要当警察叔叔。”我既欣喜又讶异,不知道孩子长大后是否还会记得他人生中的第一...
    白芷茶舍阅读 375评论 1 3
  • 以前也读过如何快速阅读之类的书,但没有实践,总以为一字一句的读才能细嚼慢咽,快速阅读是噱头罢了。 我爱买书,喜欢的...
    如意课堂阅读 172评论 0 0