一 super的本質(zhì)
先從一個(gè)面試題開始探討super相關(guān)的問(wèn)題。有一個(gè)Person類和一個(gè)Student類,Student類繼承自Person類。現(xiàn)在在Student類的init方法中有如下打印:
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"[super superclass] = %@", [super superclass]);
問(wèn)打印結(jié)果是什么。
按照以前的理解,第一個(gè)應(yīng)該是Student類的類對(duì)象,第二個(gè)是student類的父類的類對(duì)象,也就是Person類的類對(duì)象,第三個(gè)也是Student類的父類的類對(duì)象,第四個(gè)是Student類的父類的父類的類對(duì)象也就是NSObject類的類對(duì)象。
那么真實(shí)的打印情況是不是這樣?我們看一下:
2018-09-17 15:54:02.224686+0800 TEST[8409:174143] [self class] = Student
2018-09-17 15:54:02.224922+0800 TEST[8409:174143] [super class] = Student
2018-09-17 15:54:02.225040+0800 TEST[8409:174143] [self superclass] = Person
2018-09-17 15:54:02.225922+0800 TEST[8409:174143] [super superclass] = Person
通過(guò)打印結(jié)果可以看到,第二個(gè)和第四個(gè)與我之前理解的不一致,這是為什么呢?[super class]
的打印結(jié)果為什么是Student呢?在搞清楚這些問(wèn)題之前,我們先搞清楚class
和superclass
方法的實(shí)現(xiàn)。我在runtime的NSObject.mm文件中找到了實(shí)現(xiàn)的源碼:
/*******************************************************
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
//通過(guò)對(duì)象的isa指針獲取類的類對(duì)象
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
Class class_getSuperclass(Class cls)
{
if (!cls) return nil;
return cls->superclass;
}
******************************************************/
為了搞清楚super的實(shí)現(xiàn),我在Person類中實(shí)現(xiàn)run方法,然后在Student類的init方法中使用[super run]
來(lái)調(diào)用,然后將其轉(zhuǎn)化為C++的源碼,找到[super run]
的實(shí)現(xiàn):
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
簡(jiǎn)化一下:
objc_msgSendSuper(__rw_objc_super{
self,
class_getSuperclass(objc_getClass("Student")},
@selector(run));
我們可以看到,這個(gè)objc_msgSendSuper()
函數(shù)中傳入了兩個(gè)參數(shù),一個(gè)是一個(gè)結(jié)構(gòu)體:
__rw_objc_super{
self,
class_getSuperclass(objc_getClass("Student")}
還有一個(gè)就是消息@selector(run)
。
第一個(gè)參數(shù)這個(gè)結(jié)構(gòu)體中有兩個(gè)成員變量,一個(gè)是self也就是Student實(shí)例對(duì)象,還有一個(gè)是Student類的父類的類對(duì)象,也即是Person類的類對(duì)象。
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
第一個(gè)成員是消息接收者,第二個(gè)參數(shù)是父類對(duì)象。
我們?cè)倏匆幌?code>objc_msgSendSuper的實(shí)現(xiàn):
這個(gè)解釋已經(jīng)非常清楚了,也就是objc_msgSendSuper()中相當(dāng)于傳入了三個(gè)參數(shù):
objc_msgSendSuper(object ,superclass, @selector(run))
,第一個(gè)參數(shù)是消息的接收者,第二個(gè)參數(shù)決定了從這個(gè)父類對(duì)象開始尋找方法的實(shí)現(xiàn),第三個(gè)參數(shù)就是消息。
回到[super run]
,這個(gè)方法也就是給student對(duì)象發(fā)送@selector(run)消息,但是查找run方法的實(shí)現(xiàn)要從Student類的父類對(duì)象也即是Person類的類對(duì)象中開始查找。
[self class]
就是通過(guò)實(shí)例對(duì)象的isa指針找到找到其類對(duì)象,所以打印是Student。
[super class]
是給self對(duì)象發(fā)送@selector(class)消息,但是class方法的實(shí)現(xiàn)要從Person類對(duì)象開始查找。class方法是在基類NSObject類中實(shí)現(xiàn)的,所以不管是從Student類對(duì)象中開始查找還是從Person類對(duì)象中開始查找方法的實(shí)現(xiàn),做種都是找打NSObject的實(shí)現(xiàn)中,所以[self class]
和[super class]
并無(wú)差異,打印都是Student。
[self superclass]
這個(gè)獲取的就是自己的類對(duì)象的superclass指針的指向,就是父類的類對(duì)象,所以打印是Person。
[super superclass]
這個(gè)其實(shí)和第二個(gè)的情況是一樣的,給student對(duì)象發(fā)送@selector(superclass)
消息,但是superclass的實(shí)現(xiàn)要從父類Person類的類對(duì)象開始找起,但是superclass的實(shí)現(xiàn)是基類NSObject類實(shí)現(xiàn)的,所以從Student類的類對(duì)象和Person類的類對(duì)象開始查是沒有區(qū)別的。最終輸出都是student對(duì)象的父類對(duì)象,打印結(jié)果是Person。
二 isKindOfClass ,isMemberOfClass
再來(lái)看一個(gè)面試題,看下面的打印結(jié)果:
BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [[Person class] isKindOfClass:[Person class]];
BOOL res4 = [[Person class] isMemberOfClass:[Person class]];
NSLog(@"%d, %d, %d, %d", res1, res2, res3, res4);
我們首先看一下打印結(jié)果:
1, 0, 0, 0
在分析這個(gè)問(wèn)題之前,我們先查看isMemberOfClass:
和isKindOfClass:
的源碼來(lái)搞明白其具體實(shí)現(xiàn):
/*******************************************
//object_getClass()取得的是對(duì)象的isa指針指向的對(duì)象,也就是判斷傳入的類對(duì)象的元類對(duì)象是否與傳入的這個(gè)對(duì)象相等,所以這個(gè)cls應(yīng)該是元類對(duì)象才有可能相等
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
判斷傳入的實(shí)例對(duì)象的類對(duì)象是否與傳入的對(duì)象相等,所以cls只有可能是類對(duì)象才有可能相等
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
//循環(huán)判斷傳入的類對(duì)象的元類對(duì)象及其父類的元類對(duì)象是否等于傳入的cls
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//循環(huán)判斷實(shí)例對(duì)象的父類的類對(duì)象是否等于傳入的對(duì)象cls,也就是判斷實(shí)例對(duì)象是否是cls及其子類的一種
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
****************************************/
通過(guò)這個(gè)兩個(gè)方法的源碼我們可以知道,isMemberOfClass:
是檢測(cè)方法調(diào)用者對(duì)象的類是否等于傳入的這個(gè)類。isKindOfClass:
是判斷方法調(diào)用者對(duì)象的類是否等于傳入的這個(gè)類或者其子類。還有一個(gè)適用于這四個(gè)方法的一點(diǎn)是,如果方法調(diào)用者是實(shí)例對(duì)象,那么傳入的就應(yīng)該是類對(duì)象;如果方法調(diào)用者是類對(duì)象,那么傳入的就應(yīng)該是元類對(duì)象。
下面先從第二個(gè)開始分析:
[[NSObject class] isMemberOfClass:[NSObject class]];
方法調(diào)用者是[NSObject class]
也就是類對(duì)象,但是傳入的參數(shù)也是類對(duì)象,所以很顯然打印結(jié)果是0。
帶三個(gè):
[[Person class] isKindOfClass:[Person class]];
方法的調(diào)用者是類對(duì)象,傳入的參數(shù)也是類對(duì)象,所以打印結(jié)果是0。
第四個(gè):
[[Person class] isMemberOfClass:[Person class]];
方法調(diào)用者是類對(duì)象,傳入的參數(shù)也是類對(duì)象,所以打印的是0。
最后來(lái)看第一個(gè):
[[NSObject class] isKindOfClass:[NSObject class]];
按照和上面一樣的分析,方法調(diào)用者和傳入的對(duì)象都是類方法,那么應(yīng)該打印0才對(duì)呀,為何會(huì)打印1呢?我們回過(guò)頭去看一下+ (BOOL)isKindOfClass:(Class)cls;
的實(shí)現(xiàn):
//循環(huán)判斷傳入的類對(duì)象的元類對(duì)象及其父類的元類對(duì)象是否等于傳入的cls
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
我們看到這個(gè)實(shí)現(xiàn)方法,object_getClass((id)self)
是先獲得元類方法,判斷其是否等于cls,然后沿著繼承鏈取元類對(duì)象的superclass,我們想一下,當(dāng)沿著繼承鏈,一直不滿足tcls == cls
時(shí),最終會(huì)找到NSObject類,取NSObject類的元類的superclass,而NSOBject的元類的superclass指向的是NSOBject類的類對(duì)象,所以打印輸出是1。
三 一個(gè)很繞的面試題
Person類中有一個(gè)屬性叫name,Person類中還有一個(gè)print方法用來(lái)打印name屬性:
//Person.m
- (void)print{
NSLog(@"my name is %@", self.name);
}
問(wèn)題是下面這段代碼的打印結(jié)果:
//ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
}
如果在正常工作中寫出這種代碼會(huì)被炒魷魚的,繞來(lái)繞去非常古怪,但是如果是學(xué)習(xí)的話還是可以好好探究一番。
首先我們還是來(lái)看一下打印結(jié)果吧:
my name is <ViewController: 0x7fd18a50a550>
通過(guò)打印結(jié)果,這里面有兩個(gè)疑惑:
- 1.print是一個(gè)實(shí)例方法但是代碼中自始至終沒有創(chuàng)建實(shí)例對(duì)象,只有一個(gè)類對(duì)象,那么為什么能夠調(diào)用對(duì)象方法呢?
- 2.即使能夠調(diào)用對(duì)象方法,為什么打印出來(lái)的是ViewController的地址呢?
下面我們先來(lái)分析一下
id cls = [Person class];
void *obj = &cls;
這兩句代碼。
首先創(chuàng)建了一個(gè)id類型的cls指針指向Person類對(duì)象。然后又創(chuàng)建了一個(gè)指針變量obj,obj中存放的是cls的地址,用圖表示其結(jié)構(gòu)如下:
然后我們?cè)倏聪旅嬉痪浯a:
Person *person = [[Person alloc] init];
這里創(chuàng)建了一個(gè)Person類型的指針變量指向alloc出來(lái)的Person實(shí)例對(duì)象。Person實(shí)例對(duì)象的結(jié)構(gòu)其實(shí)就是一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面存放著isa指針和成員變量,具體到Person實(shí)例對(duì)象,這個(gè)實(shí)例對(duì)象中存放著指向Person類對(duì)象的isa指針和_name成員變量。由于person對(duì)象的第一個(gè)成員變量是isa指針,所以person指針指向的其實(shí)就是isa指針?biāo)诘膬?nèi)存,所以其結(jié)構(gòu)圖如下:
對(duì)比一下上下兩個(gè)圖,是否非常相似呢?
調(diào)用
[person print]
時(shí),通過(guò)person指針獲取person實(shí)例對(duì)象的地址值,取這個(gè)實(shí)例對(duì)象的地址值的前8字節(jié),就是isa指針了,然后取出isa指針中的地址值,就可以訪問(wèn)類對(duì)象了,然后找到類對(duì)象中的print方法進(jìn)行調(diào)用。
那么對(duì)于obj指針也是一樣的,對(duì)于[obj print]
,通過(guò)obj指針獲取cls指針的起始位置,同樣可以取其前八字節(jié)(雖然整個(gè)cls只占8字節(jié),但是沒關(guān)系),作為'isa'指針,然后讀出其地址值,就可以獲取類對(duì)象,從而調(diào)用其中的方法。
這樣就回答了第一個(gè)問(wèn)題,即為什么obj能調(diào)用對(duì)象方法。
對(duì)于第二個(gè)問(wèn)題,我們?cè)赾ls指針前面創(chuàng)建一個(gè)字符串試試:
NSString *str = @"test";
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
看一下打印結(jié)果:
my name is test
這就很神器了,居然打印出來(lái)的死str字符串的值,那么我們?cè)僭囍赾ls指針前面創(chuàng)建一個(gè)NSObject對(duì)象試試:
NSObject *object = [[NSObject alloc] init];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
打印結(jié)果:
my name is <NSObject: 0x6040000074c0>
打斷點(diǎn)后證實(shí)object對(duì)象的地址正是打印的地址,也就是打印的是object對(duì)象,這就很玄乎了。
我們先來(lái)搞清楚一個(gè)問(wèn)題,棧空間的分配是從高地址開始分配還是低地址開始分配?我們?cè)囼?yàn)一下便知道:
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
NSObject *obj3 = [[NSObject alloc] init];
NSObject *obj4 = [[NSObject alloc] init];
NSLog(@"%p, %p, %p, %p", &obj1, &obj2, &obj3, &obj4);
打印結(jié)果:
0x7ffeeec26ac8, 0x7ffeeec26ac0, 0x7ffeeec26ab8, 0x7ffeeec26ab0
obj1,obj2,obj3,obj4都是局部變量,分配在棧區(qū),可以看到,obj1的地址位最高,是0x7ffeeec26ac8
,由于指針變量占8字節(jié),所以obj2的地址是在此基礎(chǔ)上減8,也就是0x7ffeeec26ac0
,obj3的地址是在obj2的基礎(chǔ)上減8。
這就說(shuō)明棧區(qū)的內(nèi)存是從高地址到低地址分配的。
我們?cè)賮?lái)分析下下面代碼的內(nèi)存關(guān)系:
NSString *str = @"test";
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
首先在棧空間分配了一個(gè)NSString類型的指針str,這個(gè)指針指向常量區(qū)的test字符串,然后在低8字節(jié)的棧空間又分配了一個(gè)id類型的指針cls,這個(gè)指針指向Person類對(duì)象,然后又在低8字節(jié)的占空間分配了一個(gè)指針obj,這個(gè)指針是指向cls指針的。test,cls,obj它們的地址四連續(xù)分配的。我們知道print方法是打印self.name,也就是讀取person對(duì)象的成員變量_name的值,由于Person類只有一個(gè)屬性name,所以其實(shí)例對(duì)象的結(jié)構(gòu)也是非常簡(jiǎn)單的,就是一個(gè)isa指針和一個(gè)_name成員變量。[person print]
是怎樣去獲取_name的值的呢?
person指針指向的是person實(shí)例對(duì)象的地址,person實(shí)例對(duì)象的前8字節(jié)是isa指針,那么只需要讀取person指針的地址,從這個(gè)地址開始的前8字節(jié)就是isa指針,同理,再往下取8字節(jié)就是_name成員變量的值。
那么[obj print]
也是一樣的,obj指針把cls當(dāng)做了person實(shí)例對(duì)象,所以它會(huì)怎么去取_name的值呢?還是一樣的,通過(guò)obj存放的地址獲取cls的起始地址,然后讀從這個(gè)起始地址開始的前八字節(jié)當(dāng)做isa指針,再往后取8字節(jié)當(dāng)做_name成員變量的值。cls往后取第9到16字節(jié)的值正是@"test"字符串,所以打印出來(lái)的也就是字符串。
現(xiàn)在我們?cè)倩氐皆瓉?lái)的問(wèn)題,為什么打印的是<ViewController: 0x7fd18a50a550>
也就是視圖控制器對(duì)象。
我們來(lái)分析一下下面的一句代碼:
[super viewDidLoad];
我們?cè)诘谝徊糠志椭v過(guò)super調(diào)用的問(wèn)題,這段代碼的本質(zhì)就是:
objc_msgSendSuper({self,
class_getSuperclass(objc_getClass("ViewController"))},
@selector(viewDidLoad))
第一個(gè)參數(shù)傳入的是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體如下:
struct objc_super{
self,
class_getSuperclass(objc_getClass("ViewController"))
};
那么[super viewDidLoad];
也就相當(dāng)于聲明了一個(gè)結(jié)構(gòu)體類型的局部變量,這個(gè)局部變量有兩個(gè)成員,所有新的內(nèi)存結(jié)構(gòu)如下:
這個(gè)時(shí)候去調(diào)用print,讀取的_name也即是self,就是控制器了。