通过源码认识iskindOfClass、isMemberOfClass

苹果官方描述:

1. Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.
- (BOOL)isKindOfClass:(Class)aClass;
2. Returns a Boolean value that indicates whether the receiver is an instance of a given class.
- (BOOL)isMemberOfClass:(Class)aClass;

isa、superClass走位图

在进一步探索前我们要先通过下图了解OC中的:实例对象(instance)、类对象(class)、元类对象(meta)之间的关系

image

isa简单概括为:instance的isa-> class,class的isa-> meta,meta的isa->根元类,根元类的isa->指向根元类自己
superClass简单概括为:1、class的superClass指向父类,Root class的superClass为nil;2、元类的superClass指向父元类,根元类的superClass指向Root class。

Class相关源码

结合这些Class相关的源码和isa走位图可以更加清晰的理解isKindOfClassisMemberOfClass的实现

//参数`id obj`,中的obj包含实例对象(instance)、类对象(class)、元类对象(meta)
//obj是实例对象(instance) 返回结果是类对象(class)
//obj是类对象(class)返回的结果是元类对象(meta)
//obj是元类对象(meta)返回的结果是根元类
Class object_getClass(id obj)
{
  if (obj) return obj->getIsa();
  else return Nil;
}

+ (Class)class {
  return self;
}

- (Class)class {
  return object_getClass(self);
}

+ (Class)superclass {
   return self->superclass;
}

- (Class)superclass {
  return [self class]->superclass;
}

isKindOfClass、isMemberOfClass源码实现

isKindOfClass、isMemberOfClass两个方法的参数cls可以是类对象(class)、元类对象(meta)

  • isKindOfClass:
    类方法:self可以是类对象(class)、元类对象(meta)
    1、获取类对象的isa(元类对象)给tcls
    2、如果tcls存在,判断tcls与cls是否相等,相等返回YES,不相等继续对比tcls的superclass。
    3、tcls = tcls->superclass,一直找到根元类,如果根元类与cls也不相等,则tcls = Root class(meta)->superclass,既tcls = Root class。
    4、如果Root class与cls也不相等,tcls = Root class ->superclass,既tcls = nil,退出循环,返回NO。
    通过分析发现+ (BOOL)isKindOfClass:(Class)cls的参数传入如果是除了根类的其他类对象,则实际是用元类对象和类对象比较,一定会返回NO。

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

    实例方法:self是类的实例对象,由上面的Class相关源码- (Class)class可知,[self class]的结果是实例对象的isa指向的类对象,既 self对应的类,tcls = 类对象(class)。
    如果tcls与cls不相等,则判断tcls->superclass与cls是否相等,直到tcls = Root Class-> superclass,既tcls = nil退出循环。
    通过分析发现这个循环中并没有获取到对象的元类进行比较,所以实例对象调用isKindOfClass:方法时如果参数传入的是元类,则一定返回NO。

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

1、通过对上面isKindOfClass类方法、实例方法的分析发现,由于Root class-> superclass是nil,根元类的superclass是Root class,所以不会进行死循环。
2、判断一个对象和其指定的类之间的关系:实例对象-类对象,类对象-元类,(由于跟元类的superclass指向根类,所以这是个特殊情况,可以进类对象-类对象、元类-元类)
3、验证了官方描述:isKindOfClass用来判断一个对象是否是指定类或者该类的子类的实例对象。从这里也反应了类是其元类的对象,所以称为类对象

isKindOfClass:的实际调用

当进入断点调试时发现isKindOfClass:并不会进入上面分析的源码,在isKindOfClass:断点,按住control,点击(Xcode调试step into按钮:step into instruction (hold control)),会进入汇编调用:

  object-study`objc_opt_isKindOfClass:
    ->  0x100001884 <+0>: jmpq   *0x17a6(%rip) ; (void *)0x00000001000018e4

实际调用了objc_opt_isKindOfClass,这是因为llvm编译器对isKindOfClass方法进行了优化。下面来看下objc_opt_isKindOfClass的实现:

// Calls [obj isKindOfClass]
BOOL objc_opt_isKindOfClass(id obj, Class otherClass)
{
    #if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
     }
     #endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

重点内容:

//获取isa,实例对象的isa是类对象,类对象的isa是元类。
//这里优化了+isKindOfClass:和-isKindOfClass:
//等价于+isKindOfClass:方法中的Class tcls = self->ISA()
//等价于-isKindOfClass:方法中的Class tcls = [self class]
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
    //循环继承链,对比otherClass是否在继承链上
    for (Class tcls = cls; tcls; tcls = tcls->superclass) {
        if (tcls == otherClass) return YES;
    }
    return NO;
 }

分析发现结果和我们上面对+isKindOfClass:-isKindOfClass:分析的一致

  • isMemberOfClass:
    类方法:self可以是类对象(class)、元类对象(meta)
    1、self是类对象,则self->ISA()是其元类。
    2、self是元类对象,则self->ISA()是根元类。
    那么self->ISA() == cls就是判断一个类对象或元类对象是否是指定类的实例对象。

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

    实例方法:self是类的实例对象,由上面的Class相关源码- (Class)class可知,[self class]的结果是实例对象的isa指向的类对象,既self对应的类。那么[self class] == cls就是判断实例对象是否是类对象的实例对象。

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

验证了官方描述:isMemberOfClass用来判断一个对象是否是指定类的实例对象。从这里也反应了类是其元类的对象,元类是根元类的对象,和isa走位图吻合

isKindOfClassisMemberOfClass相同点都是判断对象和其指定类的关系,不同点是isKindOfClass包含了继承关系,可以判断是否是其类的子类对象,isMemberOfClass不包含继承关系。

代码验证

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface STPerson : NSObject
@end
@implementation STPerson
@end
int main(int argc, const char * argv[]) {

@autoreleasepool {

    NSObject *rootObj = [NSObject alloc];//实例对象

    //[rootObj class]和[NSObject class]是相同的

    //Class rootClass = [NSObject class];

    Class rootClass = [rootObj class];//Root class 类对象

    Class rootMeta = object_getClass(rootClass); //根元类
    NSLog(@"【[rootObj class]=[NSObject class]】%d",[rootObj class]==[NSObject class]); //1

    NSLog(@"【rootMeta is Meta】 %d",class_isMetaClass(rootMeta));  //1

    STPerson *person = [STPerson alloc];//STPerson 实例对象

    Class personClass = [person class];//STPerson 类对象

    Class personMeta = object_getClass(personClass); //STPerson 元类

    NSLog(@"【personMeta is Meta】%d",class_isMetaClass(personMeta)); //1

    NSLog(@"***** + (BOOL)isKindOfClass:(Class)cls ******");

    NSLog(@"【Root class-Root class】    %d",[rootClass isKindOfClass:rootClass]);

    NSLog(@"【Root Meta-Root Meta】    %d",[rootMeta isKindOfClass:rootMeta]); 

    NSLog(@"【Root class-Root Meta】    %d",[rootClass isKindOfClass:rootMeta]);

    NSLog(@"【personClass-Root class】  %d",[personClass isKindOfClass:rootClass]);   

    NSLog(@"【personClass-personClass】  %d",[personClass isKindOfClass:personClass]);

    NSLog(@"【personMeta-personMeta】    %d",[personMeta isKindOfClass:personMeta]);   

    NSLog(@"【personClass-personMeta】  %d",[personClass isKindOfClass:personMeta]); 

    NSLog(@"【personMeta-personClass】  %d",[personClass isKindOfClass:personClass]); 

    //+ (BOOL)isKindOfClass:(Class)cls的打印结果是:1、1、1、1、0、0、1、0

    NSLog(@"***** -(BOOL)isKindOfClass:(Class)cls *****");

    NSLog(@"【rootObj-rootMeta】      %d",[rootObj isKindOfClass:rootMeta]);   

    NSLog(@"【rootObj-rootClass】    %d",[rootObj isKindOfClass:rootClass]);   

    NSLog(@"【person-rootMeta】      %d",[person isKindOfClass:rootMeta]);     

    NSLog(@"【person-personMeta】    %d",[person isKindOfClass:personMeta]);   

    NSLog(@"【person-personClass】    %d",[person isKindOfClass:personClass]); 

    NSLog(@"【person-rootClass】      %d",[person isKindOfClass:rootClass]); 

    //- (BOOL)isKindOfClass:(Class)cls的打印结果是:0、1、0、0、1、1

    NSLog(@"***** + (BOOL)isMemberOfClass:(Class)cls *****");

    NSLog(@"【Root Meta-Root Meta】      %d",[rootMeta isMemberOfClass:rootMeta]); 

    NSLog(@"【personMeta-Root Meta】    %d",[personMeta isMemberOfClass:rootMeta]);

    NSLog(@"【rootClass-rootClass】      %d",[rootClass isMemberOfClass:rootClass]);

    NSLog(@"【rootClass-rootMeta】      %d",[rootClass isMemberOfClass:rootMeta]);

    NSLog(@"【personClass-Root Meta】    %d",[personClass isMemberOfClass:rootMeta]);

    NSLog(@"【personClass-personMeta】  %d",[personClass isMemberOfClass:personMeta]);

    //+ (BOOL)isMemberOfClass:(Class)cls的打印结果是:1、1、0、1、0、1

    NSLog(@"***** - (BOOL)isMemberOfClass:(Class)cls *****");

    NSLog(@"【rootObj-rootMeta】  %d",[rootObj isMemberOfClass:rootMeta]);

    NSLog(@"【rootObj-rootClass】  %d",[rootObj isMemberOfClass:rootClass]);

    NSLog(@"【person-rootMeta】    %d",[person isMemberOfClass:rootMeta]);

    NSLog(@"【person-rootClass】  %d",[person isMemberOfClass:rootClass]);

    NSLog(@"【person-personClass】 %d",[person isMemberOfClass:personClass]);

    NSLog(@"【person-personMeta】  %d",[person isMemberOfClass:personMeta]);

  //- (BOOL)isMemberOfClass:(Class)cls的打印结果是:0、1、0、0、1、0

  }
  return 0;
}

如果有歧义、错误的地方,欢迎留言指出,以免误导大家。

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