【iOS底层实验室】isKindOfClass源码中的坑

720 阅读3分钟

Hi 👋

My apps

-扫雷Elic 无尽天梯梦见账本
类型游戏财务
AppStoreElicUmemi

思考一下,下面的代码会输出什么?

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

NSLog(@"Class\n %hhd \n %hhd \n %hhd \n %hhd", result1, result2, result3, result4);

BOOL result5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL result6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL result7 = [[RYModel alloc] isKindOfClass:[RYModel class]];
BOOL result8 = [[RYModel alloc] isMemberOfClass:[RYModel class]];

NSLog(@"Instacne\n %hhd \n %hhd \n %hhd \n %hhd", result5, result6, result7, result8);

结果:

Class
 1 
 0 
 0 
 0
Instacne
 1 
 1 
 1 
 1

如果仅从字面上去理解代码,那么你的答案应该会是不一样的的。我们从源码层面来理解一下吧,源码不长,很容易理解。

一、isKindOfClass

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

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

1.1 流程图-Class

Class-isKindOfClass.png

  • [NSObject class] 即 RootClass
    • RootClass -(ISA)> RootMetaClass -(superClass)> RootClass
    • true
  • [RYModel class]
    • [RYModel class] -(ISA)> RYModelMetaClass -(superClass)> RootMetaClass -(superClass)> RootClass -(superClass)> nil
    • false

1.2 流程图-Instance

Instance-isKindeOfClass.png

  • NSObjectInstance
    • NSObjectInstance -(class)> RootClass
    • true
  • RYModelInstance
    • RYModelInstance -(class)> RYModelClass
    • true

二、isMemberOfClass

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

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

这里就很简单了,就不展开说了。

三、你以为这就完了?

如果你看到这里觉得没啥问题?那就有问题了。。。

我们运行下OC源码,两个断点。

IsKindOfClass01.png

运行之后发现 +/- (Bool)isKindOfClass:(Class)cls 居然根本没有调用。

3.1 汇编

通过断点汇编,我们在调用 isKindOfClass 的地方发现了这样的指令 objc_opt_isKindOfClass

->  0x10000381c <+332>: mov    rdi, qword ptr [rip + 0x4f7d] ; (void *)0x0000000100008808: RYModel
    0x100003823 <+339>: call   0x100003b42               ; symbol stub for: objc_opt_class
    0x100003828 <+344>: mov    rbx, rax
    0x10000382b <+347>: mov    rdi, qword ptr [rip + 0x4f6e] ; (void *)0x0000000100008808: RYModel
    0x100003832 <+354>: call   0x100003b42               ; symbol stub for: objc_opt_class
    0x100003837 <+359>: mov    rdi, rbx
    0x10000383a <+362>: mov    rsi, rax
    0x10000383d <+365>: call   0x100003b48               ; symbol stub for: objc_opt_isKindOfClass

3.2 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->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    // 这里可以忽略
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

在这里下断点,我们发现的确进到了这里。并且 ClassInstance 的方法都走到了这里。

IsKindOfClass02.png

编译器对这里进行的优化,用更高效的 objc_opt_isKindOfClass 代替了原有的方法。

补充:slowpath & fastpath

在日常阅读OC源码的过程中经常会遇到这两个宏定义。它是什么意思呢?

//x很可能为真, fastpath 可以简称为 真值判断
#define fastpath(x) (__builtin_expect(bool(x), 1)) 
//x很可能为假,slowpath 可以简称为 假值判断
#define slowpath(x) (__builtin_expect(bool(x), 0)) 

其中的 __builtin_expect 指令是由 gcc 引入的

  • 目的:编译器可以对代码进行优化,以减少指令跳转带来的性能下降。即性能优化
  • 作用:允许程序员将最有可能执行的分支告诉编译器。
  • 指令的写法为:__builtin_expect(EXP, N) 。表示 EXP==N的概率很大。
  • fastpath 定义中 __builtin_expect((x),1) 表示 x 的值为真的可能性更大;即 执行if 里面语句的机会更大
  • slowpath 定义中的 __builtin_expect((x),0) 表示 x 的值为假的可能性更大。即执行 else 里面语句的机会更大
  • 在日常的开发中,也可以通过设置来优化编译器,达到性能优化的目的,设置的路径为:Build Setting --> Optimization Level --> Debug --> 将None 改为 fastest 或者 smallest