問題?
網(wǎng)上很多關于[self class] 和 [super class] 的討論,討論問題的焦點是為什么[self class] 和 [super class] 輸出的結果是一樣的,即都在Son類中輸出的話,都輸出Son。網(wǎng)上大多都很雷同,點都說到了但沒有指明關鍵點,怎么自己一步一步的去找到問題的根源,只是填鴨式的告訴讀者。感覺很??...
解決方案(一步步找到問題)
第一步 創(chuàng)建兩個類Person和Son(情景重現(xiàn))
#import <Foundation/Foundation.h>
@interface Parent : NSObject
@end
@implementation Parent
@end
@interface Son : Parent
@end
@implementation Son
- (instancetype)init{
if (self = [super init]) {
NSLog(@"self: %@",[self class]);
NSLog(@"super: %@",[super class]);
}
return self;
}
@end
上述代碼調(diào)用Son 類的 init 方法后,輸出如下
2018-02-12 11:11:19.984281 testProject[434:111502] self: Son
2018-02-12 11:11:19.984387 testProject[434:111502] super: Son
第二步 我們可以看一下代碼真實調(diào)用情況....
看第二步之前先了解一下self和super的區(qū)別
-
self是當前方法的調(diào)用者,是方法的隱藏參數(shù),方法的隱藏參數(shù)還有一個_cmd參數(shù),可以在調(diào)試的時候看到。
image.png
如果是類方法:代表當前類
如果是對象方法:代表當前類的對象
-
super是編譯器指令
把上述代碼寫到一個文件中,命名為Parent.m文件,放到桌面的clang文件夾中

打開終端,執(zhí)行命令
- 先cd到
Parent.m目錄中 - 在執(zhí)行
clang -rewrite-objc Parent.m命令
image.png
這樣在clang目錄中可以看到多了一個Parent.cpp文件,*.cpp文件是clang命令編譯Parent.m文件的輸出(相當于我們Xcode的編譯操作)。 - 打開
Parent.cpp文件,我們看到有2段代碼是我們關注的
......省略的代碼......
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) {}
};
......省略的代碼......
//這個地方就是我們Son類的init方法
static instancetype _I_Son_init(Son * self, SEL _cmd) {
if (self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init"))) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_12a896_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_12a896_mi_1,((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class")));
}
return self;
}
......省略的代碼......
第三步 我們分析一下這塊代碼
先分析一下如下代碼,只看 [self class] 和 [super class] 塊代碼
// [self class] 等價于下面代碼
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))
// [super class] 等價于下面代碼
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))
// [self class] 和 [super class] 代碼聲明區(qū)別如下
//[self class]
objc_msgSend(id, SEL)
//[super class]
objc_msgSendSuper(__rw_objc_super *, SEL)
解釋一下區(qū)別:
1. [self class]代碼底層是objc_msgSend(id, SEL),
[super class]代碼底層是objc_msgSendSuper(__rw_objc_super *, SEL)
2. SEL 是方法選擇器 都是 - (Class)class 方法
3. 它們的方法名字不同
4. 第一個參數(shù)不同,objc_msgSend的第一個參數(shù)是id,
objc_msgSendSuper的第一個參數(shù)是 __rw_objc_super 是一個結構體,如下
struct __rw_objc_super {
struct objc_object *object; //當前對象,是self即Son類
struct objc_object *superClass; //當前對象的父類,即Parent
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
// [self class] 和 [super class] 代碼實現(xiàn)區(qū)別如下
//[self class]
objc_msgSend(
(id)self, //當前類,即Son類
sel_registerName("class")//從self類中開始查找class方法
)
//[super class]
objc_msgSendSuper(
(__rw_objc_super){
(id)self, //還是這個self,這個和上面objc_msgSend中的self是一樣的,都是Son這個類
(id)class_getSuperclass(objc_getClass("Son"))//從class_getSuperclass(objc_getClass("Son")類(即Person類)中開始查找class方法
},
sel_registerName("class")//查找的方法
)
從上述分析中可以看出
[self class] 和 [super class] 的區(qū)別也就是
objc_msgSend(id, SEL) 和 objc_msgSendSuper(__rw_objc_super *, SEL)的區(qū)別:
a. objc_msgSend查找class方法的起始位置是當前類即Son。
b. objc_msgSendSuper查找class方法的起始位置是class_getSuperclass(objc_getClass("Son"))即Person類,
最終objc_msgSendSuper也會轉(zhuǎn)為objc_msgSend( (id)self, sel_registerName("class"))方式
- 只是查找調(diào)用class方法的起始位置不同而已,最終調(diào)用方式是一樣的,這個是第一個關鍵點
說的在明白一點就是:
首先你要知道 Son繼承Parent,Parent繼承NSObject類
[self class]調(diào)用Son類中的class方法,找到class方法后調(diào)用即可。
如果沒有找到class方法后,開始向父類中查找即Parent類,如果還沒有找到的話,開始向NSObject類中找,
當然class方法在Son類沒有實現(xiàn),所以此處調(diào)用的是NSObject類中的class方法
class 查找順序是Son -> Parent -> NSObject 中的class方法
[super class]調(diào)用Parent類中的class方法,找到class方法后調(diào)用即可。
如果沒有找到class方法后,開始向父類中查找即NSObject類,
當然class方法在Parent類也沒有實現(xiàn),所以此處調(diào)用的也是NSObject類中的class方法
class 查找順序是 Parent -> NSObject 中的class方法
既然都是在NSObject中找到的方法,那為什么輸出的都是Son呢?
網(wǎng)上的其他文章看了半天還是不明白為什么輸出的都是Son,其他文章只說了上面的一個關鍵點即【只是他們查找方法的起始位置不同,最終調(diào)用方式是一樣的】
最終都是執(zhí)行objc_msgSend( (id)self, sel_registerName("class"))
還有一個關鍵點在NSObject的class的方法的實現(xiàn),如下
-(Class)class {
return object_getClass(self); //這個self是Son類的對象
}
[self class] 和 [super class] 最終都執(zhí)行
objc_msgSend( (id)self, sel_registerName("class")) 發(fā)送消息
其中self對象都是(即Son類),NSObject中的class方法需要一個參數(shù)self,而此時的self就是Son類,
所以[self class] 和 [super class] 輸出的都是Son
總結:
-
[self method]和[super method]的不同點是開始查找method的方法的起始位置不同. - 最終都是執(zhí)行
objc_msgSend( (id)self, sel_registerName("class"))這個,只是本例中有一個特殊點即NSObject類的方法class需要一個參數(shù),該參數(shù)就是調(diào)用這個方法的對象(即 self)。
-(Class)class {
return object_getClass(self); //這個self是Son類的對象
}
可以寫一個demo 加以驗證,代碼如下:
#import <Foundation/Foundation.h>
@interface Parent : NSObject
- (void)run;
@end
@implementation Parent
- (void)run{
NSLog(@"Parent: run,當前self是:%@", NSStringFromClass([self class]));
}
@end
@interface Son : Parent
- (void)run;
@end
@implementation Son
- (void)run{
NSLog(@"Son: run,當前self是:%@", NSStringFromClass([self class]));
}
- (instancetype)init{
if (self = [super init]) {
[self run];
[super run];
}
return self;
}
@end
最終輸出結果:
2018-02-12 14:56:23.916168 testProject[451:136266] Son: run,當前self是:Son
2018-02-12 14:56:23.916250 testProject[451:136266] Parent: run,當前self是:Son
還是執(zhí)行上面的clang命令,可以看到如下編譯后的源碼
// Parent.m 類的run編譯
static void _I_Parent_run(Parent * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_e1bd12_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}
// Son.m 類的run編譯
static void _I_Son_run(Son * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_e1bd12_mi_1, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}
// Son.m 類的init編譯
static instancetype _I_Son_init(Son * self, SEL _cmd) {
if (self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init"))) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("run"));
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("run"));
}
return self;
}
上述代碼驗證了
- 最終都會執(zhí)行objc_msgSend((id)self, sel_registerName("run")) 且self都是Son對象
- 查找的位置不同,
[self run]執(zhí)行了Son類的run方法并輸出內(nèi)容,[super run]執(zhí)行了Parent類的run方法并輸出內(nèi)容

以上內(nèi)容如果你感覺有問題的話,可以評論交流...

