iOS基礎(chǔ)知識 (一)

一、Runtime原理

Runtime是iOS核心運(yùn)行機(jī)制之一震捣,iOS App加載庫荔棉、加載類闹炉、執(zhí)行方法調(diào)用,全靠Runtime润樱,這一塊的知識個人認(rèn)為是最基礎(chǔ)的渣触,基本面試必問。

1壹若、Runtime消息發(fā)送機(jī)制

1)iOS調(diào)用一個方法時嗅钻,實際上會調(diào)用objc_msgSend(receiver, selector, arg1, arg2, ...),該方法第一個參數(shù)是消息接收者店展,第二個參數(shù)是方法名养篓,剩下的參數(shù)是方法參數(shù);
2)iOS調(diào)用一個方法時赂蕴,會先去該類的方法緩存列表里面查找是否有該方法柳弄,如果有直接調(diào)用,否則走第3)步概说;
3)去該類的方法列表里面找碧注,找到直接調(diào)用,把方法加入緩存列表糖赔;否則走第4)步萍丐;
4)沿著該類的繼承鏈繼續(xù)查找,找到直接調(diào)用放典,把方法加入緩存列表逝变;否則消息轉(zhuǎn)發(fā)流程;
很多面試者大體知道這個流程刻撒,但是有關(guān)細(xì)節(jié)不是特別清楚骨田。

  • 問他/她objc_msgSend第一個參數(shù)、第二個參數(shù)声怔、剩下的參數(shù)分別代表什么,不知道舱呻;
  • 很多人只知道去方法列表里面查找醋火,不知道還有個方法緩存列表。
    通過這些細(xì)節(jié)箱吕,可以了解一個人是否真正掌握了原理芥驳,而不是死記硬背。

2茬高、Runtime消息轉(zhuǎn)發(fā)機(jī)制

如果在消息發(fā)送階段沒有找到方法兆旬,iOS會走消息轉(zhuǎn)發(fā)流程,流程圖如下所示:


image

1)動態(tài)消息解析怎栽。檢查是否重寫了resolveInstanceMethod 方法丽猬,如果返回YES則可以通過class_addMethod 動態(tài)添加方法來處理消息宿饱,否則走第2)步;
2)消息target轉(zhuǎn)發(fā)脚祟。forwardingTargetForSelector 用于指定哪個對象來響應(yīng)消息谬以。如果返回nil 則走第3)步;
3)消息轉(zhuǎn)發(fā)由桌。這步調(diào)用 methodSignatureForSelector 進(jìn)行方法簽名为黎,這可以將函數(shù)的參數(shù)類型和返回值封裝。如果返回 nil 執(zhí)行第四步行您;否則返回 methodSignature铭乾,則進(jìn)入 forwardInvocation ,在這里可以修改實現(xiàn)方法娃循,修改響應(yīng)對象等炕檩,如果方法調(diào)用成功,則結(jié)束淮野。否則執(zhí)行第4)步捧书;
4)報錯 unrecognized selector sent to instance。
很多人知道這四步骤星,但是筆者一般會問:

  • 怎么在項目里全局解決"unrecognized selector sent to instance"這類crash经瓷?本人發(fā)現(xiàn)很多人回答不出來,說明面試者肯定是在死記硬背洞难,你都知道因為消息轉(zhuǎn)發(fā)那三步都沒處理才會報錯舆吮,為什么不知道在消息轉(zhuǎn)發(fā)里面處理呢?
  • 如果面試者知道可以在消息轉(zhuǎn)發(fā)里面處理队贱,防止崩潰色冀,再問下面試者,你項目中是在哪一步處理的柱嫌,看看其是否有真正實踐過锋恬?

二、load與initialize

1编丘、load與initialize調(diào)用時機(jī)

+load在main函數(shù)之前被Runtime調(diào)用与学,+initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實例方法和類方法的調(diào)用嘉抓。

2索守、load與initialize在分類、繼承鏈的調(diào)用順序

  • load方法的調(diào)用順序為:
    子類的 +load 方法會在它的所有父類的 +load 方法之后執(zhí)行抑片,而分類的 +load 方法會在它的主類的 +load 方法之后執(zhí)行卵佛。
    如果子類沒有實現(xiàn) +load 方法,那么當(dāng)它被加載時 runtime 是不會去調(diào)用父類的 +load 方法的。同理截汪,當(dāng)一個類和它的分類都實現(xiàn)了 +load 方法時疾牲,兩個方法都會被調(diào)用。
  • initialize的調(diào)用順序為:
    +initialize 方法的調(diào)用與普通方法的調(diào)用是一樣的挫鸽,走的都是消息發(fā)送的流程说敏。如果子類沒有實現(xiàn) +initialize 方法,那么繼承自父類的實現(xiàn)會被調(diào)用丢郊;如果一個類的分類實現(xiàn)了 +initialize 方法盔沫,那么就會對這個類中的實現(xiàn)造成覆蓋。
  • 怎么確保在load和initialize的調(diào)用只執(zhí)行一次
    由于load和initialize可能會調(diào)用多次枫匾,所以在這兩個方法里面做的初始化操作需要保證只初始化一次架诞,用dispatch_once來控制

筆者在面試過程中發(fā)現(xiàn)很多人對于load與initialize在分類、繼承鏈的調(diào)用順序不清楚干茉。對怎么保證初始化安全也不清楚

三谴忧、RunLoop原理

RunLoop蘋果原理圖


image

圖中展現(xiàn)了 Runloop 在線程中的作用:從 input source 和 timer source 接受事件,然后在線程中處理事件角虫。

1沾谓、RunLoop與線程關(guān)系

  • 一個線程是有一個RunLoop還是多個RunLoop? 一個戳鹅;
  • 怎么啟動RunLoop均驶?主線程的RunLoop自動就開啟了,子線程的RunLoop通過Run方法啟動枫虏。

2妇穴、Input Source 和 Timer Source

兩個都是 Runloop 事件的來源,其中 Input Source 又可以分為三類

  • Port-Based Sources隶债,系統(tǒng)底層的 Port 事件腾它,例如 CFSocketRef ,在應(yīng)用層基本用不到;
  • Custom Input Sources死讹,用戶手動創(chuàng)建的 Source;
  • Cocoa Perform Selector Sources瞒滴, Cocoa 提供的 performSelector 系列方法,也是一種事件源;
    Timer Source指定時器事件赞警,該事件的優(yōu)先級是最低的逛腿。
    本人一般會問定時器事件的優(yōu)先級是怎么樣的,大部分人回答不出來仅颇。

3、解決NSTimer事件在列表滾動時不執(zhí)行問題

因為定時器默認(rèn)是運(yùn)行在NSDefaultRunLoopMode碘举,在列表滾動時候忘瓦,主線程會切換到UITrackingRunLoopMode,導(dǎo)致定時器回調(diào)得不到執(zhí)行。
有兩種解決方案:

  • 指定NSTimer運(yùn)行于 NSRunLoopCommonModes下耕皮。
  • 在子線程創(chuàng)建和處理Timer事件境蜕,然后在主線程更新 UI。

四凌停、事件分發(fā)機(jī)制及響應(yīng)者鏈

1粱年、事件分發(fā)機(jī)制

iOS 檢測到手指觸摸 (Touch) 操作時會將其打包成一個 UIEvent 對象,并放入當(dāng)前活動Application的事件隊列罚拟,UIApplication 會從事件隊列中取出觸摸事件并傳遞給單例的 UIWindow 來處理台诗,UIWindow 對象首先會使用 hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖赐俗,這個過程稱之為 hit-test view拉队。
hitTest:withEvent:方法的處理流程如下:

  • 首先調(diào)用當(dāng)前視圖的 pointInside:withEvent: 方法判斷觸摸點是否在當(dāng)前視圖內(nèi);
  • 若返回 NO, 則 hitTest:withEvent: 返回 nil阻逮,若返回 YES, 則向當(dāng)前視圖的所有子視圖 (subviews) 發(fā)送 hitTest:withEvent: 消息粱快,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖(后加入的先遍歷),直到有子視圖返回非空對象或者全部子視圖遍歷完畢叔扼;
  • 若第一次有子視圖返回非空對象事哭,則 hitTest:withEvent: 方法返回此對象,處理結(jié)束瓜富;
  • 如所有子視圖都返回空鳍咱,則 hitTest:withEvent: 方法返回自身 (self)。
    流程圖如下:


    image

2食呻、響應(yīng)者鏈原理

iOS的事件分發(fā)機(jī)制是為了找到第一響應(yīng)者流炕,事件的處理機(jī)制叫做響應(yīng)者鏈原理。
所有事件響應(yīng)的類都是 UIResponder 的子類仅胞,響應(yīng)者鏈?zhǔn)且粋€由不同對象組成的層次結(jié)構(gòu)每辟,其中的每個對象將依次獲得響應(yīng)事件消息的機(jī)會。當(dāng)發(fā)生事件時干旧,事件首先被發(fā)送給第一響應(yīng)者渠欺,第一響應(yīng)者往往是事件發(fā)生的視圖,也就是用戶觸摸屏幕的地方椎眯。事件將沿著響應(yīng)者鏈一直向下傳遞挠将,直到被接受并做出處理。一般來說编整,第一響應(yīng)者是個視圖對象或者其子類對象舔稀,當(dāng)其被觸摸后事件被交由它處理,如果它不處理掌测,就傳遞給它的父視圖(superview)對象(如果存在)處理内贮,如果沒有父視圖,事件就會被傳遞給它的視圖控制器對象 ViewController(如果存在),接下來會沿著頂層視圖(top view)到窗口(UIWindow 對象)再到程序(UIApplication 對象)夜郁。如果整個過程都沒有響應(yīng)這個事件什燕,該事件就被丟棄。一般情況下竞端,在響應(yīng)者鏈中只要由對象處理事件屎即,事件就停止傳遞。
一個典型的事件響應(yīng)路線如下:
First Responser --> 父視圖-->The Window --> The Application --> nil(丟棄)
我們可以通過 [responder nextResponder] 找到當(dāng)前 responder 的下一個 responder事富,持續(xù)這個過程到最后會找到 UIApplication 對象技俐。

五、內(nèi)存泄露檢測與循環(huán)引用

1赵颅、造成內(nèi)存泄露原因

  • 在用C/C++時虽另,創(chuàng)建對象后未銷毀,比如調(diào)用malloc后不free饺谬、調(diào)用new后不delete捂刺;
  • 調(diào)用CoreFoundation里面的C方法后創(chuàng)建對對象后不釋放。比如調(diào)用CGImageCreate不調(diào)用CGImageRelease募寨;
  • 循環(huán)引用族展。當(dāng)對象A和對象B互相持有的時候,就會產(chǎn)生循環(huán)引用拔鹰。常見產(chǎn)生循環(huán)引用的場景有在VC的cellForRowAtIndexPath方法中cell block引用self仪缸。

2、常見循環(huán)引用及解決方案

1) 在VC的cellForRowAtIndexPath方法中cell的block直接引用self或者直接以_形式引用屬性造成循環(huán)引用列肢。

 cell.clickBlock = ^{
        self.name = @"akon";
    };

cell.clickBlock = ^{
        _name = @"akon";
    };

解決方案:把self改成weakSelf恰画;

__weak typeof(self)weakSelf = self;
    cell.clickBlock = ^{
        weakSelf.name = @"akon";
    };

2)在cell的block中直接引用VC的成員變量造成循環(huán)引用。

//假設(shè) _age為VC的成員變量
@interface TestVC(){

    int _age;

}
cell.clickBlock = ^{
       _age = 18;
    };

解決方案有兩種:

  • 用weak-strong dance
__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
       strongSelf->age = 18;
    };

  • 把成員變量改成屬性
//假設(shè) _age為VC的成員變量
@interface TestVC()

@property(nonatomic, assign)int age;

@end

__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
       weakSelf.age = 18;
    };

3)delegate屬性聲明為strong瓷马,造成循環(huán)引用拴还。

@interface TestView : UIView

@property(nonatomic, strong)id<TestViewDelegate> delegate;

@end

@interface TestVC()<TestViewDelegate>

@property (nonatomic, strong)TestView* testView;

@end

 testView.delegate = self; //造成循環(huán)引用

解決方案:delegate聲明為weak

@interface TestView : UIView

@property(nonatomic, weak)id<TestViewDelegate> delegate;

@end

4)在block里面調(diào)用super,造成循環(huán)引用欧聘。

cell.clickBlock = ^{
       [super goback]; //造成循環(huán)應(yīng)用
    };

解決方案片林,封裝goback調(diào)用

__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
       [weakSelf _callSuperBack];
    };

- (void) _callSuperBack{
    [self goback];
}

5)block聲明為strong
解決方案:聲明為copy
6)NSTimer使用后不invalidate造成循環(huán)引用。
解決方案:

  • NSTimer用完后invalidate怀骤;
  • NSTimer分類封裝
+ (NSTimer *)ak_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)(void))block
                                       repeats:(BOOL)repeats{

    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(ak_blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
}

+ (void)ak_blockInvoke:(NSTimer*)timer{

    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

--

3费封、怎么檢測循環(huán)引用

  • 靜態(tài)代碼分析。 通過Xcode->Product->Anaylze分析結(jié)果來處理蒋伦;
  • 動態(tài)分析弓摘。用MLeaksFinder(只能檢測OC泄露)或者Instrument或者OOMDetector(能檢測OC與C++泄露)。

六痕届、VC生命周期

考察viewDidLoad衣盾、viewWillAppear寺旺、ViewDidAppear等方法的執(zhí)行順序。
假設(shè)現(xiàn)在有一個 AViewController(簡稱 Avc) 和 BViewController (簡稱 Bvc)势决,通過 navigationController 的push 實現(xiàn) Avc 到 Bvc 的跳轉(zhuǎn),調(diào)用順序如下:
1蓝撇、A viewDidLoad
2果复、A viewWillAppear
3、A viewDidAppear
4渤昌、B viewDidLoad
5虽抄、A viewWillDisappear
6、B viewWillAppear
7独柑、A viewDidDisappear
8迈窟、B viewDidAppear
如果再從 Bvc 跳回 Avc,調(diào)用順序如下:
1忌栅、B viewWillDisappear
2车酣、A viewWillAppear
3、B viewDidDisappear
4索绪、A viewDidAppear

資料推薦

如果你正在跳槽或者正準(zhǔn)備跳槽不妨動動小手湖员,添加一下咱們的交流群931542608來獲取一份詳細(xì)的大廠面試資料為你的跳槽多添一份保障。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瑞驱,一起剝皮案震驚了整個濱河市娘摔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唤反,老刑警劉巖凳寺,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彤侍,居然都是意外死亡肠缨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門拥刻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怜瞒,“玉大人,你說我怎么就攤上這事般哼∥馔簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵蒸眠,是天一觀的道長漾橙。 經(jīng)常有香客問我,道長楞卡,這世上最難降的妖魔是什么霜运? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任脾歇,我火速辦了婚禮,結(jié)果婚禮上淘捡,老公的妹妹穿的比我還像新娘藕各。我一直安慰自己,他們只是感情好焦除,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布激况。 她就那樣靜靜地躺著,像睡著了一般膘魄。 火紅的嫁衣襯著肌膚如雪乌逐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天创葡,我揣著相機(jī)與錄音浙踢,去河邊找鬼。 笑死灿渴,一個胖子當(dāng)著我的面吹牛洛波,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逻杖,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奋岁,長吁一口氣:“原來是場噩夢啊……” “哼聂沙!你這毒婦竟也來了鹉戚?” 一聲冷哼從身側(cè)響起堡妒,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洽胶,失蹤者是張志新(化名)和其女友劉穎锹漱,沒想到半個月后担汤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睦擂,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡贯钩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年女嘲,在試婚紗的時候發(fā)現(xiàn)自己被綠了畜份。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡欣尼,死狀恐怖爆雹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愕鼓,我是刑警寧澤钙态,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站菇晃,受9級特大地震影響册倒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜磺送,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一驻子、第九天 我趴在偏房一處隱蔽的房頂上張望灿意。 院中可真熱鬧,春花似錦崇呵、人聲如沸缤剧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞭执。三九已至,卻和暖如春芒粹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背大溜。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工化漆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钦奋。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓座云,卻偏偏與公主長得像,于是被迫代替她去往敵國和親付材。 傳聞我的和親對象是個殘疾皇子朦拖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355