类方法归属分析&内省分析

在类的结构分析中对类底层结构进行了分析,我们知道类的属性和实例方法都存储在class_data_bits_t类型结构体的bits中,通过地址对类和实例方法进行了查找,但是我们却没有找到类方法,这是为什么呢?

类方法查找

我们大胆猜测类方法是不是在类的元类里面呢?我们不妨根据在类的结构分析的方法再次了查找试试。如果类方法在在元类里面,那么类的isa指向了它的元类,通过isa就可以找到元类的地址,从而逐步拿到元类里面的数据。
首先我们还是创建一个类SYPerson,在SYPerson类中创建一个类方法

+ (void)goodJob;

通过lldb进行打印

(lldb) x/4gx SYPerson.class
0x100002218: 0x00000001000021f0 0x0000000100334140
0x100002228: 0x00000001006ad8c0 0x0002801000000003

//通过isa & ISA_MSAK可以查看元类信息
(lldb) p/x 0x00000001000021f0 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000021f0

//地址偏移32
(lldb) p (class_data_bits_t *)0x0000000100002210
(class_data_bits_t *) $2 = 0x0000000100002210

//获取data数据
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001006ad840

(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = 4294975672
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff90cdfcd8
}

//拿到data中的方法
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt = {
     = {
      list = 0x0000000100002100
      arrayAndFlag = 4294975744
    }
  }
}

//拿到方法列表
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002100

//读取方法,拿到我们申明的类方法goodJob
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "goodJob"
      types = 0x0000000100000fa6 "v16@0:8"
      imp = 0x0000000100000f00 (KCObjc`+[SYPerson goodJob])
    }
  }
}

通过上述操作我们如愿拿到了类方法。说明类方法确实是可以通过元类查找到的,我们这里直接上结论那就是:
得到一个类的类方法相当于得到一个元类的实例方法

通过经典面试题分析类

1.有一个LGPerson的类我们通过runtime动态获取其方法,sayHello是实例方法,sayHappy是类方法,那么下面代码会打印什么结果呢?
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif

    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);


    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));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);


    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);


打印结果

//class_getInstanceMethod
lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148

//class_getClassMethod
lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148

//class_getMethodImplementation
0x100001d00-0x7fff71c75580-0x7fff71c75580-0x100001d30

分析

  • class_getInstanceMethod(pClass, @selector(sayHello))获取类pClass名为sayHello的实例方法,显然是可以获取到的所以可以打印出地址

  • class_getInstanceMethod(metaClass, @selector(sayHello))获取pClass元类名为sayHello的实例方法,这里sayHello我们知道是实例方法不在元类中所以打印地址为0x0

  • class_getInstanceMethod(pClass, @selector(sayHappy)获取类pClass名为sayHappy的实例方法,sayHappy是类方法,那么自然找不到打印0x0

  • class_getInstanceMethod(metaClass, @selector(sayHappy))获取pClass元类名为sayHappy的实例方法,我们前面已经分析过了,类方法在元类中以实例方法形态存在,所以可以打印出其地址

  • class_getClassMethod(pClass, @selector(sayHello))获取pClass中名为sayHello的类方法,显然无法找到,因为这是实例方法,打印0x0

  • class_getClassMethod(metaClass, @selector(sayHello))pClass元类中查找sayHello类方法,同理因为是实例方法元类中也无法找到,打印0x0

  • class_getClassMethod(pClass, @selector(sayHappy))在类pClass获取sayHappy类方法,sayHappy本身就是类方法,所以自然可以找到其地址

  • class_getClassMethod(metaClass, @selector(sayHappy))pClass元类中查找sayHappy类方法,这里为什么可以打印出地址呢?因为在查找的过程中因为其方法本身是类方法直接走了cls->getMeta()并不会在元类中再去查找实例方法,而是直接返回了地址

  • class_getMethodImplementation中我们看到全部都打印出了地址,那么这个class_getMethodImplementation是什么意思呢?意思就是如果向一个类的实例发送一条消息,该函数会返回该条消息的IMP。也就是说我们去查找方法的过程中只要找到了符合的名字sel就会返回其imp

内省

2.第二题如下,打印结果如何?
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       // 1
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     // 0
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       // 0 
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     // 0
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       // 1
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     // 1
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       // 1
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     // 1
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
  • 首先我们要知道什么是内省

在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也被称作运行时类型检查,不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力

  • ios中的内省
isMemberOfClass:Class:检查对象是否是那个类但不包括继承类而实例化的对象
isKindOfClass:Class:检查对象是否是那个类或者其继承类实例化的对象
isSubClassOfClass:检查某个类对象是否是另一个类型的子类
respondToSelector:selector:检查对象是否包含这个方法
instancesRespondToSelector::判断类是否有这个方法
conformsToProtocol:是用来检查对象是否实现了指定协议类的方法

我们知道什么是内省后再分析上面这道题,我们在底层源码中找到isKindOfClassisMemberOfClass的实现如下

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

可以看出类和实例调用时里面的实现是有区别的
源码分析和题解读

  • - (BOOL)isKindOfClass:(Class)cls中初始将tcls = [self class],然后再判断tcls == cls是否成立,成立返回1,否则继续将tcls赋值为其父类再做比较,如果都不成立返回0

re5传入指向NSObjectself和为NSObjectcls比较直接返回1,re7tcls指向LGPerson类,clsLGPerson类比较返回1

  • - (BOOL)isMemberOfClass:(Class)cls中传入的cls和其self实例的类进行比较,如果相等则返回1

re6re8中分别将NSObjectLGPerson的self实例对象,将它们的所属的类和传入的NSObjectLGPersoncls比较,显然返回1

  • + (BOOL)isKindOfClass:(Class)clstcls首先赋值为self的元类,然后和传入的cls是否成立,成立返回1,否则继续将元类tcls赋值为父类再做比较,如果都不成立返回0

re1中首先将NSObject的元类和NSObject比较,显然不成立,那么接着走,tcls变成NSObject的元类的父类,那就是NSobject,所以和传入的clsNSObject是相等的,返回1

re3中首先将LGPerson元类和LGPerson比较,不相等接着走tcls指向根元类再比较,还是不相等,最后根源类tcls的父类指向NSObject类,还是不相等返回0

  • + (BOOL)isMemberOfClass:(Class)cls中将self指向的元类和传入cls比较,成立返回1,不成立返回0

re2re4中将NSObjectLGPerson的元类与传入的NSObject类和LGPerson类比较,显然不成立返回0

总结

isa的走位图太经典了,看懂isa的走位图可以解决很多问题,最后再次附上isa走位图供参考

isa流程图.png

你可能感兴趣的:(类方法归属分析&内省分析)