Objective-C中self和super的区别
self和super在开发中经常遇到,最常见的场景就是 self=[super init],很明显这是两个不同的关键字,肯定是有区别的。
在写区别之前,先新建个工程,然后创建个Ghost类文件,在文件中写入以下方法并调用。
- (void)run{
NSLog(@"%@", [self class]);
NSLog(@"%@", [super class]);
NSLog(@"%@", [self superclass]);
NSLog(@"%@", [super superclass]);
}
会神奇的发现,self和super调用相同方法,输入的结果也是一样的。
为什么呢?Objective-C的底层是C/C++封装的,把上面代码转成C++代码以更好的分析。转代码之前,先把代码简化为以下,可以更方面查看。
- (void)run{
[self class];
[super class];
[self superclass];
[super superclass];
}
打开终端,进入到Ghost.m目录,然后执行以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Ghost.m -o Ghost.cpp
然后打开Ghost.cpp文件,搜索NSLog就可以找到以下代码
static void _I_Ghost_run(Ghost * self, SEL _cmd) {
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"));
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("class"));
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass"));
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("superclass"));
}
简化代码,只留下了 self和super方法调用相关内容
objc_msgSend(self, sel_registerName("class"));
objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Ghost"))},
sel_registerName("class"));
objc_msgSend(self, sel_registerName("superclass"));
objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("superclass"));
发现self的调用被转换成了objc_msgSend调用,就是常见的方法调用,这里就不多做解释。
而super调用的地方,被转换成了objc_msgSendSuper调用,在objc4-818.2源码中搜索这个方法定义找到如下
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
struct objc_super的定义如下
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
可以看出,第一个参数是实例对象,第二个参数是实例对象的父类,因为Ghost对象继承于NSObject那上面关于super调用的相关代码 就可以简化成如下
objc_msgSendSuper({self, [NSObject class]}, @selector(class));
objc_msgSendSuper({self, [NSObject class]}, @selector(superclass));
objc_msgSendSuper方法里面做了什么呢?源码中搜索下,找到各个平台的汇编代码,arm平台相关的代码如下(arm64汇编比这个复杂,整体逻辑是一样的)
ENTRY _objc_msgSendSuper
ldr r9, [r0, #CLASS] // r9 = struct super->class
CacheLookup NORMAL, _objc_msgSendSuper
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr r0, [r0, #RECEIVER] // load real receiver
bx r12 // call imp
CacheLookup2 NORMAL, _objc_msgSendSuper
// cache miss
ldr r9, [r0, #CLASS] // r9 = struct super->class
ldr r0, [r0, #RECEIVER] // load real receiver
b __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
汇编的大概意思是,r0(第一个参数 super)中取出class(这里是NSObject) 存入r9, 然后从这个class方法缓存中找方法的实现,如果找到就放入r12中,再把receiver(这里是self)放入如r0中,然后再调用方法。再下面是如果在方法缓存中没找到方法实现的话,把superclass放入r9,把receiver放入r0,然后跳转到__objc_msgSend_uncached方法,汇编代码如下
STATIC_ENTRY __objc_msgSend_uncached
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r9 is the class to search
MethodTableLookup NORMAL // returns IMP in r12
bx r12
END_ENTRY __objc_msgSend_uncached
意思是从r9中搜索方法实现,MethodTableLookup 会把方法实现放到r12寄存其中,bx r12就是方法调用。
上面逻辑简化就是,从r9中(也就是父类)搜索方法实现,搜索到放入r12中,然后调用。了解arm汇编的就会知道,在 Objective-C中r0寄存器中的对象就是这个方法的调用者。
总是,self其实就是一个指向当前调用对象的指针,在调用方法时会作为第一个参数传入objc_msgSend方法中。super是一个编译器指示符,在编译的时候会被替换为
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
并且会把当前调用对象和对象的父类放入到objc_super结构体中作为方法的第一个参数。
而objc_msgSendSuper的本质就是,从父类的方法列表中查找方法,让当前类调用。因为class和superclass的方法实现本身就在NSObject中,然后调用者其实都是self,也就是为什么输出结果都是一样的原因。