iOS中的Runtime详解4(附面试题) - 底层原理总结

super的本质

我们来看一道面试题:
下列代码中Person继承自NSObjectStudent继承自Person,写出下列代码输出内容。

#import "Student.h"
@implementation Student
- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]);
        NSLog(@"[self superclass] = %@", [self superclass]);
        NSLog(@"----------------");
        NSLog(@"[super class] = %@", [super class]);
        NSLog(@"[super superclass] = %@", [super superclass]);

    }
    return self;
}
@end

打印内容

Runtime-super[6601:1536402] [self class] = Student
Runtime-super[6601:1536402] [self superclass] = Person
Runtime-super[6601:1536402] ----------------
Runtime-super[6601:1536402] [super class] = Student
Runtime-super[6601:1536402] [super superclass] = Person

可以看到,无论是self还是super调用classsuperclass的结构都是相同的。

为什么结果是相同的?super关键字在调用方法的时候底层调用流程是怎样的?

我们通过一段代码来看一下super的底层实现,为Person类提供run方法,Student类中重写run方法,方法内部调用[super run];,将Student.m转化为c++代码查看其底层实现。

- (void) run
{
    [super run];
    NSLog(@"Student...");
}

上述代码转化为c++代码

static void _I_Student_run(Student * self, SEL _cmd) {
    
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
    
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_Student_e677aa_mi_0);
}

可以发现,[super run];转化为底层源码内部其实调用的是objc_msgSendSuper函数。

objc_msgSendSuper函数内传递了两个参数,__rw_objc_super结构体和sel_registerName("run")方法名。

__rw_objc_super结构体内传入的参数是selfclass_getSuperclass(objc_getClass("Student"))也就是Student的父类Person

首先我们找到objc_msgSendSuper函数查看其内部结构

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

可以发现objc_msgSendSuper中传入的结构体是objc_super,我们通过源码查找objc_super结构体查看其内部结构。

// 精简后的objc_super结构体
struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接受者
    __unsafe_unretained _Nonnull Class super_class; // 消息接受者的父类
    /* super_class is the first class to search */ 
    // 父类是第一个开始查找的类
};

objc_super结构体中可以发现receiver消息接收者仍然为selfsuperclass仅仅是用来告知消息查找从哪一个类开始。从父类的类对象开始去查找。

我们通过一张图看一下其中的区别。

iOS中的Runtime详解4(附面试题) - 底层原理总结_第1张图片
self/super调用方法的区别

从上图中可以看到:super调用方法的消息接收者receiver仍然是self,只是从父类的类对象开始去查找方法。

那么现在再回到面试题,我们知道class的底层实现如下所示:

+ (Class)class {
    return self;
}

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

class内部实现是根据消息接收者返回其对应的类对象,最终会找到基类的方法列表中,而selfsuper的区别仅仅是self从本类类对象开始查找方法,super从父类类对象开始查找方法,因此最终得到的结构都是相同的。

另外我们回到run方法内部,很明显可以发现,如果super不是父类开始查找方法,从本来查找方法的话,就调用方法本身造成循环调用方法而crash。

同理superclass底层实现同class类似,其底层实现代码如下所示

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

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

因此得到的结果也是相同的。

objc_msgSendSuper2函数

上述OC代码转化为c++代码并不能说明super底层调用函数就一定是objc_msgSendSuper

其实super底层真正调用的函数是objc_msgSendSuper2,我们可以通过查看汇编代码来进行验证。

- (void)viewDidLoad {
    [super viewDidLoad];
}

通过断点查看其汇编调用栈

iOS中的Runtime详解4(附面试题) - 底层原理总结_第2张图片
objc_msgSendSuper2函数汇编调用

可以发现super底层其实调用的是objc_msgSendSuper2函数,我们来到源码中查找一下objc_msgSendSuper2函数的底层实现,我们可以在汇编文件中找到其相关底层实现。

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START

ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL

END_ENTRY _objc_msgSendSuper2

通过上面的汇编代码我们可以发现,其实底层是在函数内部调用的class->superclass获取父类,并不是我们上面分析的直接传入的就是父类对象。

其实_objc_msgSendSuper2内传入的结构体为objc_super2

struct objc_super2 {
    id receiver;
    Class current_class;
};

我们可以发现objc_super2中除了消息接收者receiver,另一个成员变量current_class也就是当前类对象。

与我们上面分析的不同,_objc_msgSendSuper2函数内其实传入的是当前类对象,然后在函数内部获取当前类对象的父类,并且从父类开始查找方法。

我们也可以通过代码验证上述结构体内成员变量究竟是当前类对象还是父类对象。下文中我们回通过另外一道面试题验证。

isKindOfClass与isMemberOfClass

首先看一下isKindOfClass、isMemberOfClass对象方法底层实现

- (BOOL)isMemberOfClass:(Class)cls {
   // 直接获取实例类对象并判断是否等于传入的类对象
    return [self class] == cls;
}

- (BOOL)isKindOfClass:(Class)cls {
   // 向上查询,如果找到父类对象等于传入的类对象则返回YES
   // 直到基类还不相等则返回NO
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isKindOfClass、isMemberOfClass类方法底层实现

// 判断元类对象是否等于传入的元类元类对象
// 此时self是类对象 object_getClass((id)self)就是元类
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

// 向上查找,判断元类对象是否等于传入的元类对象
// 如果找到基类还不相等则返回NO
// 注意:这里会找到基类
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

通过上述源码分析我们可以知道:
isMemberOfClass判断左边是否刚好等于右边类型
isKindOfClass判断左边或者左边类型的父类是否刚好等于右边类型
类方法内部是获取其元类对象进行比较

你可能感兴趣的:(iOS中的Runtime详解4(附面试题) - 底层原理总结)