欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一道值得深入思考的iOS面試題詳解

 更新時(shí)間:2019年02月28日 08:36:42   作者:jackyshan  
這篇文章主要給大家分享介紹了關(guān)于一道值得深入思考的iOS面試題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

前言

最近在群里看到有人發(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)行起來會(huì):Complie error?|Runtime crash?|NSLog ?

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

過程

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

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

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

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

解析

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

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

為什么調(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對(duì)象的功能了,為什么呢?接下來我們可以看看runtime實(shí)現(xiàn)原理了,這里我只說一點(diǎn)

//對(duì)象
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詳解-簡(jiǎn)書

上述簡(jiǎn)介中部分是錯(cuò)誤的,因?yàn)檫@個(gè)只是在<objc/runtime.h>中的顯示,但是覺得直接刪除又顯現(xiàn)不出更改。因而在此專門寫出,我會(huì)在下面給出正確的解釋與數(shù)據(jù)來源

struct objc_class : objc_object {
 // Class ISA;
 Class superclass;
 cache_t cache;  // formerly cache pointer and vtable
 class_data_bits_t bits;
}

數(shù)據(jù)來源: 蘋果obj4開源代碼 第1012行 用以替換 上述簡(jiǎn)述引用中的 objc_class

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

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

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

為什么能打印出ViewController對(duì)象?

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

1.  name 這個(gè)屬性是什么時(shí)候賦的值?

2.  ViewController 這個(gè)對(duì)象是什么時(shí)候被傳入的?

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

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

該上代碼了:

@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,打印一下對(duì)象指針地址:

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

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

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

[super viewDidLoad];

NSString *str = @"11111";
 
id cls = [Spark class];

為啥要增加這行代碼呢,這步是經(jīng)過深(瞎)思(J)熟(B)慮(試),主要是考慮到函數(shù)內(nèi)部的參數(shù)生成必然會(huì)需要地方存儲(chǔ),但這部分存儲(chǔ)地址,我們是不知曉的,它的實(shí)現(xiàn)是被系統(tǒng)隱藏的。而我們的代碼又沒有明顯的設(shè)置相關(guān)代碼,那么必然是由這些條件實(shí)現(xiàn)的。所以當(dāng)我們?cè)黾恿诉@一行代碼后,不出意外的,打印結(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í)所有地址都能訪問到對(duì)應(yīng)的值。我們通過棧的默認(rèn)行為生成了一個(gè)Spark對(duì)象!

為了驗(yàn)證,我們打印一下cls和str的指針堆棧地址

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,而且正好和對(duì)象結(jié)構(gòu)體定義的一模一樣。所以這也正好能說明我們上述的打印結(jié)果My name is:11111為什么會(huì)發(fā)生。

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

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

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

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

id cls = [Cls class];
 
[super viewDidLoad];

當(dāng)我們進(jìn)行這部操作后,會(huì)發(fā)現(xiàn),執(zhí)行speak方法時(shí)崩潰了,錯(cuò)誤是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è)方法里面會(huì)傳入兩個(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對(duì)象的首地址都是指向這個(gè)對(duì)象的所屬類。這個(gè)條件是充要條件。反過來說,如果一個(gè)地址指向某個(gè)類,我們就可以把這個(gè)地址當(dāng)成對(duì)象去用。所以編譯是會(huì)通過的,也不會(huì)報(bào)unrecognized selector的錯(cuò)誤。

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

思索

這類問題,考察的東西很深,并且結(jié)合了很多知識(shí)點(diǎn)。但是當(dāng)我們拿到面試題并且能進(jìn)行思索的時(shí)候一定要好好的考慮,我對(duì)這道題的想法,也是在不斷的試驗(yàn)中逐漸的完善,并且嘗試了很多。其實(shí)找面試題為什么是這個(gè)答案的過程和,找代碼找bug的流程都是類似的,都是排除變量,逐步探索,最終將探索過程和概念結(jié)合。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 簡(jiǎn)單談?wù)凜ore Animation 動(dòng)畫效果

    簡(jiǎn)單談?wù)凜ore Animation 動(dòng)畫效果

    下面小編就為大家?guī)硪黄?jiǎn)單談?wù)凜ore Animation 動(dòng)畫效果。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • iOS自定義字體設(shè)置和系統(tǒng)自帶的字體詳解

    iOS自定義字體設(shè)置和系統(tǒng)自帶的字體詳解

    這篇文章主要給大家介紹了關(guān)于iOS自定義字體設(shè)置和系統(tǒng)自帶的字體的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • IOS 性能優(yōu)化中離屏渲染

    IOS 性能優(yōu)化中離屏渲染

    本文主要介紹了IOS 性能優(yōu)化中離屏渲染的資料,提供了幾種方法講解了優(yōu)化,有需要的小伙伴可以參考下
    2016-10-10
  • iOS之?dāng)?shù)據(jù)解析之XML解析詳解

    iOS之?dāng)?shù)據(jù)解析之XML解析詳解

    本篇文章主要介紹了iOS之?dāng)?shù)據(jù)解析之XML解析詳解,XML解析常見的兩種方式:DOM解析和SAX解析,有興趣的可以了解一下。
    2016-12-12
  • iOS UIPickerView的簡(jiǎn)單封裝示例

    iOS UIPickerView的簡(jiǎn)單封裝示例

    這篇文章主要給大家介紹了關(guān)于iOS UIPickerView的簡(jiǎn)單封裝的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位iOS開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • iOS開發(fā)UICollectionView實(shí)現(xiàn)拖拽效果

    iOS開發(fā)UICollectionView實(shí)現(xiàn)拖拽效果

    這篇文章主要為大家詳細(xì)介紹了iOS開發(fā)UICollectionView實(shí)現(xiàn)拖拽效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • iOS開發(fā)之Objective-c的Runtime理解指南

    iOS開發(fā)之Objective-c的Runtime理解指南

    這篇文章主要介紹了iOS開發(fā)之Objective-c的Runtime理解指南的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • iOS實(shí)現(xiàn)帶有縮放效果的自動(dòng)輪播圖

    iOS實(shí)現(xiàn)帶有縮放效果的自動(dòng)輪播圖

    這篇文章主要為大家詳細(xì)介紹了iOS帶有縮放效果的自動(dòng)輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法

    iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法

    在iOS編程中UILabel是一個(gè)常用的控件,下面這篇文章主要給大家介紹了關(guān)于iOS動(dòng)態(tài)調(diào)整UILabel高度的幾種方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • iOS開發(fā)傻瓜式微信支付的方法教程

    iOS開發(fā)傻瓜式微信支付的方法教程

    最近因?yàn)楣ぷ鞯男枰_發(fā)微信支付,發(fā)現(xiàn)網(wǎng)上的很多教程過于復(fù)雜,索性自己寫一篇,所以下面這篇文章主要跟大家分享了關(guān)于iOS開發(fā)傻瓜式微信支付的方法教程,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面來一起看看吧。
    2017-07-07

最新評(píng)論