“大师,今天小师妹从一本古籍中发现一些秘术,并以此问了我一个问题,可我并不知道,这下要在小师妹那里丢人了..”
“少侠莫慌,先且把题目示于老夫。”
“题目是这样”
在一个继承于NSObject的类中,调用[self class]
和[super class]
,结果分别是什么?
@interface Person : NSObject
@end
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"Person self: %@", [self class]);
NSLog(@"Person super: %@", [super class]);
}
return self;
}
@end
“少侠,看好了,答案是这样。”
Person self: Person
Person super: Person
“大师,为什么是这样呢,[super class]
不应该打印出NSObject
吗?”
“少侠,且听老夫细细道来。”
使用clang
编译器将上面使用的OC文件,编译成C++文件,来看一下源码。
可以使用命令:
- clang -rewrite-objc filename.m
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc filename.m -o output.cpp
第二种方法过滤了平台以及CPU架构,所以编译成C++文件之后,代码量会更少一些。
在编译之后的C++文件中,找到init方法。
static instancetype _I_Person_init(Person * self, SEL _cmd) {
self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9_w2_m5ffn49j8y3805_0sq6bm0000gn_T_Person_29eaed_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9_w2_m5ffn49j8y3805_0sq6bm0000gn_T_Person_29eaed_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("class")));
}
return self;
}
在这里面,找到了两个重要的方法。
[super class]
和[self class]
的不同,就在于objc_msgSend
和objc_msgSendSuper
的不同。
接下来,我们打开runtime
的源码,查看一下这两个方法做了什么。runtime
的源码在这里下载
下面来看一下这两个方法:
id objc_msgSend(id self, SEL op, ...)
self
A pointer that points to the instance of the class that is to receive the message.
用来接收消息的当前类的实例
op
The selector of the method that handles the message.
当前方法的selector
...
A variable argument list containing the arguments to the method.
self是当前类的一个实例,用来接收方法。
op是方法,在此例中是class
方法。
id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
Sends a message with a simple return value to the superclass of an instance of a class.
super
A pointer to an objc_super data structure. Pass values identifying the
context the message was sent to, including the instance of the class that is to receive the message and the superclass at which to start searching for the method implementation.
一个指向objc_super的指针
op
A pointer of type SEL. Pass the selector of the method that will handle the message.
当前方法的selector
...
A variable argument list containing the arguments to the method.
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.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
简化一下就是:
struct objc_super {
id receiver;
Class super_class;
};
objc_super
结构体里面,包含了当前类的对象作为消息接收者,super_class
代表了从父类开始寻找方法。
至此,self
、super
这两个的区别已经明了:
-
[self msg]
从当前类中开始寻找方法,寻找不到父类中寻找。 -
[super msg]
从父类中开始寻找方法。 -
[self msg]
和[super msg]
中的消息接收者,均为当前类的对象。
在此例中,[receiver class]
方法,在NSObject
类中,所以从Person
中寻找和从NSObject
中寻找的结果一样,消息的接收者又都是Person
的实例,所以产生了开头的结果。
另从编译之后的代码中,可以看到方法都自带两个隐藏的参数,一个是self,一个是cmd。
self代表当前类的对象
cmd代表当前方法的selector
cmd可以有以下作用:
- 打印当前方法
- (void)test
{
NSLog(@"call: %@", NSStringFromSelector(_cmd));
}
打印出:
call: test
- 在给category绑定属性时,可以用_cmd来作为唯一的key,因为selector是一个唯一的字符串
- (CustomNavigationControllerDelegate *)customDelegate
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCustomDelegate:(CustomNavigationControllerDelegate *)customDelegate
{
objc_setAssociatedObject(self, @selector(customDelegate), customDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
为什么getter方法里面用_cmd,但是setter方法里用@selector(customDelegate)
,因为_cmd代表的是当前方法,在getter里面时和在setter里面时,是不一样的。作为key,两个方法里一样。使用上面的方法就确保了两个地方的key一样。
“以上就是题目的前因后果,以及默认参数的使用方法。”
“大师一番话让小生豁然开朗,多谢大师。”
“少侠快去找小师妹去罢。”