一道值得思考的IOS面試題

前言

最近在群里看到有人發(fā)的一道面試題究驴,題目如下:

@interface Spark : NSObject 

@property(nonatomic,copy) NSString *name; 

@end

@implementation Spark

- (void)speak {
    NSLog(@"My name is:%@",self.name); 
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    id cls = [Spark class];

    void *obj = &cls;

    [(__bridge id)obj speak];
}

問題:上述代碼運(yùn)行起來會:Complie error?|Runtime crash?|NSLog ?

最終問題就是這段代碼的運(yùn)行結(jié)果。

過程

第一眼看這個(gè)問題,我直接就想說张症,這個(gè)東西啊,肯定是編譯報(bào)錯了振诬、要不就是崩潰啊

所以我就跟著寫了些代碼露乏,結(jié)果發(fā)現(xiàn):

WTF? 怎么能運(yùn)行,而且結(jié)果竟然還是

運(yùn)行結(jié)果

相信當(dāng)你看到這個(gè)結(jié)果的時(shí)候會和我一樣吃驚笛园,不和邏輯啊隘击,怎么竟然能執(zhí)行成功并且還打印出來當(dāng)前controller了侍芝,不符合常理啊。

解析

對于計(jì)算機(jī)而言埋同,不存在什么魔法州叠,如果一段代碼能運(yùn)行必然存在它的原理。

我們需要做的就是分析為什么能成功凶赁。

  1. 為什么調(diào)用不崩潰
    我們需要了解咧栗,cls的意思。

cls在C語言里虱肄,就是一個(gè)指針楼熄,這個(gè)指針的內(nèi)容指向Spark類

當(dāng)我們通過void *obj = &cls;這個(gè)語句執(zhí)行后,獲取的就是一個(gè)指向這個(gè)指針cls的指針

事實(shí)上在這一步操作實(shí)現(xiàn)后浩峡,obj 這個(gè)指針就已經(jīng)具有Object-c對象的功能了,為什么呢错敢?接下來我們可以看看runtime實(shí)現(xiàn)原理了翰灾,這里我只說一點(diǎn)

//對象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
//類
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
//方法
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

引自: iOS Runtime詳解-簡書

可以看到objc_object這個(gè)對象的首字段是isa 指向一個(gè)Class

也就是說,我們?nèi)绻幸粋€(gè)指向Class的地址的指針稚茅,相當(dāng)于這個(gè)對象就已經(jīng)可以使用了纸淮,只是像他的成員變量等等的一系列值都還沒有被初始化。

所以接下來用(__bridge id)obj亚享,調(diào)用是不會產(chǎn)生問題的

  1. 為什么能打印出ViewController對象?

這個(gè)問題就是由兩個(gè)小部分組成的

1\.  name 這個(gè)屬性是什么時(shí)候賦的值?
2\.  ViewController 這個(gè)對象是什么時(shí)候被傳入的咽块?

首先我們需要先了解一下,一個(gè)類對象的數(shù)據(jù)是如何存儲的欺税。

這里我就按照上文一樣引用很多的論證了侈沪,我們自己來探究

該上代碼了:

@interface Cls : NSObject 

@property(nonatomic,strong) NSString *test; 

@property(nonatomic,strong) NSString *test1;

@end

@implementation Cls

- (void)printPrinter {
    NSLog(@"self:%p",self);
    NSLog(@"self.test:%p",&_test);
    NSLog(@"self.test1:%p",&_test1);
}

@end

接下來調(diào)用printPrinter,打印一下對象指針地址:

[圖片上傳失敗...(image-6fd2a-1545188266404)]

可以發(fā)現(xiàn),指針偏移量成員變量和指針首地址差8個(gè)字節(jié)晚凿,每個(gè)成員變量與上一個(gè)成員變量偏移量也是8個(gè)字節(jié)亭罪。

完成到這一步,我們?nèi)匀粵]有發(fā)現(xiàn)上述兩個(gè)問題是應(yīng)該怎么解釋歼秽。但是我們知道了应役,一個(gè)Object-C 對象的指針,和它的成員變量的指針肯定是連續(xù)的燥筷。這就為接下來我們的分析提供了一些思路箩祥。

下一步,我在原本的題目中增加一行代碼:

[super viewDidLoad];

NSString *str = @"11111";

id cls = [Spark class];

為啥要增加這行代碼呢肆氓,這步是經(jīng)過深(瞎)思(J)熟(B)慮(試)袍祖,主要是考慮到函數(shù)內(nèi)部的參數(shù)生成必然會需要地方存儲,但這部分存儲地址做院,我們是不知曉的盲泛,它的實(shí)現(xiàn)是被系統(tǒng)隱藏的濒持。而我們的代碼又沒有明顯的設(shè)置相關(guān)代碼,那么必然是由這些條件實(shí)現(xiàn)的寺滚。所以當(dāng)我們增加了這一行代碼后柑营,不出意外的,打印結(jié)果變了

2018-11-29 20:49:39.254021+0800 test[1961:92498] My name is:11111

變成了 我們 上述的值村视,這一切都和猜想的差不多

于是一個(gè)基本設(shè)想就出來了:

因?yàn)闂I系牡刂方Y(jié)構(gòu)和原本類的需求地址結(jié)構(gòu)高度重合了官套,同時(shí)所有地址都能訪問到對應(yīng)的值。我們通過棧的默認(rèn)行為生成了一個(gè)Spark對象!

為了驗(yàn)證蚁孔,我們打印一下clsstr的指針堆棧地址

NSLog(@"cls address:%p str address:%p",&cls,&str);

2018-11-29 21:03:30.490989+0800 test[2129:122769] cls address:0x7ffeebf4fa00 str address:0x7ffeebf4fa08

我們可以看到他們之間相差也正好是8奶赔,而且正好和對象結(jié)構(gòu)體定義的一模一樣。所以這也正好能說明我們上述的打印結(jié)果My name is:11111為什么會發(fā)生杠氢。

注:這個(gè)存在的原因是因?yàn)楹瘮?shù)內(nèi)部變量采用的小端模式站刑,也就是將參數(shù)地址由棧區(qū)從高地址依次向低地址分配,所以我們打印cls地址會比str要小鼻百。

由此绞旅,第一個(gè)小問題就解決了,答案是因?yàn)槲覀冊谏啥褩?shù)的時(shí)候温艇,拼湊出了Spark對象的地址數(shù)據(jù)結(jié)構(gòu)格式因悲,和真正的對象地址數(shù)據(jù)結(jié)構(gòu)一樣,所以self.name就是在生成cls的那一刻起內(nèi)存地址就已經(jīng)被賦值了勺爱。

接下來到下一個(gè)問題了ViewController 是什么時(shí)候傳入的?

在這一步里我們只能把目光向cls對象生成前執(zhí)行的操作來看晃琳,[super viewDidLoad];我們只執(zhí)行了這一步操作,那必然是這個(gè)操作產(chǎn)生的結(jié)果琐鲁。為了驗(yàn)證卫旱,我們可以更改一下調(diào)用順序

id cls = [Cls class];

[super viewDidLoad];

當(dāng)我們進(jìn)行這部操作后,會發(fā)現(xiàn)围段,執(zhí)行speak方法時(shí)崩潰了誊涯,錯誤是EXC_BAC_ACCESS,說明是我們引用野指針了蒜撮。

由此也可以證實(shí)暴构,[super viewDidLoad];肯定做了一些騷操作,將ViewController的self壓入了棧區(qū)段磨。

接下來我們就需要探究究竟做了什么操作取逾,我們可以用如下的命令行代碼將ViewController.m重寫成c++代碼,然后觀看發(fā)生了什么苹支。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

我們可以發(fā)現(xiàn)原本這個(gè)方法里面會傳入兩個(gè)參數(shù)一個(gè)是self,一個(gè)是_cmd砾隅,當(dāng)我們調(diào)用[super viewDidLoad]時(shí),執(zhí)行的方法中傳入了參數(shù)self债蜜,由此將self做為一個(gè)值壓入了棧中晴埂,但是_cmd這個(gè)參數(shù)并未被使用究反,因此,沒有被壓入棧中儒洛。

至此精耐,這個(gè)問題已經(jīng)被解釋出來了。

答案

所有NSObject對象的首地址都是指向這個(gè)對象的所屬類琅锻。這個(gè)條件是充要條件卦停。反過來說,如果一個(gè)地址指向某個(gè)類恼蓬,我們就可以把這個(gè)地址當(dāng)成對象去用惊完。所以編譯是會通過的,也不會報(bào)unrecognized selector的錯誤处硬。

打印結(jié)果會是ViewController對象的原因是因?yàn)?code>cls在棧上的數(shù)據(jù)結(jié)構(gòu)符合了它作為真實(shí)的類時(shí)候的數(shù)據(jù)結(jié)構(gòu)小槐,cls.name原本地址正好是棧上ViewController對象地址,因此NSLog能打印出<ViewController >

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荷辕,一起剝皮案震驚了整個(gè)濱河市本股,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桐腌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苟径,死亡現(xiàn)場離奇詭異案站,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棘街,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蟆盐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遭殉,你說我怎么就攤上這事石挂。” “怎么了险污?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵痹愚,是天一觀的道長。 經(jīng)常有香客問我蛔糯,道長拯腮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任蚁飒,我火速辦了婚禮动壤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淮逻。我一直安慰自己琼懊,他們只是感情好阁簸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哼丈,像睡著了一般启妹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上削祈,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天翅溺,我揣著相機(jī)與錄音,去河邊找鬼髓抑。 笑死咙崎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吨拍。 我是一名探鬼主播褪猛,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羹饰!你這毒婦竟也來了伊滋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤队秩,失蹤者是張志新(化名)和其女友劉穎笑旺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馍资,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筒主,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸟蟹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乌妙。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖建钥,靈堂內(nèi)的尸體忽然破棺而出藤韵,到底是詐尸還是另有隱情,我是刑警寧澤熊经,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布泽艘,位于F島的核電站,受9級特大地震影響镐依,放射性物質(zhì)發(fā)生泄漏悉盆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一馋吗、第九天 我趴在偏房一處隱蔽的房頂上張望焕盟。 院中可真熱鬧,春花似錦、人聲如沸脚翘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽来农。三九已至鞋真,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沃于,已是汗流浹背涩咖。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留繁莹,地道東北人檩互。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像咨演,于是被迫代替她去往敵國和親闸昨。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 最近在群里看到有人發(fā)的一道面試題薄风,題目如下: 問題:上述代碼運(yùn)行起來會:Complie error?|Run...
    chouheiwa閱讀 5,250評論 15 65
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉饵较,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,719評論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,103評論 1 32
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,149評論 30 470
  • 最全的iOS面試題及答案 iOS面試小貼士 ———————————————回答好下面的足夠了-----------...
    zweic閱讀 2,702評論 0 73