学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

super的本质

objc_supre与objc_msgSendSuper

objc_super 和 objc_super2

它们的区别在于第二个成员:

objc_super:super_class //receiverClass的父类
objc_super2: current_class//receiverClass(消息接收者的class对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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

1
2
3
4
5
6
// 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, ...)

self和super

self

  • OC方法都带有两个隐式参数:(id)self和(SEL)_cmd

  • self是一个对象指针,指向当前方法的调用者/消息接收者
    如果是实例方法,它就是指向当前类的实例对象;
    如果是类方法,它就指向当前类的类对象

  • 当使用self调用方法的时候,底层会转换为objc_msgSend()函数的调用,该函数会从当前消息接收类中开始查找方法的实现。

super

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

super本质

使用clang将以下OC代码转换为C++代码:

1
[super viewDidLoad]
1
2
3
4
5
6
7
8
// 转换为 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

通过汇编验证

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

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

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

源码

当使用super调用方法的时候,底层会转换为objc_msgSendSuper2()函数的调用,该函数接收两个参数struct objc_super2SEL

1
2
3
4
5
struct objc_super2 {
id receiver; // 消息接收者
Class current_class; // receiverClass
};
id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

objc_msgSendSuper2()函数内部会通过current_class的superclass指针拿到它的父类,从父类开始查找方法的实现。忽略“从 receiverClass 中查找方法的过程”,对应下图就是直接从第 5 步开始

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

几个问题

Q:调用以下 init 方法的打印结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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
1
2
3
4
[self class] = HTStudent 
[super class] = HTStudent
[self superclass] = HTPerson
[super superclass] = HTPerson

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

1
2
3
4
5
6
7
8
9
10
11
12
+ (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相同,所以它们的返回值是一样的。

参考

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