美文网首页
5.iOS底层学习之isKindOfClass和isMember

5.iOS底层学习之isKindOfClass和isMember

作者: 牛牛大王奥利给 | 来源:发表于2021-07-12 20:30 被阅读0次

isKindOfClass和isMemberOfClass的区别

学习底层的过程中,苦细老师聊到这两个的区别,因为开发过程中经常使用,然后我并不知道到底啥区别,所以打算探索下。

可以先思考一下这个经典面试题,以下代码的输出分别是什么?

  @interface LGPerson : NSObject

    BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL result2 = [[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL result3 = [[LGPerson class] isKindOfClass:[LGPerson class]];
    BOOL result4 = [[LGPerson class] isMemberOfClass:[LGPerson class]];
    BOOL result5 = [[LGPerson class] isKindOfClass:[NSObject class]];

(后面截图有答案,想要自己思考的话慎翻哦!)

首先我们先从文档上来了解下相关解释。

isKindOfClass:
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.
Required.

翻译一下大概的意思就是:返回一个布尔值,这个布尔值表示的是接收的实例对象的类是否继承自第二个参数的那个类。

isMemberOfClass:
Returns a Boolean value that indicates whether the receiver is an instance of a given class.
Required.

这个翻译一下大概的意思就是:返回一个布尔值,这个布尔值表示的是接收的实例对象是否是第二个参数的类的实例对象。

照着文档来做的解释我感觉,嗯,应该都打印1才对,然而事实并非如此!
来看下真相:


491624550231_.pic_hd.jpg

打印竟然是:1,0,0,0,1。
我还是不敢相信,所以我打算探究一下,通过符号断点和源码结合的方式。一开始我通过control+stepinto然后找到了"objc_opt_class",但是在源码里搜了一下只有三处有,并且这个方法返回的是一个Class,所以他不太像我要找的iskindof方法,这个方法返回的是布尔类型。于是我又通过汇编的方式看到了isKindOfClass走的方法如下:

0x100003ef0 <+0>:  pushq  %rbp
    0x100003ef1 <+1>:  movq   %rsp, %rbp
    0x100003ef4 <+4>:  subq   $0x30, %rsp
    0x100003ef8 <+8>:  movl   $0x0, -0x4(%rbp)
    0x100003eff <+15>: movl   %edi, -0x8(%rbp)
    0x100003f02 <+18>: movq   %rsi, -0x10(%rbp)
    0x100003f06 <+22>: callq  0x100003f58               ; symbol stub for: objc_autoreleasePoolPush
    0x100003f0b <+27>: movq   0x419e(%rip), %rcx        ; (void *)0x00007fff88952cc8: NSObject
    0x100003f12 <+34>: movq   %rcx, %rdi
    0x100003f15 <+37>: movq   %rax, -0x20(%rbp)
    0x100003f19 <+41>: callq  0x100003f5e               ; symbol stub for: objc_opt_class
    0x100003f1e <+46>: movq   0x418b(%rip), %rcx        ; (void *)0x00007fff88952cc8: NSObject
    0x100003f25 <+53>: movq   %rcx, %rdi
    0x100003f28 <+56>: movq   %rax, -0x28(%rbp)
    0x100003f2c <+60>: callq  0x100003f5e               ; symbol stub for: objc_opt_class
    0x100003f31 <+65>: movq   -0x28(%rbp), %rdi
    0x100003f35 <+69>: movq   %rax, %rsi
    0x100003f38 <+72>: callq  0x100003f64               ; symbol stub for: 
<font color=#FF0000>  objc_opt_isKindOfClass </font>   
    0x100003f3d <+77>: movb   %al, -0x11(%rbp)
    0x100003f40 <+80>: movq   -0x20(%rbp), %rdi
->  0x100003f44 <+84>: callq  0x100003f52               ; symbol stub for: objc_autoreleasePoolPop
    0x100003f49 <+89>: xorl   %eax, %eax
    0x100003f4b <+91>: addq   $0x30, %rsp
    0x100003f4f <+95>: popq   %rbp
    0x100003f50 <+96>: retq   

所以我们在源码里找到方法objc_opt_isKindOfClass,我们来深入的了解下isKindOfClass到底做了什么。

isKindOfClass(objc_opt_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->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

我们来尽量逐行分析下😂
首先看下有个预处理,我们现在基本都是objc2的环境所以会走#if里面的内容。
if (slowpath(!obj)) return NO; 这个关键是slowpath这个东西看不懂。点进去看一下定义如下:

#define slowpath(x) (__builtin_expect(bool(x), 0))

然后又看到了一个并不是很懂的这个__builtin_expect,我就去网上查了一下:

这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。
意思是:EXP==N的概率很大。

所以__builtin_expect(bool(x), 0)这个意思就是:bool(x) == 0的概率很大,也就是x为0的概率大,所以其实这句的意思是一个判空,就返回NO。

然后是 Class cls = obj->getIsa(); getIsa 这个的实现如下:

#define fastpath(x) (__builtin_expect(bool(x), 1))

objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA(); 
// 同理上面已经讲过了这个builtin的意义,所以 !isTaggedPointer()为1的概率很大,也就是说isTaggedPointer()这个为0的时候 那么直接返回 ISA(),查看ISA()的实现可以了解到也就是当前的class。

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; //ptr与上一个值还等于那个值 的时候说明ptr为1 的时候是 true 否则为false ,我理解这里其实还是一个判空的操作
}

所以这句
Class cls = obj->getIsa(); 应该就是获取当前class;
然后下面是一个if判断:
if (fastpath(!cls->hasCustomCore())) ,通过上述的描述说明这句极可能为1就是会走if里面的逻辑。那么重点来了,if里面的内容:

 for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
       if (tcls == otherClass) return YES;
  }
   return NO;

所以这段是会找到当前类的superClass然后和传进来的otherClass进行比较,如果otherClass是当前class的父类那么返回真,会循环走完当前的类的所有父类,如果都没有查到那么会返回NO。然后通过调试我们发现这个方法“ objc_opt_class”是[NSObject class]所走的方法,具体实现如下:

Class objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}

通过这段源码可以了解到[xxx class]会走上述这个方法,拿到自己的元类,通过断点调试也可以看到这个方法会走的,我们了解到这些分析后再来看上面那个关于isKindOfClass的例子:

    BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL result3 = [[LGPerson class] isKindOfClass:[LGPerson class]];
    BOOL result5 = [[LGPerson class] isKindOfClass:[NSObject class]];

我们结合superClass和isa的走位图来看:
左边的[NSObject class] 先拿到自己的元类即rootClass的元类是根元类,右边的同理也是会走objc_opt_class这个方法,分别走完这个方法后我们看 BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)的部分,有两个参数,此时这两个参数的地址是一样的通过我的控制台打印都是:

image.png

然后第一个参数会调用 Class cls = obj->getIsa();
获取自己的isa 打印的地址是如下截屏:

image.png

通过打印可以发现nsobject的isa还是一个叫nsobject的但是地址不是一个,所以此时进入for循环的判断第一次比较的为:0x00000001086730f0和0x0000000108673140不相等,那么第一个参数去获取自己的superClass,拿到发现为0x0000000108673140再比较相等所以返回ture。
所以这个 BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];返回1

同理来推 BOOL result3 = [[LGPerson class] isKindOfClass:[LGPerson class]];
先拿到[LGPerson class]的元类都为0x0000000100008398这个,然后obj去调用isa为 0x00000001000083c0 为元类对象LGPerson,然后第一次判断不等接着循环去找父类拿到0x1086730f0这个为nsobject再循环去找父类都不可能等于LGPerson的元类LGperson所以最终走完循环,返回NO 返回0。

那么第三个BOOL result5 = [[LGPerson class] isKindOfClass:[NSObject class]];
就是通过第二次LGPerson去查找父类最终会找到nsobject所以返回1,这样就很清晰了!

过程截图参考:

image.png

以上是isKindOfClass的解析。

isMemberOfClass

我们根据调试进入isMemberOfClass的方法发现来到的是方法+ (BOOL)isMemberOfClass:(Class)cls,实现如下:

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

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

有两个方法:
-类方法是直接获取当前类的isa和当前的cls进行比较;
-实例方法是比较当前实例对象的类是不是等于右边传入的类。
不会进行循环查询调用。
所以:
BOOL result2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL result4 = [[LGPerson class] isMemberOfClass:[LGPerson class]];
我把这部分代码进行简单的转换可看到第一句的实际比较如下图:

image.png
虽然0x00000001086730f0和0x0000000108673140打印出来都是NSObject可见nsobjec的isa也叫nsobject但是已经不是一个对象了,所以返回false也就是0。

同理[LGPerson class] 这句的class也是LGPerson的isa也叫LGPerson但是和class LGPerson不是一个地址,所以也不等返回0。

相关文章

网友评论

      本文标题:5.iOS底层学习之isKindOfClass和isMember

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