美文网首页
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对象原理_方法归属分析

    前言 在开发中,我们经常定义各种各样的类方法,实例方法,但是你有思考过它们在底层是如何分布的吗?类方法和实例方法是...

  • iOS-OC对象原理_objc_msgSend(二)

    前言 iOS-OC对象原理_objc_msgSend(一)[https://www.jianshu.com/p/e...

  • OC底层原理 08

    类原理分析 成员变量与类方法的归属?2.成员变量与属性的区别&周边拓展补充3.machoView辅助分析4.类方法...

  • JVM学习9·对象在内存的分配

    几乎所有的对象都在堆分配 1虚拟机的优化技术 1.1逃逸分析 逃逸分析的原理:分析对象动态作用域,当一个对象在方法...

  • iOS原理探索05 -- 类方法&经典面试题分析

    类方法的归属问题 我们在iOS原理探索04--类结构的分析中知道 类的实例方法和类的属性都存在bits中,我们发现...

  • iOS-OC对象原理_字节对齐

    基本数据类型在不同操作系统下的大小 COC32位64位boolBOOL(64位)11signed char(__s...

  • iOS-OC对象原理_内存布局

    前言 本文是基于objc-781源码进行分析探索 实例对象与类对象底层结构,isa信息分布 在 中, objc.h...

  • 逃逸分析

    逃逸分析 在编程语言的编译优化原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或...

  • 关于isa走位的2道经典面试题

    第一道经典面试题:类方法的归属分析 关于类方法和实例方法的归属分析,我们首先得知道:实例方法是在类中,而类方法是在...

  • OC底层方法的本质、查找流程

    1. 前言 前面的文章了解了OC对象的本质、类的本质以及方法缓存的原理,那么这篇文章将来分析一下OC方法底层的原理...

网友评论

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

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