内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

Objective-C中Category的本质

2018-06-08 18:28 出处:清屏网 人气: 评论(0

写一个 Person 的分类: Person+DO

Person+DO.h 文件:

#import "Person.h"

@interface Person (DO) <NSCopying>

@property (nonatomic, assign) int number;

- (void)testInstanceMethod;

+ (void)testClassMethod;

@end

Person_DO.m 文件:

#import "Person+DO.h"

@implementation Person (DO)

- (void)testInstanceMethod{}

+ (void)testClassMethod{}

@end

在终端输入以下命令:

$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+DO.m -o Person+DO-arm64.cpp

打开 Person+DO-arm64.cpp 该文件,我们想要的都在这里面。

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;    // 属性列表
};
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_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"testInstanceMethod", "v16@0:8", (void *)_I_Person_DO_testInstanceMethod}}
};

_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO :从名字(INSTANCE_METHODS)可看出是对象方法列表结构体

testInstanceMethod :正是我们分类中定义的对象方法

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_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"testClassMethod", "v16@0:8", (void *)_C_Person_DO_testClassMethod}}
};

_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO :从名字(CLASS_METHODS)可看出是类方法列表结构体

testClassMethod :正是我们分类中定义的类方法

static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "@24@0:8^{_NSZone=}16"
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
    0,
    "NSCopying",
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
    0,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCopying
};

可看出,协议方法先通过 _method_list_t 结构体存储,之后通过 _protocol_t 结构体存储在 _OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO 中和 _protocol_list_t 结构体一一对应。

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_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"number","Ti,N"}}
};

从属性列表结构体中,发现了我们自己写的 number 属性

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

static struct _category_t _OBJC_$_CATEGORY_Person_$_DO __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_DO,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_DO,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_DO,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_DO,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_DO(void ) {
    _OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;
}

struct _class_t OBJC_CLASS_$_Person_class_t 类型的 OBJC_CLASS_$_Person 结构体

_OBJC_$_CATEGORY_Person_$_DO.cls = &OBJC_CLASS_$_Person;cls 指针指向分类的主类类对象 Person 的地址

至此,通过以上分类的源码,我们在分类中定义的 对象方法类方法属性协议 等都存放在 catagory_t 结构体中。

总结:

  1. Category的实现原理:将分类中的方法,属性,协议数据放在 category_t 结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
  2. Category中不能加属性:Category可以添加属性,但是并不会自动生成成员变量及 set/get 方法。因为 category_t 结构体中并不存在成员变量。
    进一步分析:
    成员变量是存放在实例对象中的,并且编译使就已经决定好了。而分类是在运行时才去加载的,无法在程序运行时将分类的成员变量添加到实例对象的结构体中。因此分类中不可以添加成员变量。

load 和 initialize

objc-loadmethod.mm 文件中:

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

优先调用类的 load 方法,之后调用分类的 load 方法

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

initialize 特点:

  1. 第一次使用类的时候会调用(第一次接收到消息)
  2. 调用子类的 initialize 之前,会先保证调用父类的 initialize 方法
  3. 如果之前已经调用过 initialize ,就不会再调用 initialize 方法
  4. 当分类重写 initialize 方法时会先调用分类的方法
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
// Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

load 方法通过直接拿到 load 方法地址进行调用

总结:

loadinitialize 区别:

  1. load 是根据函数地址直接调用
    initialize 是通过 objc_msgSend 调用
  2. loadruntime 加载类、分类的时候调用(只会调用1次)
    initialize 是类第一次接收到消息的时候调用,每一个类只会 initialize 一次(父类的 initialize 方法可能会被调用多次,子类未实现)
  3. 分类中 load 方法不会覆盖本类的 load 方法
    如果分类实现了 initialize 方法,就覆盖类本身的 initialize 方法
分享给小伙伴们:
本文标签: CategoryObjective-C

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号