一道高級iOS面試題(runtime方向)

 

 

說到iOS,要麼公司規模比較小,<=3人,不須要面試。git

其餘的,大機率要讓你刀槍棍棒十八般武藝都拿出來耍耍。github

而其中,但凡敵軍陣營中有iOSer的,又極大機率會考到 Runtime 的知識點。面試

如下,是一題 sunnyxx的一道 runtime 考題,給大夥練練手,若是掌握了,Runtime層面的初中級問題應該都不在話下~數據結構

題目來襲:

//MNPerson
@interface MNPerson : NSObject

@property (nonatomic, copy)NSString *name;

- (void)print;

@end

@implementation MNPerson

- (void)print{
    NSLog(@"self.name = %@",self.name);
}

@end

---------------------------------------------------

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    id cls = [MNPerson class];

    void *obj = &cls;

    [(__bridge id)obj print];

}

問輸出結果是啥,會不會崩潰。架構

 

最終結果:函數

self.name = <ViewController: 0x7fe667608ae0>

what?工具

  • 問題1:print 是實例方法,可是並無哪裏調用了 [MNPerson alloc]init] ??
  • 問題2: 爲啥打印了 viewController?

當前內存地址結構 - 與正常的[person print] 對比學習

 
  • person變量的指針,執行 MNPerson 實例對象
  • 實例對象的自己是個結構體,以前指向他,等價於執行結構體的第一個成員
  • 結構體的第一個成員是isa,因此能夠理解爲,person->isa
  • 因此兩個print,其實內存結構一致
    • obj -> cls -> [MNPerson Class]
    • person -> isa -> [MNPerson Class]

調用print 方法,不須要關心有沒有成員變量 _name,因此能夠理解爲,cls == isaatom

  • 函數調用,是經過查找isa,其實本質,是查找結構體的前八個字節;
  • 前八個字節正好是isa,因此這裏能夠理解爲 cls == isa,這麼理解的話,cls其實等於isa;
  • 因此能夠找獲得 MNPerson 類,就能夠找到MNPerson 類內部的方法,從而調用 print 函數

問題2:爲啥裏面打印的是 ViewControllerspa

這就須要瞭解到iOS的內存分配相關知識

內存分配

void test(){ int a = 4; int b = 5; int c = 6; NSLog(@"a = %p,b = %p,c = %p",&a,&b,&c); } --------------------------- a = 0x7ffee87e9fdc, b = 0x7ffee87e9fd8, c = 0x7ffee87e9fd4
  • 局部變量是在棧空間
  • 上圖能夠發現,a先定義,a的地址比b高,得出結論:棧的內存分配是從高地址到低地址
  • 棧的內存是連續的 (這點也很重要!!)

OC方法的本質,實際上是函數調用, 底層就是調用 objc_msgSend() 函數發送消息。

- (void)viewDidLoad {

    [super viewDidLoad];

    NSString  *test = @"666";

    id cls = [MNPerson class];

    void *obj = &cls;

    [(__bridge id)obj print];

}

以上述代碼爲例,三個變量 - test、cls、obj,都是局部變量,因此都在棧空間

棧空間是從高地址到低地址分配,因此test是最高地址,而obj是最低地址

MNPerson底層結構

struct MNPerson_IMPL{
    Class isa;
    NSString *_name;
}

- (void)print{
    NSLog(@"self.name = %@",self->_name);
}
  1. 要打印的 _name 成員變量,實際上是經過self -> 去查找;
  2. 這裏的 self,就是函數調用者;
  3. [(__bridge id)obj print]; 即經過 obj 開始找;
  4. 而找 _name ,是經過指針地址查找,找得MNPerson_IMPL 結構體
  5. 由於這裏的 MNPerson_IMPL 裏面就兩個變量,因此這裏查找 _name,就是經過 isa 的地址,跳過8個字節,找到 _name
 

而前面又說過,cls = isa,而_name 的地址 = isa往下偏移 8 個字節,因此上面的圖能夠轉成

 

_name的本質,先找到 isa,而後跳過 isa 的八個字節,就找到 _name這個變量

因此上圖輸出

self.name = 666

最先沒有 test變量的時候呢

- (void)viewDidLoad {

    [super viewDidLoad];

    id cls = [MNPerson class];

    void *obj = &cls;

    [(__bridge id)obj print];

}

[super viewDidLoad];作了什麼

底層 - objc_msgSendSuper

objc_msgSendSuper({ self, [ViewController class] },@selector(ViewDidLoad)),

等價於:

struct temp = {
    self,
    [ViewController class] 
}

objc_msgSendSuper(temp, @selector(ViewDidLoad))

因此等於有個局部變量 - 結構體 temp,

結構體的地址 = 他的第一個成員,這裏的第一個成員是self

 

因此等價於 _name = self = 當前ViewController,因此最後輸出

self.name = <ViewController: 0x7fc6e5f14970>

話外篇 super 的本質

 

**其實super的本質,不是 objc_msgSendSuper({self,[super class],@selector(xxx)}) **

而是

objc_msgSendSuper2(
{self,
[current class]//當前類
},
@selector(xxx)})

函數內部邏輯,拿到第二個成員 - 當前類,經過superClass指針找到他的父類,從superClass開始搜索,最終結果是差很少的~


題目來源:

神經病院入學考試

runtime消息機制理解

推薦👇:

  • 020 持續更新,精品小圈子每日都有新內容,乾貨濃度極高。

  • 結實人脈、討論技術 你想要的這裏都有!

  • 搶先入羣,跑贏同齡人!(入羣無需任何費用)

  • (直接搜索羣號:789143298,快速入羣)
  • 點擊此處,與iOS開發大牛一塊兒交流學習

申請即送:

  • BAT大廠面試題、獨家面試工具包,

  • 資料免費領取,包括 數據結構、底層進階、圖形視覺、音視頻、架構設計、逆向安防、RxSwift、flutter,

     
相關文章
相關標籤/搜索