前言
本文涉及到的源码环境为objc4-818.2
我们在初始化方法中无数次用到了self和super,我们今天就对他们做一下研究。
- 创建类
Father继承自NSObject - 创建类
Son继承自Father
@implementation Son
-(instancetype)init{
if (self = [super init]) {
NSLog(@"1 -- %@",[self class]);
NSLog(@"2 -- %@",[super class]);
}
return self;
}
@end
输出结果
1 -- Son
2 -- Son
1打印出当前Son没有问题,那为什么2不输出Son的父类Father呢????- 为什么
[super init]返回子类实例对象而不是父类实例对象???
self
在源码环境的NSObject.mm中查看class方法的实现
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
// 我们研究普通对象
if (fastpath(!isTaggedPointer())) return ISA();
......
}
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
...
return getClass(authenticated);
}
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
...
uintptr_t clsbits = bits;
...
clsbits &= ISA_MASK;
...
return (Class)clsbits;
}
我们前文学过nonpointer对象的isa指针包含指向类对象的指针,以上过程就是从实例对象的isa取出类指针的过程。
对于class方法我们可能比较好奇他访问的self为什么不是NSObject呢??
- (Class)class {
// 这里访问的self是谁??
return object_getClass(self);
}
我们知道函数的执行在OC底层是通过objc_msgSend进行消息发送完成的,objc_msgSend有两个默认参数self和_cmd
self为消息接收者_cmd为函数名...后面为参数
id objc_msgSend(id self, SEL _cmd, ...);
同样在函数中也有默认参数self和_cmd,他们和objc_msgSend中的默认参数相对应,可简单理解为
- (Class)class(id self, SEL _cmd) {
......
}
因为我们在自定义类Father和Son中都没有实现class方法,所以消息查找流程会顺着继承链查找到NSObject中的class方法,但是self对象依然是Son的实例对象。
我们再举一个小例子,在父类Father中实现方法fatherMethod
@interface Father : NSObject
- (void)fatherMethod;
@end
@implementation Father
- (void)fatherMethod{
NSLog(@"self == %@",self);
}
@end
在子类Son中没有实现任何方法,我们用子类的实例对象来调用fatherMethod方法
int main(int argc, char * argv[]) {
@autoreleasepool {
Son *son = [[Son alloc] init];
[son fatherMethod];
}
return 0;
}
执行结果为
self == <Son: 0x28211c140>
无论方法在哪里实现,方法中访问的self都是消息接收者。
super
self是消息接收者,是消息发送函数objc_msgSend的默认参数之一。super就没那么简单了,他不是一个参数而是一个编译器的关键字。我们在Son.m中实现
@implementation Son
-(instancetype)init{
if (self = [super init]) {
NSLog(@"%@",[super class]);
}
return self;
}
@end
我们通过clang和汇编两种方式来探索其底层原理
通过clang查看其底层实现
终端进入Son.m路径执行命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Son.m
可以看到Son.cpp文件中[super class]的底层实现为
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
......
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))
消息发送是通过objc_msgSendSuper而不是objc_msgSend,消息接收者变成了__rw_objc_super结构体指针
通过汇编查看
不同的是这里执行了
objc_msgSendSuper2
源码探索
我们带着这些疑问来进入底层的世界
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
这两个函数的参数是一样的,有两个默认参数
objc_super类型的superSEL类型的op- 参数们
objc_super结构为(objc2环境)
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
备注写的清楚,方法查找时super_class是第一个查找的class
在objc-msg-arm64.s文件中搜索objc_msgSendSuper和objc_msgSendSuper2的实现
ENTRY _objc_msgSendSuper
// 跳转到L_objc_msgSendSuper2_body中继续执行
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
// 和objc_msgSend不同的是p16不是指向class而是指向superclass
ldp p0, p16, [x0]
ldr p16, [x16, #SUPERCLASS]
L_objc_msgSendSuper2_body:
CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper2
和objc_msgSend不同的是p16不是指向class而是指向superclass,但是消息接收者receiver是一样的,验证一下。
在父类Father中定义和实现方法fatherMethod和method
@interface Father : NSObject
- (void)fatherMethod;
- (void)method;
@end
@implementation Father
- (void)fatherMethod{
NSLog(@"self == %@",self);
}
- (void)method{
NSLog(@"father-method");
}
@end
在子类Son中重写fatherMethod方法并实现method方法
@interface Son : Father
- (void)method;
@end
@implementation Son
- (void)fatherMethod{
[super fatherMethod];
[super method];
}
- (void)method{
NSLog(@"son-method");
}
@end
运行之
int main(int argc, char * argv[]) {
@autoreleasepool {
Son *son = [[Son alloc] init];
[son fatherMethod];
}
return 0;
}
执行结果
self == <Son: 0x2806b8130>
father-method
[super fatherMethod]中消息接收者依然是子类Son的实例对象,所以fatherMethod中访问到的self为Son的实例对象
虽然子类Son也实现了method方法,但是[super method]是从父类Father开始查找的
小结
self调用方法[self xxx]和super调用方法[super xxx]
- 相同点为:消息接收者
receiver是一样的 - 不同点为:
[super xxx]会从父类开始消息查找流程
-(instancetype)init{
if (self = [super init]) {
......
}
return self;
}
+ (id)init {
return (id)self;
}
[super init]从父类开始查找init函数,但是消息接收者依然是子类实例对象,所以在init函数中访问并返回的self为子类实例对象。