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中調用方法,會被轉成消息發送機制的函數 objc_msgSend(id self, SEL cmd, ...),因此,方法內self與objc_msgSend()函數中的self是等價的,就是調用時傳入的消息的接受者(Objective-C高級編程 p94)。

super是一個編譯器指示符

當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。這種機制到底底層是如何實現的?

例如,當調用[super class]時,會轉為 objc_msgSendSuper(),而非objc_msgSend(),看下 objc_msgSendSuper 的函數定義:

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

struct objc_super {

? ? id receiver;

? ? Class superClass;

};

當編譯器遇到 Son 里 init 方法里的 [super class] 時,開始做這幾個事:

構建 objc_super 的結構體變量,此時該變量的第一個成員變量 receiver 就是當前方法內的self(Son *)而第二個成員變量 superClass 就是指當前方法所屬類的父類 Father

調用 objc_msgSendSuper ,將結構體變量和 @selector(class)?傳過去

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

所以,當調用[self class]時,此時的self,就是init方法的接受者Son *。因此,[self class]轉為objc_msgSend(),第一個參數是Son *,第二個參數是@selector(class)。根據isa先從Son類開始找,沒有,然后到 Son的父類 Father中去找,也沒有,再去 Father 的父類 NSObject 去找,一層一層向上找之后,在 NSObject 的類中發現這個 class 方法

- (Class)class {

? ? ?return object_getClass(self);

}

Class object_getClass(id obj)

{

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

? ? ? ?else return Nil;

}

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

當使用 [super class] 時,這時要轉換成 objc_msgSendSuper 的方法。先構造 objc_super 的結構體變量,第一個成員變量就是 self(Son *)第二個成員變量是 Father,然后要找@selector(class)?。先去 superClass 也就是 Father 中去找,沒有,然后去 Father 的父類中去找,結果還是在 NSObject 中找到了。然后內部使用函數 objc_msgSend(objc_super->receiver, @selector(class))??去調用,此時已經和我們用 [self class] 調用時相同了,此時的 receiver 還是Son *,所以這里輸出的還是Son

其實很好理解。例如,在Son init方法中,調用[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;

}

先來分析一下源碼這兩個函數的對象實現

類對象調用class方法,直接返回類本身

+ (Class)class {

? ? ?return self;

}

實例對象調用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);

}

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

類對象調用這個與元類比較,由于元類同樣的繼承關系,只要是類對象的元類是所比較元類或其子類都返回真

+ (BOOL)isKindOfClass:(Class)cls {

? ? ?//tcls 等價 于tcls != nil 因為根元類的父類是NSObject,NSObject的父類是nil

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

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

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

? ? ? ?}

? ? ?return NO;

}

實例對象調用與類對象比較,只要是實例對象的類是所比較元類或其子類都返回真

- (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;

}

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

- (BOOL)isMemberOfClass:(Class)cls {

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

}

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

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

[NSObject class]還是NSObject,NSObject的元類是根元類,與[NSObject class] == NSObject不等。循環,根元類的父類是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不等。循環,Sark元類的父類是根元類,與Sark不等。循環,根元類的父類是NSObject,與Sark不等。NSObject的父類是nil,循環結束。

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

Sark的元類與Sark不等

總結,isMember比較傲嬌,調用者只取一次isa不等就是不等。isKindOf調用者取一次isa 不等取其父類


Class與內存地址

下面的代碼會?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

首先編譯能不能通過?其次,調用輸出什么?

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

這道題,涉及到實例變量的內存結構和類與對象的關系

cls指向Sark類的指針,而obj是一個指針,存儲著cls的地址,也就指向obj的指針,類似一個二級指針。然后調用實例方法。這是怎么回事呢?

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

然后,cls指向了Person類,而我們定義一個對象Person *p = [Person new]; 對象的isa也指向Person類,而實例對象的第一個成員是isa,因此實例對象的地址與isa的地址是相同的。obj指向cls,如同指向了實例對象的isa,如同指向了對象。那么,也就是cls如同一個實例對象,obj類似一個指向實例對象的指針,即p。

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

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

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

一個是指針所指對象地址,一個是指針地址

我們說過,方法最終轉為函數,而函數默認兩個參數,一個是消息的接受者,一個是選擇子,對應的是參數名是self與_cmd。我們在viewDidLoad打印幾個地址:

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實例對象的地址,然后是指向實例對象,指向選擇子的指針地址,

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

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

cls如同Person的實例對象,而p如同指向實例對象的指針,調用Person的方法

- (void)speak {

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

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

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

}

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

我們知道內存,對內存進行編制后,由下到上,地址越來越大,??臻g在上,分配時由上到下,越后分配的地址越小,而堆空間在下,分配時由下而上,越后分配的地址越大

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

打印self.name時。前面說過實例對象在類中的內存結構。

Person對象

isa

*name

當一個類被編譯時,實例變量的布局也就形成了,訪問類的實例變量。從對象頭部開始,實例變量依次根據自己所占空間而產生偏移量。

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

90???name

8f

8e

8d

8c

8b

8a

89

88 Person *

要去0x7fff58e08a90 查找name指針指向的字符串對象??赡敲礊槭裁磿敵鰒iewController相關的呢?

我們回到viewDidLoad方法,

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

我們調用函數,參數入棧,self,_cmd的地址是a8,a0,方法內,[super viewDidLoad]; 我們前面說過super是個指示符,需要構造結構體作為函數參數

struct objc_super {

? ? id receiver;

? ? Class superClass;

};

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容