category底层原理

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

通过查看源码可以看出,给分类可以添加实例方法,类方法,协议,属性(无法添加实例变量)。

添加分类NSPerson+Add类


#import "NSPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface NSPerson (Add)
@property (nonatomic,strong)NSString *testStr;
- (void)add;
+ (void)testAdd;
@end

#import "NSPerson+Add.h"

@implementation NSPerson (Add)

- (void)add{
}
+ (void)testAdd{
 }

@end

使用clang查看编译源码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSPerson+Add.m

//NSPerson 结构体
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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

//分类添加的实例方法列表
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_NSPeson_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"add", "v16@0:8", (void *)_I_NSPeson_Add_add}}
};

//分类添加的类方法列表
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_NSPeson_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
//方法名字
    {{(struct objc_selector *)"testAdd", "v16@0:8", (void *)_C_NSPeson_Add_testAdd}}
};

//添加的属性列表
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_NSPeson_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
//testStr属性名字
    {{"testStr","T@\"NSString\",&,N"}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSPeson;


// *********NSPerson+Add的结构体*************
static struct _category_t _OBJC_$_CATEGORY_NSPeson_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "NSPeson",//name
    0, // &OBJC_CLASS_$_NSPeson,
//实例方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSPeson_$_Add,
//类方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSPeson_$_Add,
    0,
//属性列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSPeson_$_Add,
};
static void OBJC_CATEGORY_SETUP_$_NSPeson_$_Add(void ) {
    _OBJC_$_CATEGORY_NSPeson_$_Add.cls = &OBJC_CLASS_$_NSPeson;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
    (void *)&OBJC_CATEGORY_SETUP_$_NSPeson_$_Add,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_NSPeson_$_Add,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

通过编译器生成的实例方法列表,类方法列表和属性列表都对应的赋值给添加的分类的结构体中,可以看出在编译时期,分类添加的方法和属性是放到分类本身的结构体中


// *********NSPerson+Add的结构体*************
static struct _category_t _OBJC_$_CATEGORY_NSPeson_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "NSPeson",//name
    0, // &OBJC_CLASS_$_NSPeson,
//实例方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSPeson_$_Add,
//类方法列表
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSPeson_$_Add,
    0,
//属性列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSPeson_$_Add,
};

为什么分类的方法会覆盖类的方法?
在runtime运行时,会先遍历找出所有的分类,然后在将原来的类的方法添加新的方法列表中,然后根据分类的数据扩容,在将分类的方法添加到类的类对象或元类的方法中时,使用将原来类的方法向后移,这样的话分类的方法在原来类的方法的前面,所以会覆盖到原来的类方法,(注并不真正的覆盖,而且分类的方法调用顺序靠前,在找到方法后,就不会在继续往下找)

后编译的分类,先调用

Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容