前言
本文涉及到的源码环境为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
类型的super
SEL
类型的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
为子类实例对象。