OC类方法归属分析

453 阅读3分钟

通过一个例子引入

首先创建一个LGPerson类,并定义如下:

然后看一下如下代码的执行结果

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 0x0000000100000000
        // LGTeacher *teacher = [LGTeacher alloc];

        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);

//        lgInstanceMethod_classToMetaclass(pClass);
//        lgClassMethod_classToMetaclass(pClass);
//        NSLog(@"Hello, World!");
    }
    return 0;
}

以上代码通过运行时的方式获取到了LGPerson的方法,并打印输入。

最后的打印结果如下:

为何我们在LGPerson中定义了一定实例方法sayHello,一个类方法sayHappy,最后却只打印了实例方法sayHello呢?难道类方法sayHappy不在类方法列表里面?

lldb调试分析归方法归属

通过isa指针指向和类的结构分析我们已经证明了对象方法是存储在类结构的class_data_bits_t bits->class_rw_t *data()->method_array_t methods()中,通过lldb调试,结合isa的走位和指针偏移,我们可以打印出类方法列表。

我们知道:

  • 对象的isa -> 类的isa -> 元类
  • 对象方法存储在中.

那么我们是否可以猜测类方法存储在元类中呢?答案是肯定的,下面我们来证明

第一步:到Apple open source下载781源码

第二步:将下载的源码配置成可编译objc源码

第三步:创建LGPerson类并声明和实现一个对象方法和一个类方法,并在main函数中初始化一个实例对象,运行并断点

第四步:lldb调试,根据isa走位,找到LGPerson的元类的类结构,如下

通过如上的一顿操作,就成功的打印出LGPerson的类方法sayHappy,它存储在LGPerson的元类中

** 这样我们就成功的分析出了类方法归属于元类 **

深入理解类的方法归属

  • 对象方法归属于类,类方法归属于元类;
  • 对象方法对类来说是类对象的实例方法,类方法对元类来说是元类对象的实例方法。

下面看一下如下代码的打印结果

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

说明

  • pClass 是LGPerson类;
  • metaClass 是LGPerson的元类;
  • class_getInstanceMethod 是获取当前对象的方法实现

打印结果如下:

分析:

  • sayHelloLGPerson的对象方法,归属于LGPerson,所以method1打印0x1000031b0;method2也就打印0x0;
  • 'sayHappy' 是LGPerson的类方法,归属于元类,所以method3打印0x0;而method4打印0x100003148

这里也这一步验证了实例方法归属于类方法归属于元类.

下面看看实例方法和类方法的实现的归属情况,先看如下代码:

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
  • class_getClassMethod 打印类方法是否有方法的实现

打印结果如下:

分析:

  • sayHelloLGPerson的对象方法,不是LGPerson的类方法;所以method1打印0x0;
  • sayHello和元类没有任何关系,所以method2也打印0x0;
  • sayHappyLGPerson的类方法,所以method3打印函数指针地址0x100003148;
  • 那么为什么method4也打印函数指针地址0x100003148呢?

下面我们找到 class_getClassMethod的底层实现:

可以看到class_getClassMethod底层调用的其实就是class_getInstanceMethod,并且传入的参数是cls->getMeta(),即当前类的元类。我们再来看一下getMeta的底层实现.

可以看到,当getMeta本身处理的就是元类时,直接返回就是元类自己,并且在class_getClassMethod中调用了class_getInstanceMethod,即当元类调用class_getClassMethod时,实际上就是元类在调用class_getInstanceMethod

所以method4也打印函数指针地址0x100003148

总结:得到一个类方法,其实就是得到一个元类的实例方法

思考:为什么getMeta函数中,当找到是元类时就直接返回元类自己,不再继续按照isa的走位指向继续找到根元类了呢?

来看下面的isa走位图

从图中我们可以知道,元类的isa->根元类;根元类的isa指向自己。 所以如果getMeta不作元类判断返回元类自己,那getMeta就会陷入无限次的递归。

总结:类方法归属元类,获取类方法其实就是获取元类的实例方法。