iOS Runtime經(jīng)典面試題解析

面試題

這道面試題如下泣侮,問最后print方法能不能調(diào)用成功涂滴?如果能最后打印什么?

@interface Person: NSObject
@property (copy,nonatomic) NSString * name;
-(void)print;
@end
  
@implementation Person
-(void)print{
    NSLog(@"my name is %@",self.name);
}
@end
  
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void * obj = &cls;
    [(__bridge id)obj print];
}
@end

這是一道非常好的面試題躬贡,主要考察了iOS底層的函數(shù)調(diào)用機制以及函數(shù)調(diào)用棧的問題。

解答

pint可以調(diào)用成功

首先我們先看print函數(shù)是否可以調(diào)用成功的問題眼坏,[Person class]返回的是Person的類對象,在底層類對象是一個結(jié)構(gòu)體,結(jié)構(gòu)體里首個變量是isa指針宰译,接下來是superclass指針檐蚜,cache的方法緩存指針以及具體的類信息指針,都指向的是具體結(jié)構(gòu)體沿侈,類對象在底層的結(jié)構(gòu)體結(jié)構(gòu)大概如下:

struct objc_class {
  Class isa;
  Class superclass;
  cache_t cache;
  class_data_bits bits;
}
struct class_rw_t {
  units32_t flags;
  units32_t version;
  const class_ro_t *ro;
  method_list_t *methods;// 方法列表
  property_list_t *properties;//屬性列表
  const protocol_list_t *protocols;//協(xié)議列表
  Class firstSubclass;
  Class nextSiblingClass;
  char *demangledName;
}
struct class_ro_t {
  unit32_t flags;
  unit32_t instanceStart;
  unit32_t instanceSize;
  #ifdef __LP64__
  unit32_t reserved;
  #endif
  const unit8_t *ivarLayout;
  const char *name;//類名
  method_list_t *baseMethodlist;
  protocol_list_t *baseProtocols;
  const ivar_list_t *ivars;//成員變量列表
  const unit8_t *weakIvarLayout;
  property_list_t *baseProperties;
}

從上面的代碼可以看出來obj存放的是cls的地址闯第,同時cls的地址指向的是Person類對象。根據(jù)Runtime的底層原理缀拭,iOS的函數(shù)調(diào)用在底層是通過objc_msgSend函數(shù)來給函數(shù)調(diào)用者發(fā)送消息咳短,objc_msgSend的執(zhí)行流程有三大階段:

  • 消息發(fā)送

  • 動態(tài)方法解析

  • 消息轉(zhuǎn)發(fā)

這里我們重點看消息發(fā)送階段,下面這張經(jīng)典的圖基本闡述了消息發(fā)送階段:

首先實例通過isa指針找到類對象蛛淋,查看類對象的方法列表里是否存在對應的方法咙好,如果沒有則通過類對象里面的superclass找到其父類的類對象,在父類對象的類對象里繼續(xù)查找褐荷,直至到頂層的NSObject勾效。

那么在回頭看看[(__bridge id)obj print]的調(diào)用,同樣是消息發(fā)送叛甫,首先找到objisa指針层宫,從上面的分析可以看出來,obj的前8個字節(jié)存放的應該就是isa指針其监,因為不管是實例對象還是類對象其底層的struct結(jié)構(gòu)體中萌腿,前8個字節(jié)就是isa指針,由于obj存放的是cls的地址抖苦,所以這里的isa其實就是cls的地址毁菱,而這個isa指向的是Person的類對象,所以最后能在Person類對象的方法列表中找到print方法睛约。

最后的打印

既然能調(diào)用方法鼎俘,那么最后打印什么呢?其實最后需要確定的是self->_name這個成員變量的值是什么辩涝。在底層實例對象的成員變量是緊挨著isa指針的贸伐,在isa指針的下面的一段連續(xù)的存儲空間中,所以我們需要弄清楚上面的isa指針緊挨著的8個字節(jié)的存儲空間中到底是什么怔揩?

椬叫希空間

viewDidLoad調(diào)用時會開辟一段棧空間在作為函數(shù)調(diào)用的臨時空間商膊,函數(shù)調(diào)用完畢后就回收此空間伏伐,當然函數(shù)調(diào)用時里面的局部變量也是存在這個棧空間里的晕拆,里面的[super viewDidLoad]會繼續(xù)開辟一段椕牯幔空間,二段棧空間是連續(xù)的吝镣,椀唐鳎空間的回收是先開辟的后回收,這也符合棧數(shù)據(jù)結(jié)構(gòu)的特點末贾,[super viewDidLoad]方法在底層是通過objc_msgSendSuper2來調(diào)用的闸溃,其需要接受二個參數(shù):

  • struct objc_super2
  • SEL

其中objc_super2結(jié)構(gòu)體如下:

struct objc_super2 {
  id receiver;
  Class current_class;
}

receiver是消息接受者,current_classreceiverClass對象拱撵,由于此結(jié)構(gòu)體要當參數(shù)傳入方法辉川,所以在開辟的棧空間內(nèi)會存放receivercurrent_class這二個臨時變量拴测,在這里receiverself乓旗,current_classViewController class脊框。由于椫弁空間是從高地址到地址的,占空間的內(nèi)部大致如下:

最后按照上面尋找成員變量的方式腮郊,跳過isa指針就是成員變量抄谐,由于上面已經(jīng)分析指導isa指針就是cls渺鹦,所以self就是找到的第一個成員變量,由于person只有一個成員變量_name蛹含,所以這里self就等于_name這個成員變量毅厚,最后的打印結(jié)果為:my name is <ViewController: 0x13a110720>

調(diào)試打印

首先我們打印出obj的地址值浦箱,然后打印后面的連續(xù)48字節(jié)的地址吸耿,分別打印地址的內(nèi)容:

這很好的證明了cls后面的8個字節(jié)存儲的是viewController,在往后8個字節(jié)存放的是viewController的類對象酷窥。

思考

如果代碼為下面這種情況打印什么咽安?

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString * str = @"mamba";
    id cls = [Person class];
    void * obj = &cls;
    [(__bridge id)obj print];
    
}

通過上面的分析,str這個字符串局部變量會緊挨著cls的地址蓬推,所以最后輸出是my name is mamba,如果注釋掉

[super viewDidLoad]的調(diào)用沸伏,則會發(fā)生壞內(nèi)存訪問,程序崩潰毅糟。

總結(jié)

本文根據(jù)一個實際的面試題來回復了Runtime中的函數(shù)調(diào)用消息機制以及函數(shù)調(diào)用棧的相關(guān)知識,通過這個面試題能到加深對iOS底層知識的理解姆另。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喇肋,一起剝皮案震驚了整個濱河市坟乾,隨后出現(xiàn)的幾起案子苟蹈,更是在濱河造成了極大的恐慌,老刑警劉巖慧脱,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贺喝,居然都是意外死亡菱鸥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門躏鱼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹊漠,你說我怎么就攤上這事∏牛” “怎么了畔师?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長看锉。 經(jīng)常有香客問我,道長伯铣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任焚鲜,我火速辦了婚禮,結(jié)果婚禮上恃泪,老公的妹妹穿的比我還像新娘犀斋。我一直安慰自己贝乎,他們只是感情好叽粹,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布却舀。 她就那樣靜靜地躺著锤灿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪但校。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天状囱,我揣著相機與錄音,去河邊找鬼袭艺。 笑死,一個胖子當著我的面吹牛猾编,可吹牛的內(nèi)容都是我干的升敲。 我是一名探鬼主播答倡,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼苇羡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了设江?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤叉存,失蹤者是張志新(化名)和其女友劉穎度帮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笨篷,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡率翅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了冕臭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燕锥。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悯蝉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鼻由,到底是詐尸還是另有隱情,我是刑警寧澤蕉世,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布讨彼,位于F島的核電站,受9級特大地震影響哈误,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜜自,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一卢佣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虚茶,春花似錦、人聲如沸嘹叫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至消约,卻和暖如春员帮,著一層夾襖步出監(jiān)牢的瞬間或粮,已是汗流浹背集侯。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工帜消, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浓体,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓命浴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親生闲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353