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

2020-09-15  本文已影响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

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

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验证

(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个实例方法就这样赤裸裸的暴露在了眼前!同样也证实了类对象的方法列表中确实只存在实例方法。

(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

总结

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

上一篇 下一篇

猜你喜欢

热点阅读