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

类的结构分析中对类底层结构进行了分析,我们知道类的属性和实例方法都存储在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<unsigned long> = 4294975672
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff90cdfcd8
}

//拿到data中的方法
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      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<method_t, method_list_t, 3> = {
    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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345