美文网首页
iOS-OC对象原理_方法归属分析

iOS-OC对象原理_方法归属分析

作者: 泽泽伐木类 | 来源:发表于2020-09-15 16:47 被阅读0次

    前言

    在开发中,我们经常定义各种各样的类方法,实例方法,但是你有思考过它们在底层是如何分布的吗?类方法和实例方法是在一起吗?如果不在一起,他们都分别存放在哪里呐?带着问题开始我们今天的探索。

    创建一个简单的类

    这里创建一个简单地ZZPerson类

    @interface ZZPerson : NSObject
    
    - (void)testInstanceMethod0;
    - (void)testInstanceMethod1;
    
    + (void)testClassMethod0;
    + (void)testClassMethod1;
    
    @end
    
    @implementation ZZPerson
    
    - (void)testInstanceMethod0
    {
        NSLog(@"我是实例方法0");
    }
    - (void)testInstanceMethod1
    {
        NSLog(@"我是实例方法1");
    }
    
    + (void)testClassMethod0
    {
        NSLog(@"我是类方法0");
    }
    
    + (void)testClassMethod1
    {
       NSLog(@"我是类方法1");
    }
    
    @end
    

    这部分代码比较简单,主要是定义了2个实例方法和2个类方法。

    通过runtime方式探索

    • 操作一:获取一个类对象的所有方法
    //获取一个类中的所有方法并输出
    void zzObjc_getMethodList(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));
            NSLog(@"\nMethod, name: %@", key);
        }
        free(methods);
    }
    

    输出结果:

    2020-09-15 15:21:17.463609+0800 ZZObjc[199:50336892] 
    Method, name: testInstanceMethod0
    2020-09-15 15:21:17.464687+0800 ZZObjc[199:50336892] 
    Method, name: testInstanceMethod1
    

    通过这个方式我们只拿到了该类对象的所有实例方法,却没有输出任何类方法。这里也无法说明类方法是否存在于当前类对象中。

    • 操作二:判断sel是否在某个类中
    void zzMethodInClass(Class pClass){
        /*
         - (void)testInstanceMethod0;
         - (void)testInstanceMethod1;
         + (void)testClassMethod0;
         + (void)testClassMethod1;
         */
        const char *className = class_getName(pClass);
        //获取pClass的元类
        Class metaClass = objc_getMetaClass(className);
        //在类对象中获取 testInstanceMethod0 实例方法
        Method method1 = class_getInstanceMethod(pClass, @selector(testInstanceMethod0));
        //在元类对象中获取 testInstanceMethod0 实例方法
        Method method2 = class_getInstanceMethod(metaClass, @selector(testInstanceMethod0));
        //在类对象中获取 testClassMethod0 实例方法
        Method method3 = class_getInstanceMethod(pClass, @selector(testClassMethod0));
        //在元类对象中获取 testClassMethod0 实例方法
        Method method4 = class_getInstanceMethod(metaClass, @selector(testClassMethod0));
        
        NSLog(@"\n%p\n%p\n%p\n%p",method1,method2,method3,method4);
    }
    

    输出结果:

    2020-09-15 15:34:46.695230+0800 KCObjc[1431:50348801] 
    method1:0x1000021d8
    method2:0x0
    method3:0x0
    method4:0x100002158
    

    这里反映出来的结论是:实例方法在类对象里,类方法在元类对象。但是这里让人疑惑的是:+ (void)testClassMethod0;明明就是一个类方法,这里确是通过Method class_getInstanceMethod(Class cls, SEL sel) 查询,为什么不是Method class_getClassMethod(Class cls, SEL sel)???
    带着疑问继续看。
    这里我们修改一下method3的获取方式:

    //    Method method3 = class_getInstanceMethod(pClass, @selector(testClassMethod0));
        Method method3 = class_getClassMethod(pClass, @selector(testClassMethod0));
    

    这里我们通过class_getClassMethod()获取method3

    2020-09-15 15:48:22.384323+0800 ZZObjc[2548:50360370] 
    method1:0x1000021d8
    method2:0x0
    method3:0x100002158
    method4:0x100002158
    

    此时method3也有值了,而且地址跟method4一样。这也说明testClassMethod0方法是唯一的。
    接下来我们看下class_getClassMethod()的源码实现:

    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    

    这里同样调用了class_getInstanceMethod (),并且将cls的元类对象传给了该方法。这里跟我们从外部先获取到当前类对象元类对象,然后再调用class_getInstanceMethod其实是一样的。这里同样也说明要获取类方法确实需要跟元类打交道

    通过LLDB验证

    • 获取ZZPerson类对象的方法列表
    (lldb) p/x ZZPerson.class
    (Class) $0 = 0x0000000100002290 ZZPerson
    

    通过指针偏移获取class_data_bits_t bits,根据类对象的内存布局,这里需要偏移32 bytes(0x20)来获取bits,即0x0000000100002290+0x20 = 0x1000022B0,并读取bits->data(),来获取该对象的具体内容

    (lldb) p (class_data_bits_t *)0x1000022B0
    (class_data_bits_t *) $1 = 0x00000001000022b0
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x00000001010811d0
    

    具体内容存贮在class_rw_t *类型指针下,读取该指针:

    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975872
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    

    读取该class_rw_t结构体内的methods()

    (lldb) p $3.methods()
    (const method_array_t) $4 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000021c8
          arrayAndFlag = 4294975944
        }
      }
    }
    (lldb) p $4.list
    (method_list_t *const) $5 = 0x00000001000021c8
    (lldb) p *$5
    (method_list_t) $6 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 2
        first = {
          name = "testInstanceMethod0"
          types = 0x0000000100000fa2 "v16@0:8"
          imp = 0x0000000100000d50 (KCObjc`-[ZZPerson testInstanceMethod0])
        }
      }
    }
    (lldb) 
    

    到此,我们可以看到当前列表的count = 2,firsttestInstanceMethod0方法信息,我们把方法全部输出:

    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "testInstanceMethod0"
      types = 0x0000000100000fa2 "v16@0:8"
      imp = 0x0000000100000d50 (KCObjc`-[ZZPerson testInstanceMethod0])
    }
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = "testInstanceMethod1"
      types = 0x0000000100000fa2 "v16@0:8"
      imp = 0x0000000100000d80 (KCObjc`-[ZZPerson testInstanceMethod1])
    }
    

    此时,2个实例方法就这样赤裸裸的暴露在了眼前!同样也证实了类对象的方法列表中确实只存在实例方法。

    • 获取 ZZPerson 元类对象的方法列表
      获取列元类对象isa
    (lldb) x/4gx ZZPerson.class
    0x100002290: 0x0000000100002268 0x0000000100333140
    0x1000022a0: 0x000000010032d440 0x0000801000000000
    

    isa中读取shiftcls信息(不清楚的移步NOPOINTER_ISA

    (lldb) p/x 0x0000000100002268 & 0x0000000ffffffff8ULL
    (unsigned long long) $1 = 0x0000000100002268
    (lldb) po $1
    ZZPerson
    (lldb) 
    

    此时,我们就拿到了元类对象,元类的本质也是objc_class结构体,所有获取信息的方式跟类对象是一样的。

    (lldb) p (class_data_bits_t *)0x100002288
    (class_data_bits_t *) $2 = 0x0000000100002288
    (lldb) p $2->data()
    (class_rw_t *) $3 = 0x000000010182e540
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975744
      }
      firstSubclass = nil
      nextSiblingClass = 0x00007fff89236c60
    }
    (lldb) p $4.methods()
    (const method_array_t) $5 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100002148
          arrayAndFlag = 4294975816
        }
      }
    }
    (lldb) p $5.list
    (method_list_t *const) $6 = 0x0000000100002148
    (lldb) p *$6
    (method_list_t) $7 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 2
        first = {
          name = "testClassMethod0"
          types = 0x0000000100000fa2 "v16@0:8"
          imp = 0x0000000100000cf0 (KCObjc`+[ZZPerson testClassMethod0])
        }
      }
    }
    (lldb) p $7.get(0)
    (method_t) $8 = {
      name = "testClassMethod0"
      types = 0x0000000100000fa2 "v16@0:8"
      imp = 0x0000000100000cf0 (KCObjc`+[ZZPerson testClassMethod0])
    }
    (lldb) p $7.get(1)
    (method_t) $9 = {
      name = "testClassMethod1"
      types = 0x0000000100000fa2 "v16@0:8"
      imp = 0x0000000100000d20 (KCObjc`+[ZZPerson testClassMethod1])
    }
    

    两个自定义的类方法就赤裸裸的呈现在了面前。

    补充

    Objective-C type encodings

    总结

    实例方法存在类对象中,类方法存在元类对象中,类方法是元类对象的实例方法。

    相关文章

      网友评论

          本文标题:iOS-OC对象原理_方法归属分析

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