美文网首页
聊聊分类 Category

聊聊分类 Category

作者: 苏沫离 | 来源:发表于2019-01-10 00:09 被阅读0次

在Objective-C 2.0中,提供了Category 这个语言特性,可以动态地为已有类添加新行为。那么我们在使用Category 时,有没有想过 Category 是什么?它是如何实现的?我们今天就来研究下Category 的底层实现!

1、Category 的本质

我们全局搜索category 可以找到它的声明与定义:

//Category 本质是一个结构指针,指向结构体实例 category_t
typedef struct category_t *Category;;

// 结构 category_t 
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;//分类添加的实例属性列表
    struct property_list_t *_classProperties;  //分类添加的类属性列表
    
    //获取方法列表
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    //获取属性列表
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

Category 结构成员看出可以在 Category中添加实例方法、类方法,实现某些协议,添加属性;但是不可以在 Category 中添加实例变量。

那么 Objective-C 是如何实现 Category 中添加的实例方法、类方法,实现的协议,添加的属性呢?

2、Category的源码转换过程

2.1、 Objective-C 源码

在程序某处声明的一个协议ObjectDelegate

@protocol ObjectDelegate <NSObject>
- (void)logObjectInfo;
@end

使用 Objective-C 写一段 Category 的源码:

@interface SuperModel (Test)
<ObjectDelegate>
@property (nonatomic ,strong) NSString *nickName;
@property (nonatomic ,strong) NSString *age;
- (void)logNickName;
+ (void)logHello;
@end
@implementation SuperModel (Test)

- (void)logNickName{
    NSLog(@"class_getName  ==== %s",class_getName(self.class));
}

+ (void)logHello{
    NSLog(@"class_getName  ---- %s",class_getName(self.class));
}

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

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

#pragma mark - ObjectDelegate

- (void)logObjectInfo{
    NSLog(@"实现协议 ObjectDelegate 的 -logObjectInfo 方法");
}
@end
2.2、转为 C++ 源码
xcrun -sdk iphonesimulator clang -rewrite-objc main.m

使用上述指令将上文的 Objective-C 代码转为 C++ 代码,可以生成一个 .cpp 文件,该文件有几万行代码,很多代码并不是本节我们研究的方向所以暂不关注!在该文件搜索关键字 Test 查找与分类有关的实现代码:

2.2.1、一些方法的实现

.cpp 文件找到关于 Category 中添加的实例方法、类方法的关键代码:

//实例方法 - (void)logNickName 的实现函数
static void _I_SuperModel_Test_logNickName(SuperModel * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_15,class_getName(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}

//类方法 + (void)logHello 的实现函数
static void _C_SuperModel_Test_logHello(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_16,class_getName(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}

//属性 nickName 的 set 函数
static void _I_SuperModel_Test_setNickName_(SuperModel * self, SEL _cmd, NSString *nickName) {
    objc_setAssociatedObject(self, sel_registerName("nickName"), nickName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

//属性 nickName 的 get 函数
static NSString * _I_SuperModel_Test_nickName(SuperModel * self, SEL _cmd) {
    return objc_getAssociatedObject(self, _cmd);
}

//实现协议的函数
static void _I_SuperModel_Test_logObjectInfo(SuperModel * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6___xgbfnx57zz3j5cdqx19dtnm0000gn_T_Model_ff5446_mi_17);
}

//分类的实例方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[4];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    4,
    {{(struct objc_selector *)"logNickName", "v16@0:8", (void *)_I_SuperModel_Test_logNickName},
        {(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_SuperModel_Test_setNickName_},
        {(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_SuperModel_Test_nickName},
        {(struct objc_selector *)"logObjectInfo", "v16@0:8", (void *)_I_SuperModel_Test_logObjectInfo}}
};

//分类的类方法列表
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_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"logHello", "v16@0:8", (void *)_C_SuperModel_Test_logHello}}
};

我们可以看到在Category中添加的实例方法、类方法的IMP指向的函数的具体实现;然后生成两个方法列表:

  • _OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test用来存储Category中添加的所有实例方法;
  • _OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test用来存储Category中添加的所有类方法;
2.2.2、一些属性的实现

.cpp文件找到关于Category中添加的属性的关键代码:

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_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,//2 个属性
    {{"nickName","T@\"NSString\",&,N"}},
    {"age","T@\"NSString\",&,N"}}
};

我们可以看到在Category中添加的属性被存储在属性列表_OBJC_$_PROP_LIST_SuperModel_$_Test中。
Category中添加了两个属性,但是在上节中没有发现关于属性 agesetget 方法。也就是说:Category中添加的属性系统并不会动态的创建setget 方法,需要开发者去实现setget 方法

2.2.3、一些协议的实现

.cpp文件找到关于Category中实现的协议方法的关键代码:

//实现的 ObjectDelegate 协议的方法列表
static struct{
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;//实现的方法数量
    struct _objc_method method_list[1];//实现的方法列表
} _OBJC_PROTOCOL_INSTANCE_METHODS_ObjectDelegate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"logObjectInfo", "v16@0:8", 0}}
};

//表示协议的结构体 _protocol_t
struct _protocol_t {
    void * isa;  // NULL
    const char *protocol_name;//协议名称
    const struct _protocol_list_t * protocol_list; //遵循的父协议
    const struct method_list_t *instance_methods;//必须实现的实例方法列表
    const struct method_list_t *class_methods;//必须实现的类方法列表
    const struct method_list_t *optionalInstanceMethods;//可选的实例方法列表
    const struct method_list_t *optionalClassMethods;//可选的类方法列表
    const struct _prop_list_t * properties;//属性列表
    const unsigned int size;  // 该结构体实例的内存大小
    const unsigned int flags;  // = 0
    const char ** extendedMethodTypes;
};

//ObjectDelegate 协议的结构体实例
struct _protocol_t _OBJC_PROTOCOL_ObjectDelegate __attribute__ ((used)) = {
    0,
    "ObjectDelegate",//协议名称
    (const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_ObjectDelegate,//遵循的父协议
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_ObjectDelegate,//必须实现的实例方法列表
    0,//必须实现的类方法列表
    0,//可选的实例方法列表
    0,//可选的类方法列表
    0,//属性列表
    sizeof(_protocol_t),// 该结构体实例的内存大小
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_ObjectDelegate
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_ObjectDelegate = &_OBJC_PROTOCOL_ObjectDelegate;

/* 实现的协议列表。
 * 注意:不是实现的协议方法列表
 */
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_ObjectDelegate//实现的 ObjectDelegate 协议
};

关于协议 _protocol_t并不是本章研究的内容,在这里我们只是观察下协议 _protocol_t是如何添加到 Category 的:

  • Category中实现的协议,生成一个个_protocol_t结构体实例:如实现的协议ObjectDelegate转为结构体实例_OBJC_PROTOCOL_ObjectDelegate
  • 实现的协议列表存储着实现的一个个协议:如 协议列表_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test存储实现的协议ObjectDelegate的内存地址;

2.2.4、Category的结构体实例

截止到目前,我们已经研究了Category中添加实例方法、类方法,实现某些协议,添加属性的过程;接下来研究Category的结构体实例:

//分类转为结构体:对结构体赋值
static struct _category_t _OBJC_$_CATEGORY_SuperModel_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "SuperModel",//分类所属的类的名称
    0, // &OBJC_CLASS_$_SuperModel, //分类所属的类
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test,//分类里添加的实例方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test,//分类添加的类方法列表
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test,//分类实现的协议列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SuperModel_$_Test,//分类添加的属性列表
};

static void OBJC_CATEGORY_SETUP_$_SuperModel_$_Test(void ) {
    _OBJC_$_CATEGORY_SuperModel_$_Test.cls = &OBJC_CLASS_$_SuperModel;
}

__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
    (void *)&OBJC_CATEGORY_SETUP_$_SuperModel_$_Test,
};

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_SuperModel_$_Test,
};

我们可以在结构体实例 _OBJC_$_CATEGORY_SuperModel_$_Test的结构成员分别是前文分析的 :

  • 实例方法列表地址:_OBJC_$_CATEGORY_INSTANCE_METHODS_SuperModel_$_Test
  • 类方法列表:_OBJC_$_CATEGORY_CLASS_METHODS_SuperModel_$_Test
  • 实现的协议列表:_OBJC_CATEGORY_PROTOCOLS_$_SuperModel_$_Test
  • 属性列表:_OBJC_$_PROP_LIST_SuperModel_$_Test

此时 .cpp文件研究完毕,我们清楚的了解 Category是如何实现它所添加的方法、属性、协议的!但是我们的问题并没有找到答案:系统是如何加载Category中的方法、属性、协议?

接下来去 Runtime源码 查询答案。

3、Category如何加载 ?

load方法调用栈.png
3.1、Runtime 的入口函数

Objective-C 的运行是依赖于 Runtime 的:在 Runtime 库objc-os.mm文件找到入口函数如下:

void _objc_init(void){
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    environ_init();//环境初始化,读取影响运行时的环境变量; 如果需要,还可以打印环境变量帮助。
    tls_init();//初始化线程存储的键
    static_init();//运行 C++ 静态构造函数
    lock_init();// 锁的初始化
    exception_init();//初始化 libobjc 的异常处理系统

    /* 注册dyld事件的监听:
     * 注册 unmap_image,以防某些 +load 取消映射
     * map_images 函数是初始化的关键,内部完成了大量 Runtime 环境的初始化操作。
     */
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

Runtime 的入口函数 _objc_init()主要功能:

  • 1、环境初始化,读取影响运行时的环境变量; 如果需要,还可以打印环境变量帮助;
  • 2、初始化线程存储的键 _objc_pthread_key
  • 3、运行 C++ 静态构造函数;
  • 4、锁的初始化
  • 5、初始化 libobjc 的异常处理系统;
  • 6、通过 dyld 调用 map_images() 函数,load_images() 函数,unmap_image() 函数

Category被附加到类上是dyld通过_dyld_objc_notify_register()调用 map_images()时发生的;
我们去看下map_images()函数:

/* 处理被映射到 dyld 的指定镜像;
 * 在加锁后,将任务交给 map_images_nolock() 函数
 */
void map_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[]){
    mutex_locker_t lock(runtimeLock);
    //加了个锁,然后调用 map_images_nolock() 函数
    return map_images_nolock(count, paths, mhdrs);
}

/* 处理被映射到 dyld 的一些镜像 mhdrs[],主要功能:
 * 1、首次调用,初始化共享缓存
 * 2、统计所有的 header_info ,统计所有的 class 数量,未优化的 class 数量;
 * 3、首次调用,注册内部使用的选择器,初始化自动释放池与哈希表
 * 4、第 2 步获取的信息,调用 _read_images() 函数来处理
 */
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]){
      //...代码省略
      _read_images(hList, hCount);
      //...代码省略
}

map_images_nolock() 函数根据入参 mhdrs[]统计所有的header_info ,统计所有的 class 数量,未优化的 class 数量;然后调用_read_images() 函数去处理统计出来的信息。

3.2、_read_images() 函数
void _read_images(header_info **hList, uint32_t hCount){
     //....以上代码省略

    // 发现类别。
    for (EACH_HEADER) {
        category_t **catlist =  _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category的目标类丢失了(可能是弱链接的)。
                //推翻对此 Category 的任何了解。
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with  missing weak-linked target class",  cat->name, cat);
                }
                continue;
            }

            // 这个类别的过程:首先,向其目标类注册类别。
            // 然后,如果实现了类,则重建类的方法列表。
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols   ||  cat->instanceProperties)  {
                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  ) {
                /* addUnattachedCategoryForClass() 为类添加独立的类别
                 * 该函数会对 Class 和 Category 做一个映射关联
                 */
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    
                    /* remethodizeClass() 会把 Category 中的方法列表加到 Class 的methed_list_t里面去;
                     * 而且是插入到Class方法列表的前面
                     * 这就是 Category 中重写主类的方法导致的方法覆盖的原因
                     */
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");

    // Category 必须是最后一个查找,以避免潜在的竞争
    // 当其他线程在此线程完成修复之前调用新类别代码时:
    // +load handled by prepare_load_methods()

    //....以下代码省略
}

参考文章
Mach-O文件结构
深入理解Objective-C:Category

相关文章

网友评论

      本文标题:聊聊分类 Category

      本文链接:https://www.haomeiwen.com/subject/atmwrqtx.html