runtime 小測試

下面代碼輸出什么?

self super

@implementation Son : Father

- (id)init

{

? ? ? self = [super init];

? ? ? ?if (self){

? ? ? ? ?NSLog(@"%@", NSStringFromClass([self class]));

? ? ? ? ? NSLog(@"%@", NSStringFromClass([super class]));

? ? ? ?}

return self;

}

@end

這道面試題,主要是考察self與super的

OC中調(diào)用方法,會(huì)被轉(zhuǎn)成消息發(fā)送機(jī)制的函數(shù) objc_msgSend(id self, SEL cmd, ...),因此,方法內(nèi)self與objc_msgSend()函數(shù)中的self是等價(jià)的,就是調(diào)用時(shí)傳入的消息的接受者(Objective-C高級(jí)編程 p94)。

super是一個(gè)編譯器指示符

當(dāng)使用 self 調(diào)用方法時(shí),會(huì)從當(dāng)前類的方法列表中開始找,如果沒有,就從父類中再找;而當(dāng)使用 super 時(shí),則從父類的方法列表中開始找。這種機(jī)制到底底層是如何實(shí)現(xiàn)的?

例如,當(dāng)調(diào)用[super class]時(shí),會(huì)轉(zhuǎn)為 objc_msgSendSuper(),而非objc_msgSend(),看下 objc_msgSendSuper 的函數(shù)定義:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

struct objc_super {

? ? id receiver;

? ? Class superClass;

};

當(dāng)編譯器遇到 Son 里 init 方法里的 [super class] 時(shí),開始做這幾個(gè)事:

構(gòu)建 objc_super 的結(jié)構(gòu)體變量,此時(shí)該變量的第一個(gè)成員變量 receiver 就是當(dāng)前方法內(nèi)的self(Son *)而第二個(gè)成員變量 superClass 就是指當(dāng)前方法所屬類的父類 Father

調(diào)用 objc_msgSendSuper ,將結(jié)構(gòu)體變量和 @selector(class)?傳過去

objc_msgSendSuper函數(shù)里面在做的事情類似這樣:從 objc_super 結(jié)構(gòu)體指向的 superClass 的方法列表開始找 @selector(class)?,找到后再以 objc_super->receiver 去調(diào)用這個(gè) selector,可能也會(huì)使用 objc_msgSend 這個(gè)函數(shù)

所以,當(dāng)調(diào)用[self class]時(shí),此時(shí)的self,就是init方法的接受者Son *。因此,[self class]轉(zhuǎn)為objc_msgSend(),第一個(gè)參數(shù)是Son *,第二個(gè)參數(shù)是@selector(class)。根據(jù)isa先從Son類開始找,沒有,然后到 Son的父類 Father中去找,也沒有,再去 Father 的父類 NSObject 去找,一層一層向上找之后,在 NSObject 的類中發(fā)現(xiàn)這個(gè) class 方法

- (Class)class {

? ? ?return object_getClass(self);

}

Class object_getClass(id obj)

{

? ? ? if (obj) return obj->getIsa();

? ? ? ?else return Nil;

}

self就是消息的接受者Son*,因此輸出 Son

當(dāng)使用 [super class] 時(shí),這時(shí)要轉(zhuǎn)換成 objc_msgSendSuper 的方法。先構(gòu)造 objc_super 的結(jié)構(gòu)體變量,第一個(gè)成員變量就是 self(Son *)第二個(gè)成員變量是 Father,然后要找@selector(class)?。先去 superClass 也就是 Father 中去找,沒有,然后去 Father 的父類中去找,結(jié)果還是在 NSObject 中找到了。然后內(nèi)部使用函數(shù) objc_msgSend(objc_super->receiver, @selector(class))??去調(diào)用,此時(shí)已經(jīng)和我們用 [self class] 調(diào)用時(shí)相同了,此時(shí)的 receiver 還是Son *,所以這里輸出的還是Son

其實(shí)很好理解。例如,在Son init方法中,調(diào)用[super init];,消息的接受者還是self,不然給誰初始化?只是,查找方法從父類開始了。


isKindOfClass 與 isMemberOfClass

下面代碼輸出什么?

@interface Sark : NSObject

@end

@implementation Sark

@end

int main(int argc, const char * argv[]) {

? ? ? ?@autoreleasepool {

? ? ? ? ? ? BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

? ? ? ? ? ? BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; ?

? ? ? ? ? ? ?BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];

? ? ? ? ? ? ?BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

? ? ? ? ? ? ?NSLog(@"%d %d %d %d", res1, res2, res3, res4);

? ? ? }

? ? ? ?return 0;

}

先來分析一下源碼這兩個(gè)函數(shù)的對象實(shí)現(xiàn)

類對象調(diào)用class方法,直接返回類本身

+ (Class)class {

? ? ?return self;

}

實(shí)例對象調(diào)用class方法,返回對象的isa

- (Class)class {

? ? ?return object_getClass(self);

}

Class object_getClass(id obj)

{

? ? ?if (obj) return obj->getIsa();

? ? ?else return Nil;

}

inline Class objc_object::getIsa()

{

? ? ?if (isTaggedPointer()) {

? ? ? ? uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;

? ? ? ? ?return objc_tag_classes[slot];

? ? ? }

? ? ?return ISA();

}

inline Class objc_object::ISA()

{

? ? ?assert(!isTaggedPointer());

? ? ?return (Class)(isa.bits & ISA_MASK);

}

無論是實(shí)例對象還是類對象,object_getClass(obj),均返回對象isa

類對象調(diào)用這個(gè)與元類比較,由于元類同樣的繼承關(guān)系,只要是類對象的元類是所比較元類或其子類都返回真

+ (BOOL)isKindOfClass:(Class)cls {

? ? ?//tcls 等價(jià) 于tcls != nil 因?yàn)楦惖母割愂荖SObject,NSObject的父類是nil

? ? ? ?for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {

? ?//能這么比較 tcls == cls,比較地址,也說明了 元類對象的唯一

? ? ? ? ? ? ? if (tcls == cls) return YES;

? ? ? ?}

? ? ?return NO;

}

實(shí)例對象調(diào)用與類對象比較,只要是實(shí)例對象的類是所比較元類或其子類都返回真

- (BOOL)isKindOfClass:(Class)cls {

? ? ? ?for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {

? ? ? ? ? if (tcls == cls) return YES;

? ? ? }

return NO;

//能這么比較 tcls == cls,比較地址,也說明了 類對象的唯一

}

類對象與元類對象比較,只有類對象的元類與所比較的元類相同才為真

+ (BOOL)isMemberOfClass:(Class)cls {

? ? ? return object_getClass((id)self) == cls;

}

實(shí)例對象與類對象比較,只有實(shí)例對象的類與所比較的類相同才為真

- (BOOL)isMemberOfClass:(Class)cls {

? ? ? ?return [self class] == cls;

}

這道題,除了考察這幾個(gè)方法還是考察了類與元類的繼承體系,尤其是NSObject

BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等。循環(huán),根元類的父類是NSObject,相等。res1 = YES

BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];

[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等

BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];

[Sark class]還是Sark,Sark的元類與Sark不等。循環(huán),Sark元類的父類是根元類,與Sark不等。循環(huán),根元類的父類是NSObject,與Sark不等。NSObject的父類是nil,循環(huán)結(jié)束。

BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];

Sark的元類與Sark不等

總結(jié),isMember比較傲嬌,調(diào)用者只取一次isa不等就是不等。isKindOf調(diào)用者取一次isa 不等取其父類


Class與內(nèi)存地址

下面的代碼會(huì)?Compile Error / Runtime Crash / NSLog…?

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

- (void)speak;

@end

@implementation Person

- (void)speak {

? ? ? NSLog(@"my name's %@", self.name);

}

@end

@implementation ViewController

- (void)viewDidLoad {

? ? ?[super viewDidLoad];

? ? ?id cls = [Person class];

? ? ? void *obj = &cls;

? ? ? [(__bridge id)obj speak];

}

@end

首先編譯能不能通過?其次,調(diào)用輸出什么?

答案是可以編譯通過,輸出 my name is

這道題,涉及到實(shí)例變量的內(nèi)存結(jié)構(gòu)和類與對象的關(guān)系

cls指向Sark類的指針,而obj是一個(gè)指針,存儲(chǔ)著cls的地址,也就指向obj的指針,類似一個(gè)二級(jí)指針。然后調(diào)用實(shí)例方法。這是怎么回事呢?

首先,將C語言的指針類型轉(zhuǎn)為OC的id類型,但方法也不能隨便調(diào)用,這個(gè)方法存在當(dāng)前類或者導(dǎo)入頭文件的聲明中,而我們導(dǎo)入了#import "Person.h",編譯通過。

然后,cls指向了Person類,而我們定義一個(gè)對象Person *p = [Person new]; 對象的isa也指向Person類,而實(shí)例對象的第一個(gè)成員是isa,因此實(shí)例對象的地址與isa的地址是相同的。obj指向cls,如同指向了實(shí)例對象的isa,如同指向了對象。那么,也就是cls如同一個(gè)實(shí)例對象,obj類似一個(gè)指向?qū)嵗龑ο蟮闹羔槪磒。

Person *person = [[Person alloc] init];

NSLog(@"%@",person); ?//

NSLog(@"%p %p",person,&person); ?//0x6000000098c0 0x7fff5a353a88

一個(gè)是指針?biāo)笇ο蟮刂罚粋€(gè)是指針地址

我們說過,方法最終轉(zhuǎn)為函數(shù),而函數(shù)默認(rèn)兩個(gè)參數(shù),一個(gè)是消息的接受者,一個(gè)是選擇子,對應(yīng)的是參數(shù)名是self與_cmd。我們在viewDidLoad打印幾個(gè)地址:

NSLog(@"%p",self); //0x7fe5e2e0b570

NSLog(@"%@",self); //

NSLog(@"%p??%p",&self,&_cmd); //0x7fff58e08aa8??0x7fff58e08aa0

Class cls = [Person class];

void *p = &cls;

NSLog(@"%p %p",cls,&cls);? //0x106df8140 0x7fff58e08a88

NSLog(@"%p %p",p,&p); ?//0x7fff58e08a88 0x7fff58e08a80

首先是ViewController實(shí)例對象的地址,然后是指向?qū)嵗龑ο螅赶蜻x擇子的指針地址,

NSLog(@"%p %p",cls,&cls);??打印的是cls指向的Person類的地址和cls的地址

NSLog(@"%p %p",p,&p);??打印的是p指向的cls的地址和p的地址

cls如同Person的實(shí)例對象,而p如同指向?qū)嵗龑ο蟮闹羔?,調(diào)用Person的方法

- (void)speak {

? ? ?NSLog(@"%p",&_cmd);//0x7fff58e08a40

? ? ?NSLog(@"%p??%p",self,&self);//0x7fff58e08a88??0x7fff58e08a48

? ? ?NSLog(@"my name's %@", self.name);

}

我們看到speak方法內(nèi),self指向的地址是0x7fff58e08a88,正是實(shí)例變量cls的地址。但是很明顯,這個(gè)地址7fff與Person類地址106明顯不像,一個(gè)是棧上的地址,一個(gè)是堆上的地址。

我們知道內(nèi)存,對內(nèi)存進(jìn)行編制后,由下到上,地址越來越大,??臻g在上,分配時(shí)由上到下,越后分配的地址越小,而堆空間在下,分配時(shí)由下而上,越后分配的地址越大

cls的棧地址也說明了,它不是一個(gè)真正的分配在堆上的對象,或許這是披了一件外衣。但是,只有我們知道。

打印self.name時(shí)。前面說過實(shí)例對象在類中的內(nèi)存結(jié)構(gòu)。

Person對象

isa

*name

當(dāng)一個(gè)類被編譯時(shí),實(shí)例變量的布局也就形成了,訪問類的實(shí)例變量。從對象頭部開始,實(shí)例變量依次根據(jù)自己所占空間而產(chǎn)生偏移量。

查找self.name也就是在實(shí)例變量的起始地址,偏移8個(gè)字節(jié)(64位下isa 8個(gè)字節(jié)),就得到name的首地址,而實(shí)例變量的首地址是 0x7fff58e08a88,偏移8個(gè)字節(jié)。(按字節(jié)編制,每個(gè)地址八位)

90???name

8f

8e

8d

8c

8b

8a

89

88 Person *

要去0x7fff58e08a90 查找name指針指向的字符串對象??赡敲礊槭裁磿?huì)輸出viewController相關(guān)的呢?

我們回到viewDidLoad方法,

NSLog(@"%p??%p",&self,&_cmd); //0x7fff58e08aa8??0x7fff58e08aa0

我們調(diào)用函數(shù),參數(shù)入棧,self,_cmd的地址是a8,a0,方法內(nèi),[super viewDidLoad]; 我們前面說過super是個(gè)指示符,需要構(gòu)造結(jié)構(gòu)體作為函數(shù)參數(shù)

struct objc_super {

? ? id receiver;

? ? Class superClass;

};

superClass指針是self所在類的父類 98,receiver指針指向self所指的實(shí)例對象 90,,至于為什么_cmd沒分配,我也不知道。接下來的代碼就是 Class cls = [Person class]; cls是88,void *p = &cls; p是80

這也就符合我們的打印結(jié)果,那么,我們上面尋找的90就是self了。因此,打印 my name's %@ 就是self所指的對象ViewController了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容