ios-对象原理探索六-类的结构和isa走位

274 阅读6分钟

ios对象原理探索五

前言

本文章主要有两方面的内容:

  • 上篇文章遗留的问题解析
  • 通过两个经典面试题巩固isa走位

遗留的问题解析

上篇文章遗留了两个问题.

  1. 为什么在类中创建的成员变量,在property_list没有找到
  2. 为什么在类中创建的类方法,在method_array_t没有找到
  • 先看第一个遗留问题

在上篇文章中的class_rw_t结构体中见到了methods(),properties()的方法,也通过断点调试获取了里面的内容,接着上篇文章继续断点调试探索下ro()这个指针中的东西

继续调试

分别打印下里面地址存储的信息

继续打印:

打印baseMethodListbaseProperties

总结:

有此我们可以看出成员变量存储在ro()ivars中.因为const class_ro_t *ro() const{}

创建过程中用const修饰所以它在系统编译时就实现了,所以不能更改.

  • 第二个遗留问题

经过isa结构分析得出了一个经典的isa走位图,我们根据走位图的分析,猜测我们的类方法存在在元类中,下面验证下猜测的对不对,isa分析中得知,对象的isa指针指向类,类(类对象)的isa指向元类,元类的isa指针指向根元类,根元类的isa指针指向自己,根据这个原理验证.进入源码:

继续按照上篇文章的方法打印调试

继续调试:

只有一个方法“sayBay”

打印下元类信息:

总结:

 上面的步骤验证了类方法存在元类中,也验证了根据isa走位图猜测的类方法存储位置.

面试题(一)验证

  • 资料1:

结果:

第一组NSLog()

**知识点:

  1. isKindOfClass 源码解析(实例方法 & 类方法)

  1. isMemberOfClass 源码解析(实例方法 & 类方法)**

断点跟进:由LLVM优化断点走这边

总结:

isKindOfClass

* 类方法元类 --> 根元类 --> 根类 --> nil传入类的对比

  • 实例方法**:对象的类 --> 父类 --> 根类 --> nil传入类的对比**

isMemberOfClass

  • 类方法: 类的元类传入类 对比

  • 实例方法对象的父类传入类 对比

用上面的答案分析下执行结果:

类方法 + (BOOL)isKindOfClass:(Class)cls +(BOOL)isMemberOfClass:(Class)aClass

  • re1  

[NSObject class] 根类(NSObject)  源码中的 tcls = self->ISA(),获取的是NSObject(根元类),所以第一次for循环return false,继续for循环 tcls = tcls->superclass赋值NSObject(根元类)superclass根类(NSObject)所以return true

  • re3
同`re1`理

MRPerson vs MRPerson的元类即元类LGPerson -- 不相等

MRPerson`类` vs 元类MRPerson的父类`NSObject`即`根元类` -- 不相等 MRPerson`类` vs 根元类的父类`NSObject`即`根类` -- 不相等 MRPerson`类` vs 根类的父类即 `nil` -- 不相等
  • re2

NSObject根类(类) vs NSObject的元类即根元类 -- 不相等

  • re4

MRPerson vs 元类 -- 不相等

**实例方法  调用 **- (BOOL)isKindOfClass:(Class)cls  和- (BOOL)isMemberOfClass:(Class)aClass

  • re5
NSObject(`类对象`,即根类) vs 对象的类即NSObject根类 -- 相等
  • re7

LGPerson(类对象) vs 对象的类即LGPerson -- 相等

  • re6

NSObject(类对象,即根类) vs 对象的类即NSObject根类 -- 相等

  • re8

LGPerson(类对象) vs 对象的类即LGPerson -- 相等

面试题(二)验证

假设我们有一个MRPerson类,并且- (void)sayHello+ (void)sayHappy 都有实现

#import <Foundation/Foundation.h>
#import "MRPerson.h"
#import <objc/runtime.h>

#ifdef DEBUG
#define FYLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define FYLog(format, ...);
#endif

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

        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

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

    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

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

    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void fyIMP_classToMetaclass(Class pClass){

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

    // - (void)sayHello;
    // + (void)sayHappy;
    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);
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        MRPerson *person = [MRPerson alloc];
        Class pClass     = object_getClass(person);
        fyObjc_copyMethodList(pClass);

        fyInstanceMethod_classToMetaclass(pClass);
        fyClassMethod_classToMetaclass(pClass);
        fyIMP_classToMetaclass(pClass);
    }
    return 0;
}

打印结果:

分析结果之前先看下这两个方法的源码结构:

class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) 返回类的对象方法

调取苹果的API

其大致含义就是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL

所以对fyInstanceMethod_classToMetaclass 这个方法的调用分析

  • method1:0x100003150

  • 传入的pClassMRPerson元类 传入的pClassMRPerson类,需要去获取selName = sayHello的实例方法

  • 首先在MRPerson中查找,有前面的MRPerson类可知,是有这个实例方法的,所以会返回查找到的实例方法,所以method1的地址不为0x0

  • ****method2:****0x0

  • 传入的pClassMRPerson元类,需要去获取selName = sayHello的实例方法

  • 其查找的顺序为元类 --> 根元类 --> 根类 --> nil,直到最后也没有找到,所以class_getInstanceMethod返回NULL,其method2的地址为0x0,表示未找到

  • ****method3:****0x0

  • 传入的pClassMRPerson类,需要去获取selName = sayHello的实例方法

  • 查找顺序为MRPerson类 --> 根类 --> nil,也没有找到sayhello实例方法,返回NULL,所以method3的地址为0x0,表示未找到

  • ****method4:****0x1000030e8

  • 传入的pClassMRPerson元类,需要去获取selName = sayHello的实例方法

  • 首先在MRPerson元类中查找,发现有sayHappy的实例方法,主要是因为类对象的类方法存储在元类中,类方法在元类中是实例方法,然后返回查找到的实例方法,所以method4的地址为0x1000030e8,表示找到了指定的实例方法

class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) 返回类的类方法

cls->getMeta()

if(是元类) return 自己,因为我们类方法最终存在于元类这一层,就不用继续往下找了,防止了无限递归

所以对fyClassMethod_classToMetaclass这个方法的调用分析

  • method1 地址:0x0

  • pClassMRPerson类selNamesayHello

  • 首先判断 MRPerson类 是否是元类,此时不是,返回MRPerson的元类,然后在元类中查找 sayhello实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL

  • method2地址:0x0

  • pClassMRPerson元类selNamesayHello

  • 首先判断 MRPerson元类 是否是元类,此时,直接返回元类,然后在元类中查找 sayhello实例方法,发现并没有找到,返回NULL

  • **method3地址:**0x1000030e8

  • pClassMRPerson类selNamesayHappy

  • 首先判断 LGPerson类 是否是元类,此时不是,返回LGPerson的元类,然后在元类中查找 **sayHappy**实例方法,发现有这个实例方法,直接返回找到的实例方法

  • **method4地址:**0x1000030e8

  • pClassMRPerson元类selName 是 sayHappy

  • 首先判断 MRPerson元类 是否是元类,此时,直接返回元类,然后在元类中查找 sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法

  • getMeta()方法做元类判断,所以method4返回一次有值

class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 返回方法的实现

根据上面的源码方法流程,现查找,找不到_objc_msgForward转发

总结:

  • class_getInstanceMethod:获取实例方法,如果指定的或其父类不包含带有指定选择器的实例方法,则为NULL

  • class_getClassMethod:获取类方法,如果指定的或其父类不包含具有指定选择器的类方法,则为NULL

  • class_getMethodImplementation:获取方法具体实现,如果未查找到,则进行消息转发