前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出 Runtime(四):super 的本质

深入浅出 Runtime(四):super 的本质

作者头像
师大小海腾
发布2020-04-16 14:32:19
1.5K0
发布2020-04-16 14:32:19
举报
文章被收录于专栏:iOS 技术分享iOS 技术分享

1. objc_super 与 objc_msgSendSuper

我们先来看两个数据结构objc_superobjc_super2。 它们的区别在于第二个成员: objc_super:super_class // receiverClass 的父类 objc_super2:current_class // receiverClass(消息接收者的class对象)

代码语言:javascript
复制
// message.h(objc4)
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;  // 消息接收者
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;  // receiverClass 的父类
#endif
    /* super_class is the first class to search */
};

// objc_runtime_new.h(objc4)
struct objc_super2 {
    id receiver;  // 消息接收者
    Class current_class;  // receiverClass(消息接收者的class对象)
};

再来看两个函数objc_msgSendSuper()objc_msgSendSuper2()。 从源码来看,两个函数所接收的参数没有区别, 但是从官方注释我们可以推测,objc_msgSendSuper2()函数所接收的第一个参数应该为objc_super2而非objc_super

代码语言:javascript
复制
// message.h(objc4)
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

// objc-abi.h(objc4)
// objc_msgSendSuper2() takes the current search class, not its superclass.
id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

2. self 和 super

self

  • OC 方法都带有两个隐式参数:(id)self(SEL)_cmd
  • self 是一个对象指针,指向当前方法的调用者/消息接收者; 如果是实例方法,它就是指向当前类的实例对象; 如果是类方法,它就是指向当前类的类对象。
  • 当使用 self 调用方法的时候,底层会转换为objc_msgSend()函数的调用,通过上一篇文章可以知道,该函数会从当前消息接收者类中开始查找方法的实现。

super

  • super 是一个编译器指令;
  • 当使用 super 调用方法的时候,底层会转换为objc_msgSendSuper2()函数的调用,该函数会从当前消息接受者类的父类中开始查找方法的实现。

3. super 本质

我们通过 clang 将以下 OC 代码 转换为 C++ 代码:

代码语言:javascript
复制
    [super viewDidLoad];
代码语言:javascript
复制
    // 转换为 C++
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    // 简化
    struct objc_super arg = {
        self,
        class_getSuperclass(objc_getClass("ViewController"))
    };
    objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));

可以看到,Runtime 将super转换为objc_msgSendSuper()函数的调用,参数为objc_superSEL

LLVM & 中间代码

那么为什么前面说super会转换为objc_msgSendSuper2()函数的调用呢? 因为转成的 C++ 的实现和真正的底层实现是有差异的, LLVM编译器会将“ OC 代码”先转成“中间代码(.ll)”再转成“汇编、机器代码”,该中间代码非 C/C++。 可以使用以下命令行指令生成中间代码:clang -emit-llvm -S main.m 具体可以查看官方文档 LLVM,这里不做过多介绍。

通过汇编验证

将 ViewController.m 文件转换成汇编代码进行验证:

查看第 18 行代码即[super viewDidLoad]转换成的汇编代码

以上可以看到,[super viewDidLoad]底层实际上是转换成了objc_msgSendSuper2()函数的调用而非objc_msgSendSuper()

super 本质

  • 当使用 super 调用方法的时候,底层会转换为objc_msgSendSuper2()函数的调用,该函数接收两个参数struct objc_super2SEL
代码语言:javascript
复制
struct objc_super2 {
    id receiver;  // 消息接收者
    Class current_class;  // receiverClass
};
id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
  • objc_msgSendSuper2()函数内部会通过current_classsuperclass指针拿到它的父类,从父类开始查找方法的实现。忽略“从 receiverClass 中查找方法的过程”,对应下图就是直接从第 5 步开始。

objc_msgSendSuper2()执行流程

  • 要注意receiver消息接收者还是子类对象,而不是父类对象,只是查找方法实现的范围变了。

4. 相关面试题

Q:调用以下 init 方法的打印结果是什么?
代码语言:javascript
复制
@interface HTPerson : NSObject
@end

@interface HTStudent : HTPerson
@end

@implementation HTStudent
- (instancetype)init
{
    if (self = [super init]) {
        
        NSLog(@"[self class] = %@",[self class]);
        NSLog(@"[super class] = %@",[super class]);
        NSLog(@"[self superclass] = %@",[self superclass]);
        NSLog(@"[super superclass] = %@",[super superclass]);
        
    }
    return self;
}
@end

[self class] = HTStudent [super class] = HTStudent [self superclass] = HTPerson [super superclass] = HTPerson

classsuperclass方法的实现在 NSObject 类中,可以看到它们的返回值取决于receiver

代码语言:javascript
复制
+ (Class)class {
    return self;
}
- (Class)class {
    return object_getClass(self);
}
+ (Class)superclass {
    return self->superclass;
}
- (Class)superclass {
    return [self class]->superclass;
}

[self class]是从receiverClass开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。 [super class]是从receiverClass->superclass开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。 由于receiver相同,所以它们的返回值是一样的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. objc_super 与 objc_msgSendSuper
  • 2. self 和 super
    • self
      • super
      • 3. super 本质
        • LLVM & 中间代码
          • 通过汇编验证
            • super 本质
              • Q:调用以下 init 方法的打印结果是什么?
          • 4. 相关面试题
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档