super的本質(zhì)
首先來(lái)看一道面試題。 下列代碼中Person
繼承自NSObject
贝室,Student
繼承自Person
契讲,寫(xiě)出下列代碼輸出內(nèi)容仿吞。
#import "Student.h"
@implementation Student
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"----------------");
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[super superclass] = %@", [super superclass]);
}
return self;
}
@end
直接來(lái)看一下打印內(nèi)容
Runtime-super[6601:1536402] [self class] = Student
Runtime-super[6601:1536402] [self superclass] = Person
Runtime-super[6601:1536402] ----------------
Runtime-super[6601:1536402] [super class] = Student
Runtime-super[6601:1536402] [super superclass] = Person
上述代碼中可以發(fā)現(xiàn)無(wú)論是self
還是super
調(diào)用class
或superclass
的結(jié)果都是相同的。
為什么結(jié)果是相同的捡偏?super
關(guān)鍵字在調(diào)用方法的時(shí)候底層調(diào)用流程是怎樣的唤冈?
我們通過(guò)一段代碼來(lái)看一下super
底層實(shí)現(xiàn),為Person
類(lèi)提供run
方法银伟,Student
類(lèi)中重寫(xiě)run
方法你虹,方法內(nèi)部調(diào)用[super run];
,將Student.m
轉(zhuǎn)化為c++
代碼查看其底層實(shí)現(xiàn)彤避。
- (void) run
{
[super run];
NSLog(@"Student...");
}
上述代碼轉(zhuǎn)化為c++代碼
static void _I_Student_run(Student * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_Student_e677aa_mi_0);
}
通過(guò)上述源碼可以發(fā)現(xiàn)傅物,[super run];
轉(zhuǎn)化為底層源碼內(nèi)部其實(shí)調(diào)用的是objc_msgSendSuper
函數(shù)。
objc_msgSendSuper
函數(shù)內(nèi)傳遞了兩個(gè)參數(shù)忠藤。__rw_objc_super
結(jié)構(gòu)體和sel_registerName("run")
方法名挟伙。
__rw_objc_super
結(jié)構(gòu)體內(nèi)傳入的參數(shù)是self
和class_getSuperclass(objc_getClass("Student"))
也就是Student
的父類(lèi)Person
首先我們找到objc_msgSendSuper
函數(shù)查看內(nèi)部結(jié)構(gòu)
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以發(fā)現(xiàn)objc_msgSendSuper
中傳入的結(jié)構(gòu)體是objc_super
,我們來(lái)到objc_super
內(nèi)部查看其內(nèi)部結(jié)構(gòu)模孩。 我們通過(guò)源碼查找objc_super
結(jié)構(gòu)體查看其內(nèi)部結(jié)構(gòu)。
// 精簡(jiǎn)后的objc_super結(jié)構(gòu)體
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接受者
__unsafe_unretained _Nonnull Class super_class; // 消息接受者的父類(lèi)
/* super_class is the first class to search */
// 父類(lèi)是第一個(gè)開(kāi)始查找的類(lèi)
};
從objc_super
結(jié)構(gòu)體中可以發(fā)現(xiàn)receiver
消息接受者仍然為self
贮缅,superclass
僅僅是用來(lái)告知消息查找從哪一個(gè)類(lèi)開(kāi)始榨咐。從父類(lèi)的類(lèi)對(duì)象開(kāi)始去查找。
我們通過(guò)一張圖看一下其中的區(qū)別谴供。
從上圖中我們知道 super
調(diào)用方法的消息接受者receiver
仍然是self
块茁,只是從父類(lèi)的類(lèi)對(duì)象開(kāi)始去查找方法。
那么此時(shí)重新回到面試題桂肌,我們知道class的底層實(shí)現(xiàn)如下面代碼所示
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
class
內(nèi)部實(shí)現(xiàn)是根據(jù)消息接受者返回其對(duì)應(yīng)的類(lèi)對(duì)象数焊,最終會(huì)找到基類(lèi)的方法列表中,而self
和super
的區(qū)別僅僅是self
從本類(lèi)類(lèi)對(duì)象開(kāi)始查找方法崎场,super
從父類(lèi)類(lèi)對(duì)象開(kāi)始查找方法佩耳,因此最終得到的結(jié)果都是相同的。
另外我們?cè)诨氐?code>run方法內(nèi)部谭跨,很明顯可以發(fā)現(xiàn)干厚,如果super
不是從父類(lèi)開(kāi)始查找方法,從本類(lèi)查找方法的話螃宙,就調(diào)用方法本身造成循環(huán)調(diào)用方法而crash蛮瞄。
同理superclass
底層實(shí)現(xiàn)同class
類(lèi)似,其底層實(shí)現(xiàn)代碼如下入所示
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
因此得到的結(jié)果也是相同的谆扎。
objc_msgSendSuper2函數(shù)
上述OC代碼轉(zhuǎn)化為c++代碼并不能說(shuō)明super
底層調(diào)用函數(shù)就一定objc_msgSendSuper
挂捅。
其實(shí)super
底層真正調(diào)用的函數(shù)時(shí)objc_msgSendSuper2函數(shù)
我們可以通過(guò)查看super調(diào)用方法轉(zhuǎn)化為匯編代碼來(lái)驗(yàn)證這一說(shuō)法
- (void)viewDidLoad {
[super viewDidLoad];
}
通過(guò)斷點(diǎn)查看其匯編調(diào)用棧
上圖中可以發(fā)現(xiàn)super
底層其實(shí)調(diào)用的是objc_msgSendSuper2
函數(shù),我們來(lái)到源碼中查找一下objc_msgSendSuper2
函數(shù)的底層實(shí)現(xiàn)堂湖,我們可以在匯編文件中找到其相關(guān)底層實(shí)現(xiàn)闲先。
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
通過(guò)上面匯編代碼我們可以發(fā)現(xiàn)状土,其實(shí)底層是在函數(shù)內(nèi)部調(diào)用的class->superclass
獲取父類(lèi),并不是我們上面分析的直接傳入的就是父類(lèi)對(duì)象饵蒂。
其實(shí)_objc_msgSendSuper2
內(nèi)傳入的結(jié)構(gòu)體為objc_super2
struct objc_super2 {
id receiver;
Class current_class;
};
我們可以發(fā)現(xiàn)objc_super2
中除了消息接受者receiver
声诸,另一個(gè)成員變量current_class
也就是當(dāng)前類(lèi)對(duì)象。
與我們上面分析的不同_objc_msgSendSuper2
函數(shù)內(nèi)其實(shí)傳入的是當(dāng)前類(lèi)對(duì)象退盯,然后在函數(shù)內(nèi)部獲取當(dāng)前類(lèi)對(duì)象的父類(lèi)彼乌,并且從父類(lèi)開(kāi)始查找方法。
我們也可以通過(guò)代碼驗(yàn)證上述結(jié)構(gòu)體內(nèi)成員變量究竟是當(dāng)前類(lèi)對(duì)象還是父類(lèi)對(duì)象渊迁。下文中我們會(huì)通過(guò)另外一道面試題驗(yàn)證慰照。
isKindOfClass 與 isMemberOfClass
首先看一下isKindOfClass isKindOfClass
對(duì)象方法底層實(shí)現(xiàn)
- (BOOL)isMemberOfClass:(Class)cls {
// 直接獲取實(shí)例類(lèi)對(duì)象并判斷是否等于傳入的類(lèi)對(duì)象
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
// 向上查詢,如果找到父類(lèi)對(duì)象等于傳入的類(lèi)對(duì)象則返回YES
// 直到基類(lèi)還不相等則返回NO
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isKindOfClass isKindOfClass
類(lèi)方法底層實(shí)現(xiàn)
// 判斷元類(lèi)對(duì)象是否等于傳入的元類(lèi)元類(lèi)對(duì)象
// 此時(shí)self是類(lèi)對(duì)象 object_getClass((id)self)就是元類(lèi)
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
// 向上查找琉朽,判斷元類(lèi)對(duì)象是否等于傳入的元類(lèi)對(duì)象
// 如果找到基類(lèi)還不相等則返回NO
// 注意:這里會(huì)找到基類(lèi)
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
通過(guò)上述源碼分析我們可以知道毒租。 isMemberOfClass
判斷左邊是否剛好等于右邊類(lèi)型。 isKindOfClass
判斷左邊或者左邊類(lèi)型的父類(lèi)是否剛好等于右邊類(lèi)型箱叁。 注意:類(lèi)方法內(nèi)部是獲取其元類(lèi)對(duì)象進(jìn)行比較
我們查看以下代碼
NSLog(@"%d",[Person isKindOfClass: [Person class]]);
NSLog(@"%d",[Person isKindOfClass: object_getClass([Person class])]);
NSLog(@"%d",[Person isKindOfClass: [NSObject class]]);
// 輸出內(nèi)容
Runtime-super[46993:5195901] 0
Runtime-super[46993:5195901] 1
Runtime-super[46993:5195901] 1
分析上述輸出內(nèi)容: 第一個(gè) 0:上面提到過(guò)類(lèi)方法是獲取self的元類(lèi)對(duì)象與傳入的參數(shù)進(jìn)行比較墅垮,但是第一行我們傳入的是類(lèi)對(duì)象,因此返回NO耕漱。
第二個(gè) 1:同上算色,此時(shí)我們傳入Person元類(lèi)對(duì)象,此時(shí)返回YES螟够。驗(yàn)證上述說(shuō)法
第三個(gè) 1:我們發(fā)現(xiàn)此時(shí)傳入的是NSObject類(lèi)對(duì)象并不是元類(lèi)對(duì)象灾梦,但是返回的值卻是YES。 原因是基元類(lèi)的superclass指針是指向基類(lèi)對(duì)象的妓笙。如下圖13號(hào)線
那么Person元類(lèi)
通過(guò)superclass
指針一直找到基元類(lèi)若河,還是不相等,此時(shí)再次通過(guò)superclass
指針來(lái)到基類(lèi)寞宫,那么此時(shí)發(fā)現(xiàn)相等就會(huì)返回YES了萧福。
復(fù)習(xí)
通過(guò)一道面試題對(duì)之前學(xué)習(xí)的知識(shí)進(jìn)行復(fù)習(xí)。 問(wèn):以下代碼是否可以執(zhí)行成功淆九,如果可以统锤,打印結(jié)果是什么。
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end
// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
NSLog(@"test print name is : %@", self.name);
}
@end
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
Person *person = [[Person alloc] init];
[person test];
}
這道面試題確實(shí)很無(wú)厘頭的一道題炭庙,日常工作中沒(méi)有人這樣寫(xiě)代碼饲窿,但是需要解答這道題需要很完備的底層知識(shí),我們通過(guò)這道題來(lái)復(fù)習(xí)一下焕蹄,首先看一下打印結(jié)果逾雄。
Runtime面試題[15842:2579705] test print name is : <ViewController: 0x7f95514077a0>
Runtime面試題[15842:2579705] test print name is : (null)
通過(guò)上述打印結(jié)果我們可以看出,是可以正常運(yùn)行并打印的,說(shuō)明obj
可以正常調(diào)用test
方法鸦泳,但是我們發(fā)現(xiàn)打印self.name
的內(nèi)容卻是<ViewController: 0x7f95514077a0>
银锻。下面person
實(shí)例調(diào)用test
不做過(guò)多解釋了,主要用來(lái)和上面方法調(diào)用做對(duì)比做鹰。
為什么會(huì)是這樣的結(jié)果呢击纬?首先通過(guò)一張圖看一下兩種調(diào)用方法的內(nèi)存信息。
通過(guò)上圖我們可以發(fā)現(xiàn)兩種方法調(diào)用方式很相近钾麸。那么obj為什么可以正常調(diào)用方法更振?
obj為什么可以正常調(diào)用方法
首先通過(guò)之前的學(xué)習(xí)我們知道,person
調(diào)用方法時(shí)首先通過(guò)isa
指針找到類(lèi)對(duì)象進(jìn)而查找方法并進(jìn)行調(diào)用饭尝。
而person
實(shí)例對(duì)象內(nèi)實(shí)際上是取最前面8個(gè)字節(jié)空間也就是isa
并通過(guò)計(jì)算得出類(lèi)對(duì)象地址肯腕。
而通過(guò)上圖我們可以發(fā)現(xiàn),obj
在調(diào)用test
方法時(shí)钥平,也會(huì)通過(guò)其內(nèi)存地址找到cls
实撒,而cls
中取出最前面8個(gè)字節(jié)空間其內(nèi)部存儲(chǔ)的剛好是Person
類(lèi)對(duì)象地址。因此obj
是可以正常調(diào)用方法的涉瘾。
為什么self.name打印內(nèi)容為ViewController對(duì)象
問(wèn)題出在[super viewDidLoad];
這段代碼中知态,通過(guò)上述對(duì)super
本質(zhì)的分析我們知道,super
內(nèi)部調(diào)用objc_msgSendSuper2
函數(shù)立叛。
我們知道objc_msgSendSuper2
函數(shù)內(nèi)部會(huì)傳入兩個(gè)參數(shù)肴甸,objc_super2
結(jié)構(gòu)體和SEL
,并且objc_super2
結(jié)構(gòu)體內(nèi)有兩個(gè)成員變量消息接受者和其父類(lèi)囚巴。
struct objc_super2 {
id receiver; // 消息接受者
Class current_class; // 當(dāng)前類(lèi)
};
};
通過(guò)以上分析我們可以得知[super viewDidLoad];
內(nèi)部objc_super2
結(jié)構(gòu)體內(nèi)存儲(chǔ)如下所示
struct objc_super = {
self,
[ViewController Class]
};
那么objc_msgSendSuper2
函數(shù)調(diào)用之前,會(huì)先創(chuàng)建局部變量objc_super2
結(jié)構(gòu)體用于為objc_msgSendSuper2
函數(shù)傳遞的參數(shù)友扰。
局部變量由高地址向低地址分配在椡妫空間
我們知道局部變量是存儲(chǔ)在棧空間內(nèi)的村怪,并且是由高地址向低地址有序存儲(chǔ)秽浇。 我們通過(guò)一段代碼驗(yàn)證一下。
long long a = 1;
long long b = 2;
long long c = 3;
NSLog(@"%p %p %p", &a,&b,&c);
// 打印內(nèi)容
0x7ffee9774958 0x7ffee9774950 0x7ffee9774948
通過(guò)上述代碼打印內(nèi)容甚负,我們可以驗(yàn)證局部變量在椉砘溃空間內(nèi)是由高地址向低地址連續(xù)存儲(chǔ)的。
那么我們回到面試題中梭域,通過(guò)上述分析我們知道斑举,此時(shí)代碼中包含局部變量以此為objc_super2 結(jié)構(gòu)體
、cls
病涨、obj
富玷。通過(guò)一張圖展示一下這些局部變量存儲(chǔ)結(jié)構(gòu)。
上面我們知道當(dāng)person
實(shí)例對(duì)象調(diào)用方法的時(shí)候,會(huì)取實(shí)例變量前8個(gè)字節(jié)空間也就是isa
來(lái)找到類(lèi)對(duì)象地址赎懦。那么當(dāng)訪問(wèn)實(shí)例變量的時(shí)候雀鹃,就跳過(guò)isa
的8個(gè)字節(jié)空間往下面去找實(shí)例變量。
那么當(dāng)obj
在調(diào)用test
方法的時(shí)候同樣找到cls
中取出前8個(gè)字節(jié)励两,也就是Person類(lèi)對(duì)象
的內(nèi)存地址黎茎,那么當(dāng)訪問(wèn)實(shí)例變量_name
的時(shí)候,會(huì)繼續(xù)向高地址內(nèi)存空間查找当悔,此時(shí)就會(huì)找到objc_super
結(jié)構(gòu)體傅瞻,從中取出8個(gè)字節(jié)空間也就是self
,因此此時(shí)訪問(wèn)到的self.name
就是ViewController對(duì)象
先鱼。
當(dāng)訪問(wèn)成員變量_name
的時(shí)候俭正,test
函數(shù)中的self
也就是方法調(diào)用者其實(shí)是obj
,那么self.name
就是通過(guò)obj
去找_name
焙畔,跳過(guò)cls的8個(gè)指針掸读,在取8個(gè)指針此時(shí)自然獲取到ViewController對(duì)象
。
因此上述代碼中cls
就相當(dāng)于isa
宏多,isa
下面的8個(gè)字節(jié)空間就相當(dāng)于_name
成員變量儿惫。因此成員變量_name
的訪問(wèn)到的值就是cls
地址后向高地址位取8個(gè)字節(jié)地址空間存儲(chǔ)的值。
為了驗(yàn)證上述說(shuō)法伸但,我們做一個(gè)實(shí)驗(yàn)肾请,在cls
后高地址中添加一個(gè)string
,那么此時(shí)cls
下面的高地址位就是string
更胖。以下示例代碼
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string = @"string";
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
Person *person = [[Person alloc] init];
[person test];
}
此時(shí)的局部變量?jī)?nèi)存結(jié)構(gòu)如下圖所示
此時(shí)在訪問(wèn)_name
成員變量的時(shí)候铛铁,越過(guò)cls
內(nèi)存往高地址找就會(huì)來(lái)到string
,此時(shí)拿到的成員變量就是string
了却妨。 我們來(lái)看一下打印內(nèi)容
Runtime面試題[16887:2829028] test print name is : string
Runtime面試題[16887:2829028] test print name is : (null)
再通過(guò)一段代碼使用int數(shù)據(jù)進(jìn)行試驗(yàn)
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
Person *person = [[Person alloc] init];
[person test];
}
// 程序crash饵逐,壞地址訪問(wèn)
我們發(fā)現(xiàn)程序因?yàn)閴牡刂吩L問(wèn)而crash,此時(shí)局部變量?jī)?nèi)存結(jié)構(gòu)如下圖所示
當(dāng)需要訪問(wèn)_name
成員變量的時(shí)候彪标,會(huì)在cls
后高地址為查找8位的字節(jié)空間倍权,而我們知道int
占4位字節(jié),那么此時(shí)8位的內(nèi)存空間同時(shí)占據(jù)int
數(shù)據(jù)及objc_super
結(jié)構(gòu)體內(nèi)薄声,因此就會(huì)造成壞地址訪問(wèn)而crash题画。
我們添加新的成員變量進(jìn)行訪問(wèn)
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *nickName;
- (void)test;
@end
------------
// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
NSLog(@"test print name is : %@", self.nickName);
}
@end
--------
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj1 = [[NSObject alloc] init];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
Person *person = [[Person alloc] init];
[person test];
}
我們看一下打印內(nèi)容
// 打印內(nèi)容
// Runtime面試題[17272:2914887] test print name is : <ViewController: 0x7ffc6010af50>
// Runtime面試題[17272:2914887] test print name is : (null)
可以發(fā)現(xiàn)此時(shí)打印的仍然是ViewController對(duì)象
,我們先來(lái)看一下其局部變量?jī)?nèi)存結(jié)構(gòu)
首先通過(guò)obj
找到cls
婴程,cls
找到類(lèi)對(duì)象進(jìn)行方法調(diào)用廓奕,此時(shí)在訪問(wèn)nickName
時(shí),obj
查找成員變量蒸绩,首先跳過(guò)8個(gè)字節(jié)的cls
,之后跳過(guò)name
所占的8個(gè)字節(jié)空間铃肯,最終再取8個(gè)字節(jié)空間取出其中的值作為成員變量的值患亿,那么此時(shí)也就是self
了押逼。
總結(jié):這道面試題雖然很無(wú)厘頭,讓人感覺(jué)無(wú)從下手但是考察的內(nèi)容非常多咙冗。 1. super的底層本質(zhì)為調(diào)用objc_msgSendSuper2
函數(shù)漂彤,傳入objc_super2
結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部存儲(chǔ)消息接受者和當(dāng)前類(lèi)挫望,用來(lái)告知系統(tǒng)方法查找從父類(lèi)開(kāi)始。
2. 局部變量分配在椛H空間蛉幸,并且從高地址向低地址連續(xù)分配。先創(chuàng)建的局部變量分配在高地址奕纫,后續(xù)創(chuàng)建的局部變量連續(xù)分配在較低地址若锁。
3. 方法調(diào)用的消息機(jī)制斧吐,通過(guò)isa指針找到類(lèi)對(duì)象進(jìn)行消息發(fā)送。
4. 指針存儲(chǔ)的是實(shí)例變量的首字節(jié)地址煤率,上述例子中person
指針存儲(chǔ)的其實(shí)就是實(shí)例變量?jī)?nèi)部的isa
指針的地址。
5. 訪問(wèn)成員變量的本質(zhì)洋只,找到成員變量的地址,按照成員變量所占的字節(jié)數(shù),取出地址中存儲(chǔ)的成員變量的值识虚。
驗(yàn)證objc_msgSendSuper2內(nèi)傳入的結(jié)構(gòu)體參數(shù)
我們使用以下代碼來(lái)驗(yàn)證上文中遺留的問(wèn)題
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj test];
}
上述代碼的局部變量?jī)?nèi)存結(jié)構(gòu)我們之前已經(jīng)分析過(guò)了肢扯,真正的內(nèi)存結(jié)構(gòu)應(yīng)該如下圖所示
通過(guò)上面對(duì)面試題的分析,我們現(xiàn)在想要驗(yàn)證objc_msgSendSuper2
函數(shù)內(nèi)傳入的結(jié)構(gòu)體參數(shù)担锤,只需要拿到cls
的地址蔚晨,然后向后移8個(gè)地址就可以獲取到objc_super
結(jié)構(gòu)體內(nèi)的self
肛循,在向后移8個(gè)地址就是current_class
的內(nèi)存地址。通過(guò)打印current_class
的內(nèi)容累舷,就可以知道傳入objc_msgSendSuper2
函數(shù)內(nèi)部的是當(dāng)前類(lèi)對(duì)象還是父類(lèi)對(duì)象了夹孔。
我們來(lái)證明他是UIViewController
還是ViewController
即可
通過(guò)上圖可以發(fā)現(xiàn)析蝴,最終打印的內(nèi)容確實(shí)為當(dāng)前類(lèi)對(duì)象。 因此objc_msgSendSuper2
函數(shù)內(nèi)部其實(shí)傳入的是當(dāng)前類(lèi)對(duì)象尝盼,并且在函數(shù)內(nèi)部獲取其父類(lèi)佑菩,告知系統(tǒng)從父類(lèi)方法開(kāi)始查找的殿漠。
Runtime API
首先我們通過(guò)來(lái)看一段代碼,后續(xù)Runtime API的使用均基于此代碼蕾哟。
// Person類(lèi)繼承自NSObject莲蜘,包含run方法
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)run;
@end
#import "Person.h"
@implementation Person
- (void)run
{
NSLog(@"%s",__func__);
}
@end
// Car類(lèi)繼承自NSObejct,包含run方法
#import "Car.h"
@implementation Car
- (void)run
{
NSLog(@"%s",__func__);
}
@end
類(lèi)相關(guān)API
1\. 動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)(參數(shù):父類(lèi)逐哈,類(lèi)名问顷,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
2\. 注冊(cè)一個(gè)類(lèi)(要在類(lèi)注冊(cè)之前添加成員變量)
void objc_registerClassPair(Class cls)
3\. 銷(xiāo)毀一個(gè)類(lèi)
void objc_disposeClassPair(Class cls)
示例:
void run(id self , SEL _cmd) {
NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建類(lèi) superclass:繼承自哪個(gè)類(lèi) name:類(lèi)名 size_t:格外的大小,創(chuàng)建類(lèi)是否需要擴(kuò)充空間
// 返回一個(gè)類(lèi)對(duì)象
Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);
// 添加成員變量
// cls:添加成員變量的類(lèi) name:成員變量的名字 size:占據(jù)多少字節(jié) alignment:內(nèi)存對(duì)齊算途,最好寫(xiě)1 types:類(lèi)型哗戈,int類(lèi)型就是@encode(int) 也就是i
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_height", 4, 1, @encode(float));
// 添加方法
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注冊(cè)類(lèi)
objc_registerClassPair(newClass);
// 創(chuàng)建實(shí)例對(duì)象
id student = [[newClass alloc] init];
// 通過(guò)KVC訪問(wèn)
[student setValue:@10 forKey:@"_age"];
[student setValue:@180.5 forKey:@"_height"];
// 獲取成員變量
NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);
// 獲取類(lèi)的占用空間
NSLog(@"類(lèi)對(duì)象占用空間%zd", class_getInstanceSize(newClass));
// 調(diào)用動(dòng)態(tài)添加的方法
[student run];
}
return 0;
}
// 打印內(nèi)容
// Runtime應(yīng)用[25605:4723961] _age = 10 , _height = 180.5
// Runtime應(yīng)用[25605:4723961] 類(lèi)對(duì)象占用空間16
// Runtime應(yīng)用[25605:4723961] <Student: 0x10072e420> - run
注意
類(lèi)一旦注冊(cè)完畢唯咬,就相當(dāng)于類(lèi)對(duì)象和元類(lèi)對(duì)象里面的結(jié)構(gòu)就已經(jīng)創(chuàng)建好了。
因此必須在注冊(cè)類(lèi)之前狞贱,添加成員變量蜀涨。方法可以在注冊(cè)之后再添加,因?yàn)榉椒ㄊ强梢詣?dòng)態(tài)添加的氧枣。
創(chuàng)建的類(lèi)如果不需要使用了 别垮,需要釋放類(lèi)。
4\. 獲取isa指向的Class烧董,如果將類(lèi)對(duì)象傳入獲取的就是元類(lèi)對(duì)象胧奔,如果是實(shí)例對(duì)象則為類(lèi)對(duì)象
Class object_getClass(id obj)
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"%p,%p,%p",object_getClass(person), [Person class],
object_getClass([Person class]));
}
return 0;
}
// 打印內(nèi)容
Runtime應(yīng)用[21115:3807804] 0x100001298,0x100001298,0x100001270
5\. 設(shè)置isa指向的Class龙填,可以動(dòng)態(tài)的修改類(lèi)型。例如修改了person對(duì)象的類(lèi)型胶背,也就是說(shuō)修改了person對(duì)象的isa指針的指向喘先,中途讓對(duì)象去調(diào)用其他類(lèi)的同名方法廷粒。
Class object_setClass(id obj, Class cls)
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];
object_setClass(person, [Car class]);
[person run];
}
return 0;
}
// 打印內(nèi)容
Runtime應(yīng)用[21147:3815155] -[Person run]
Runtime應(yīng)用[21147:3815155] -[Car run]
最終其實(shí)調(diào)用了car的run方法
6\. 用于判斷一個(gè)OC對(duì)象是否為Class
BOOL object_isClass(id obj)
// 判斷OC對(duì)象是實(shí)例對(duì)象還是類(lèi)對(duì)象
NSLog(@"%d",object_isClass(person)); // 0
NSLog(@"%d",object_isClass([person class])); // 1
NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1
// 元類(lèi)對(duì)象也是特殊的類(lèi)對(duì)象
7\. 判斷一個(gè)Class是否為元類(lèi)
BOOL class_isMetaClass(Class cls)
8\. 獲取類(lèi)對(duì)象父類(lèi)
Class class_getSuperclass(Class cls)
成員變量相關(guān)API
1\. 獲取一個(gè)實(shí)例變量信息,描述信息變量的名字暇番,占用多少字節(jié)等
Ivar class_getInstanceVariable(Class cls, const char *name)
2\. 拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
3\. 設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
4\. 動(dòng)態(tài)添加成員變量(已經(jīng)注冊(cè)的類(lèi)是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
5\. 獲取成員變量的相關(guān)信息思喊,傳入成員變量信息恨课,返回C語(yǔ)言字符串
const char *ivar_getName(Ivar v)
6\. 獲取成員變量的編碼,types
const char *ivar_getTypeEncoding(Ivar v)
示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 獲取成員變量的信息
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
// 獲取成員變量的名字和編碼
NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));
Person *person = [[Person alloc] init];
// 設(shè)置和獲取成員變量的值
object_setIvar(person, nameIvar, @"xx_cc");
// 獲取成員變量的值
object_getIvar(person, nameIvar);
NSLog(@"%@", object_getIvar(person, nameIvar));
NSLog(@"%@", person.name);
// 拷貝實(shí)例變量列表
unsigned int count ;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i ++) {
// 取出成員變量
Ivar ivar = ivars[i];
NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
}
return 0;
}
// 打印內(nèi)容
// Runtime應(yīng)用[25783:4778679] _name, @"NSString"
// Runtime應(yīng)用[25783:4778679] xx_cc
// Runtime應(yīng)用[25783:4778679] xx_cc
// Runtime應(yīng)用[25783:4778679] _name, @"NSString"
屬性相關(guān)AIP
1\. 獲取一個(gè)屬性
objc_property_t class_getProperty(Class cls, const char *name)
2\. 拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
3\. 動(dòng)態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
4\. 動(dòng)態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
5\. 獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
方法相關(guān)API
1\. 獲得一個(gè)實(shí)例方法希俩、類(lèi)方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
2\. 方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
3\. 拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
4\. 動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
5\. 動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
6\. 獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
7\. 選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
8\. 用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
Runtime的應(yīng)用
關(guān)于Runtime的應(yīng)用請(qǐng)參考iOS-RunTime應(yīng)用
Runtime 博文推薦
Runtime圖解
Runtime 10種實(shí)際用法
完整總結(jié)
objc_msgSend
詳解
快速上手
消息機(jī)制
Method Swizzling開(kāi)發(fā)實(shí)例匯總
最實(shí)用的runtime總結(jié)
實(shí)際開(kāi)發(fā)中的應(yīng)用