深度剖析:內(nèi)存管理、多線程與Runtime)
1. 寫(xiě)在前面這份題目為什么值得認(rèn)真對(duì)待先說(shuō)結(jié)論歡聚時(shí)代2018年校招iOS A卷放在今天來(lái)看依然是一份“含金量不低”的基礎(chǔ)題。它不考花哨的Swift語(yǔ)法糖不考新框架API背誦而是把重點(diǎn)壓在iOS開(kāi)發(fā)最核心的幾塊地基上——內(nèi)存管理、多線程、RunLoop、Runtime、UI布局、網(wǎng)絡(luò)層。這些東西恰恰是很多工作了兩三年的人依然說(shuō)不透的內(nèi)容。如果你正在準(zhǔn)備iOS校招或者社招這份題目適合拿來(lái)當(dāng)自測(cè)清單哪些題能脫口而出哪些題需要想一下哪些題完全沒(méi)概念這就是你當(dāng)前水平的真實(shí)剖面。我當(dāng)年刷完這份卷子后的感受是學(xué)校教的那點(diǎn)東西和實(shí)際面試要的東西之間隔著一個(gè)“系統(tǒng)性整理”的距離。這篇文章我會(huì)按照“原題涉及哪些知識(shí)點(diǎn)→每類(lèi)知識(shí)點(diǎn)的核心原理→我的答題思路和踩坑經(jīng)驗(yàn)”這個(gè)邏輯來(lái)展開(kāi)不貼原題原話畢竟涉及版權(quán)而是把題目背后考的每一個(gè)知識(shí)點(diǎn)拆開(kāi)揉碎讓你看完之后就算遇到變體題也能應(yīng)對(duì)。2. 內(nèi)存管理ARC時(shí)代為什么還要問(wèn)MRC2.1 從一道“引用計(jì)數(shù)”題說(shuō)開(kāi)去A卷里內(nèi)存管理的題目不少核心圍繞引用計(jì)數(shù)、循環(huán)引用、weak和strong的區(qū)別、block對(duì)變量的捕獲這幾塊。先說(shuō)引用計(jì)數(shù)。很多新手會(huì)覺(jué)得“現(xiàn)在都ARC了誰(shuí)還管retain和release”但面試官問(wèn)這個(gè)不是為了讓你手寫(xiě)release而是為了確認(rèn)你是否真正理解對(duì)象的生命周期。引用計(jì)數(shù)的本質(zhì)是一個(gè)整數(shù)每個(gè)OC對(duì)象內(nèi)部都有一個(gè)retainCount當(dāng)它為0時(shí)對(duì)象被釋放。ARC做的事情就是在編譯期幫你插入retain和release調(diào)用它不是一個(gè)運(yùn)行時(shí)機(jī)制而是一個(gè)編譯期機(jī)制。這一點(diǎn)很多解釋都講錯(cuò)了我在面試中反復(fù)被問(wèn)到過(guò)所以單獨(dú)拿出來(lái)強(qiáng)調(diào)。你要能說(shuō)清楚strong對(duì)應(yīng)retainweak對(duì)應(yīng)assign但會(huì)在對(duì)象釋放時(shí)自動(dòng)置nilcopy對(duì)應(yīng)retain加一次拷貝。在實(shí)際開(kāi)發(fā)中weak最常見(jiàn)的應(yīng)用場(chǎng)景就是delegate屬性和block內(nèi)部對(duì)self的弱引用前者是為了避免兩個(gè)對(duì)象互相持有后者是為了打破block對(duì)self的強(qiáng)引用閉環(huán)。2.2 循環(huán)引用最常見(jiàn)的線上崩潰來(lái)源循環(huán)引用幾乎是必考題。我給你一個(gè)標(biāo)準(zhǔn)的例子一個(gè)ViewController持有block屬性block內(nèi)部又使用了self。如果self對(duì)block是strongblock對(duì)self也是strong那兩者就互相引用誰(shuí)也釋放不掉dealloc永遠(yuǎn)不會(huì)調(diào)用內(nèi)存泄漏就產(chǎn)生了。解決辦法三個(gè)思路第一種block內(nèi)部用__weak修飾的self弱引用最常見(jiàn)也最推薦第二種在block執(zhí)行完成后把block屬性置nil強(qiáng)行打破閉環(huán)第三種如果block不需要長(zhǎng)時(shí)間持有self可以考慮用__block修飾并在block內(nèi)部置nil但這種方式在ARC下要小心因?yàn)開(kāi)_block對(duì)象的處理規(guī)則在不同版本里有變化。我踩過(guò)的一個(gè)坑是在block里調(diào)用了self的getter方法比如self.name然后編譯器提示“Block implicitly retains self”。這說(shuō)明即使用了weakSelf只要你在block里訪問(wèn)了self的某個(gè)屬性編譯器也會(huì)生成強(qiáng)引用。正確做法是在block外先定義__weak typeof(self) weakSelf self;然后在block內(nèi)再定義一個(gè)__strong typeof(weakSelf) strongSelf weakSelf;用strongSelf來(lái)操作屬性。這個(gè)“weak-strong dance”在SDK開(kāi)發(fā)、網(wǎng)絡(luò)回調(diào)里極其常見(jiàn)面試時(shí)能主動(dòng)寫(xiě)出來(lái)是加分項(xiàng)。2.3 內(nèi)存警告與ViewDidUnload2018年的題目里還有關(guān)于內(nèi)存警告處理的題。對(duì)UIViewController來(lái)說(shuō)系統(tǒng)在內(nèi)存吃緊時(shí)會(huì)調(diào)用didReceiveMemoryWarning而對(duì)應(yīng)的viewDidUnload在iOS 6之后就被廢棄了不再調(diào)用。原因是系統(tǒng)不再默認(rèn)釋放self.view而是讓開(kāi)發(fā)者自己重寫(xiě)didReceiveMemoryWarning來(lái)釋放可重建的資源比如圖片緩存、大數(shù)組、動(dòng)畫(huà)等。很多老項(xiàng)目還留著viewDidUnload的代碼在新系統(tǒng)上根本不會(huì)執(zhí)行這就造成了一個(gè)假象開(kāi)發(fā)者以為自己做了內(nèi)存釋放實(shí)際上什么都沒(méi)發(fā)生。面試?yán)锾岬竭@一點(diǎn)能體現(xiàn)你對(duì)系統(tǒng)版本差異的敏感度。3. 多線程與RunLoopiOS流暢度的底牌3.1 GCD的隊(duì)列與死鎖A卷里多線程的題目占了相當(dāng)比重尤其是GCD。GCD的核心概念就兩樣隊(duì)列和任務(wù)。隊(duì)列分串行隊(duì)列和并發(fā)隊(duì)列任務(wù)分同步任務(wù)和異步任務(wù)。排列組合一下就有四種情況但這四種情況的執(zhí)行時(shí)機(jī)和行為各不相同面試題經(jīng)常從這里出。這里最經(jīng)典的陷阱題就是“在主隊(duì)列調(diào)用sync任務(wù)會(huì)發(fā)生什么”。答案是死鎖。主隊(duì)列是串行隊(duì)列主線程上的任務(wù)必須一個(gè)接一個(gè)執(zhí)行當(dāng)你用dispatch_sync(dispatch_get_main_queue(), ^{})時(shí)相當(dāng)于在主線程正在執(zhí)行的任務(wù)中插入了一個(gè)“必須等這個(gè)block執(zhí)行完才繼續(xù)”的等待而主隊(duì)列的機(jī)制決定這個(gè)block要等當(dāng)前任務(wù)執(zhí)行完才開(kāi)始于是互相等待死鎖就產(chǎn)生了。另一個(gè)高頻考點(diǎn)是信號(hào)量dispatch_semaphore_t。它用來(lái)控制并發(fā)數(shù)的上限比如同時(shí)最多允許三個(gè)網(wǎng)絡(luò)請(qǐng)求運(yùn)行。這個(gè)在實(shí)際開(kāi)發(fā)中很有用比如批量上傳圖片時(shí)限制并發(fā)數(shù)防止內(nèi)存暴漲。3.2 RunLoop與界面卡頓的關(guān)聯(lián)RunLoop這塊面試官特別喜歡從“為什么滑動(dòng)TableView的時(shí)候NSTimer不走了”這個(gè)問(wèn)題切入。原因是RunLoop在滑動(dòng)模式下會(huì)切換到UITrackingRunLoopMode而默認(rèn)添加的Timer跑在NSDefaultRunLoopMode下滑動(dòng)時(shí)默認(rèn)模式下的任務(wù)不執(zhí)行Timer自然就不觸發(fā)。解決方案就是把Timer加到NSRunLoopCommonModes下這樣Timer在Default和Tracking兩種模式下都能跑。使用[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]即可。更深一層RunLoop的source0、source1、timer三種事件源也是面試考點(diǎn)。source0是應(yīng)用內(nèi)部事件比如觸摸事件source1是系統(tǒng)內(nèi)核事件比如端口消息timer就是定時(shí)器。理解這些對(duì)排查界面卡頓很有幫助因?yàn)榭D的本質(zhì)就是主線程RunLoop無(wú)法及時(shí)處理事件perf工具抓到的通常就是主線程執(zhí)行耗時(shí)操作時(shí)的堆棧。3.3 NSOperation與GCD的選擇NSOperationQueue和GCD的區(qū)別也是個(gè)老生常談的問(wèn)題。我總結(jié)了一張對(duì)比表面試時(shí)可以直接用維度GCDNSOperationQueue底層C語(yǔ)言API輕量高效基于GCD封裝OC對(duì)象取消任務(wù)不支持需自己寫(xiě)標(biāo)志位原生支持cancel依賴關(guān)系需用dispatch_barrier或信號(hào)量模擬原生支持addDependency最大并發(fā)數(shù)信號(hào)量控制maxConcurrentOperationCount直接設(shè)置操作狀態(tài)監(jiān)聽(tīng)需手動(dòng)管理KVO監(jiān)聽(tīng)isFinished等結(jié)論就是簡(jiǎn)單一次性任務(wù)用GCD足夠復(fù)雜任務(wù)編排、需要取消和依賴時(shí)用NSOperationQueue更合適。面試時(shí)把你的選擇邏輯講清楚面試官會(huì)覺(jué)得你是“帶著方案思考”而不是“背概念”。4. Runtime與消息機(jī)制OC動(dòng)態(tài)性的內(nèi)核4.1 objc_msgSend的調(diào)用鏈Runtime幾乎是每年必考2018年的A卷也不例外。核心考點(diǎn)就是objc_msgSend的執(zhí)行流程消息發(fā)送時(shí)系統(tǒng)會(huì)沿著對(duì)象的isa找到其類(lèi)對(duì)象在類(lèi)對(duì)象的method list里查找方法找不到就去父類(lèi)找最終到了NSObject還沒(méi)有就進(jìn)入消息轉(zhuǎn)發(fā)流程。消息轉(zhuǎn)發(fā)有三道防線動(dòng)態(tài)方法解析resolveInstanceMethod、快速轉(zhuǎn)發(fā)forwardingTargetForSelector、完整轉(zhuǎn)發(fā)methodSignatureForSelector forwardInvocation。面試官問(wèn)你“一個(gè)OC對(duì)象調(diào)用了一個(gè)不存在的方法會(huì)發(fā)生什么”你就應(yīng)該把這個(gè)完整鏈路背出來(lái)。能答到forwardInvocation這一步就已經(jīng)不錯(cuò)了。4.2 Method Swizzling的作用與風(fēng)險(xiǎn)Method Swizzling也是高頻考點(diǎn)考察點(diǎn)在于“你會(huì)不會(huì)用”以及“你知道不知道它的風(fēng)險(xiǎn)”。它利用Runtime在運(yùn)行時(shí)交換兩個(gè)方法的IMP常用于AOP場(chǎng)景比如統(tǒng)計(jì)頁(yè)面曝光、日志上傳、埋點(diǎn)。常見(jiàn)的坑有三個(gè)第一交換方法應(yīng)該在load中執(zhí)行不要在initialize里反復(fù)執(zhí)行第二交換時(shí)要注意被交換方法所在類(lèi)的繼承關(guān)系父類(lèi)子類(lèi)都要處理第三交換后不能調(diào)原方法的IMP否則會(huì)遞歸調(diào)用導(dǎo)致死循環(huán)。我自己就遇到過(guò)第三方SDK用Swizzling改變了UIViewController的viewWillAppear行為導(dǎo)致我們自己的統(tǒng)計(jì)代碼重復(fù)上報(bào)的問(wèn)題排查了很久才定位到是交換順序沖突。4.3 KVC與KVO的實(shí)現(xiàn)原理KVC鍵值編碼和KVO鍵值觀察在2018年的題目中也有涉及。KVC的原理是當(dāng)你調(diào)用setValue:forKey:時(shí)系統(tǒng)會(huì)先去查找setter方法setXxx找不到就用_xxx或xxx成員變量再找不到就調(diào)用setValue:forUndefinedKey:如果這個(gè)也沒(méi)有實(shí)現(xiàn)就拋異常。KVO的實(shí)現(xiàn)原理更有意思當(dāng)你對(duì)一個(gè)對(duì)象添加觀察者時(shí)Runtime會(huì)動(dòng)態(tài)生成一個(gè)子類(lèi)NSKVONotifying_xxx重寫(xiě)被觀察屬性的setter方法在setter里同時(shí)調(diào)用willChangeValueForKey和didChangeValueForKey然后通知觀察者。所以KVO只對(duì)KVC兼容的屬性生效直接對(duì)成員變量賦值不會(huì)觸發(fā)通知。5. UI與布局從Auto Layout到UITableView5.1 Auto Layout的優(yōu)先級(jí)與固有內(nèi)容大小A卷里有關(guān)UI布局的題主要集中在Auto Layout上。高頻考點(diǎn)一個(gè)是intrinsicContentSize固有內(nèi)容大小比如UILabel根據(jù)文字內(nèi)容、字體大小能計(jì)算自己需要的尺寸UIButton則會(huì)根據(jù)圖片和標(biāo)題計(jì)算。理解了這一點(diǎn)你就知道為什么有時(shí)候約束明明寫(xiě)得很全UILabel還是撐不開(kāi)或壓縮重疊。另一個(gè)高頻考點(diǎn)是約束的優(yōu)先級(jí)required和optional。required優(yōu)先級(jí)的約束必須全部滿足optional約束可以沖突時(shí)自動(dòng)放棄。這個(gè)在實(shí)際適配不同屏幕尺寸時(shí)很有用比如讓按鈕在“滿足寬度約束的前提下盡量貼右”就可以把貼右約束設(shè)為optional。如果只是盲目把約束寫(xiě)滿在小屏設(shè)備上就會(huì)出現(xiàn)約束沖突警告布局錯(cuò)亂。5.2 UITableView的復(fù)用機(jī)制與卡頓優(yōu)化Cell復(fù)用是必考題但A卷考得更深一點(diǎn)不僅問(wèn)“為什么復(fù)用”還問(wèn)“怎么保證數(shù)據(jù)的正確性”。復(fù)用機(jī)制高效是因?yàn)楸苊饬祟l繁創(chuàng)建和銷(xiāo)毀UIView的昂貴開(kāi)銷(xiāo)。但復(fù)用的坑在于當(dāng)你快速滑動(dòng)時(shí)一個(gè)顯示“已關(guān)注”狀態(tài)的Cell滾出屏幕后又被復(fù)用到顯示“未關(guān)注”的Cell上就會(huì)出現(xiàn)狀態(tài)錯(cuò)亂。標(biāo)準(zhǔn)解法是在cellForRowAtIndexPath里把所有的狀態(tài)按鈕文案、圖片、選中狀態(tài)全部重新賦值不能依賴Cell之前的殘留狀態(tài)。另外iOS 5之后通過(guò)注冊(cè)類(lèi)和dequeueReusableCellWithIdentifier:forIndexPath:方式獲取Cell時(shí)系統(tǒng)會(huì)自動(dòng)創(chuàng)建并走初始化無(wú)需判空。關(guān)于卡頓優(yōu)化核心是減少主線程的工作量Cell高度提前計(jì)算并緩存、圖片異步加載、避免在drawRect里做復(fù)雜操作、把離屏渲染降到最低。A卷如果問(wèn)“TableView滑動(dòng)卡頓從哪些方面優(yōu)化”這是一套完整的答法。5.3 iOS 11的Safe Area與適配2018年正好是iPhone X發(fā)布后的時(shí)間點(diǎn)A卷里出現(xiàn)了關(guān)于Safe Area的題目。iOS 11引入了SafeAreaLayoutGuide取代了之前的topLayoutGuide和bottomLayoutGuide。如果你的項(xiàng)目用Masonry這類(lèi)框架要注意把約束綁定到safeArea上否則會(huì)出現(xiàn)劉海屏下布局被狀態(tài)欄和底部橫條遮擋的問(wèn)題。一個(gè)比較實(shí)用的經(jīng)驗(yàn)是在viewDidLayoutSubviews里通過(guò)self.view.safeAreaInsets獲取安全區(qū)域的值做額外的適配邏輯比如判斷底部橫條高度來(lái)決定按鈕的偏移量。這個(gè)值在viewDidLoad里拿不到要在viewDidAppear或viewDidLayoutSubviews里才有。6. 網(wǎng)絡(luò)層從GET/POST到HTTPS與緩存策略6.1 HTTP協(xié)議基礎(chǔ)與報(bào)文結(jié)構(gòu)A卷的網(wǎng)絡(luò)題不會(huì)讓你貼一大段代碼而是問(wèn)原理性的內(nèi)容。HTTP請(qǐng)求的本質(zhì)是TCP連接上傳輸文本格式的報(bào)文報(bào)文由請(qǐng)求行、請(qǐng)求頭、空行、請(qǐng)求體四部分組成。GET和POST的區(qū)別不只在語(yǔ)義上更實(shí)際的差異是GET參數(shù)拼在URL后面有長(zhǎng)度限制POST參數(shù)放在body里容量大得多GET是冪等的POST不是冪等的。面試官經(jīng)常會(huì)追問(wèn)“POST能不能用URL傳參”答案是“能”URL上可以拼任意參數(shù)服務(wù)端也未必拒絕但這不符合規(guī)范。我傾向于回答“可以但不建議”然后把原因講清楚URL會(huì)出現(xiàn)在日志里敏感信息會(huì)泄漏URL有長(zhǎng)度限制緩存和收藏夾會(huì)把參數(shù)帶走。6.2 HTTPS握手過(guò)程與Charles抓包HTTPS的題目這些年越來(lái)越常見(jiàn)。核心是握手的簡(jiǎn)化流程客戶端先發(fā)ClientHello帶支持的加密套件列表服務(wù)端回ServerHello并附上證書(shū)客戶端驗(yàn)證證書(shū)后生成預(yù)主密鑰用服務(wù)端公鑰加密發(fā)給服務(wù)端雙方各自算出會(huì)話密鑰之后用對(duì)稱(chēng)加密通信。理解這個(gè)過(guò)程對(duì)日常開(kāi)發(fā)的幫助很大。比如你Charles抓包的時(shí)候SSL Proxying的原理就是Charles偽造了一個(gè)證書(shū)負(fù)責(zé)解密然后用你自己的證書(shū)和服務(wù)端通信負(fù)責(zé)加密相當(dāng)于中間人的角色。所以在抓包時(shí)iOS端必須安裝并信任Charles的根證書(shū)否則客戶端會(huì)驗(yàn)證證書(shū)失敗請(qǐng)求直接報(bào)錯(cuò)。我自己在這個(gè)環(huán)節(jié)踩過(guò)一個(gè)大坑App里用了證書(shū)鎖定SSL Pinning也就是客戶端內(nèi)嵌了服務(wù)端的公鑰或證書(shū)指紋此時(shí)Charles即使裝了證書(shū)也解不了包因?yàn)榭蛻舳诵r?yàn)的是自己內(nèi)置的那個(gè)證書(shū)。解決辦法是debug模式下開(kāi)放一個(gè)開(kāi)關(guān)關(guān)閉證書(shū)校驗(yàn)或者在內(nèi)置證書(shū)集合里臨時(shí)加入Charles的證書(shū)。6.3 網(wǎng)絡(luò)緩存策略與斷點(diǎn)續(xù)傳緩存策略也是常考點(diǎn)NSURLRequest的緩存策略有幾種useProtocolCachePolicy默認(rèn)、reloadIgnoringLocalCacheData忽略緩存、returnCacheDataElseLoad先用緩存沒(méi)有就請(qǐng)求、returnCacheDataDontLoad只看緩存沒(méi)有就失敗。實(shí)際開(kāi)發(fā)中我更習(xí)慣配合ETag和Last-Modified做服務(wù)端驗(yàn)證的緩存每次請(qǐng)求帶上If-None-Match如果服務(wù)端資源沒(méi)變就返回304客戶端直接使用本地緩存。這樣既保證內(nèi)容新鮮又節(jié)約流量和帶寬。斷點(diǎn)續(xù)傳涉及HTTP的Range頭格式是Range: bytes0-1023服務(wù)端支持就會(huì)返回206狀態(tài)碼和對(duì)應(yīng)分片數(shù)據(jù)。面試中如果能把這個(gè)細(xì)節(jié)答出來(lái)說(shuō)明你真的做過(guò)下載功能。7. 常見(jiàn)陷阱與備考建議7.1 三張“問(wèn)題速查表”我把這整套知識(shí)點(diǎn)整理成幾個(gè)速查表方便你在面試前快速過(guò)一遍內(nèi)存管理常見(jiàn)問(wèn)題問(wèn)題原因解決方案Block內(nèi)使用self導(dǎo)致循環(huán)引用雙方強(qiáng)引用互相持使用weakSelfstrongSelf定時(shí)器不釋放NSTimer被RunLoop持有invalidate并置nil大圖片內(nèi)存暴漲解碼后的位圖占內(nèi)存使用downsample或縮略圖集合類(lèi)持有大對(duì)象內(nèi)存峰值過(guò)高使用autoreleasepool局部釋放多線程常見(jiàn)陷阱問(wèn)題原因解決方案主隊(duì)列sync死鎖串行隊(duì)列互相等待改用async或子隊(duì)列生產(chǎn)者消費(fèi)者阻塞信號(hào)量使用不當(dāng)注意wait/signal配對(duì)線程安全數(shù)據(jù)競(jìng)爭(zhēng)多線程同時(shí)寫(xiě)可變數(shù)據(jù)加鎖或用串行隊(duì)列保護(hù)資源回收崩潰信號(hào)量或鎖在dealloc中釋放確保所有路徑都釋放UI布局常見(jiàn)異常問(wèn)題原因解決方案約束沖突警告優(yōu)先級(jí)設(shè)置不當(dāng)調(diào)整約束priorityLabel內(nèi)容截?cái)喙逃写笮∮?jì)算失效設(shè)置preferredMaxLayoutWidthSafeArea遮擋未適配安全區(qū)域綁定safeAreaLayoutGuideCell狀態(tài)錯(cuò)亂復(fù)用導(dǎo)致舊狀態(tài)殘留每次都重新賦值7.2 備考心得用“設(shè)計(jì)題”的方式學(xué)“概念題”2018年的A卷給我最大的啟發(fā)是它考的不是API的“怎么用”而是API的“為什么”。單純背結(jié)論應(yīng)付不了追問(wèn)必須真正理解底層原理才能面對(duì)變化多端的題目形式。一個(gè)建議是面對(duì)每個(gè)知識(shí)點(diǎn)都問(wèn)自己三個(gè)“為什么”。為什么ARC能自動(dòng)釋放對(duì)象因?yàn)榫幾g器插入了release調(diào)用。為什么block會(huì)捕獲局部變量因?yàn)閎lock本質(zhì)上是一個(gè)結(jié)構(gòu)體需要把變量值拷貝到結(jié)構(gòu)體內(nèi)部。為什么UILabel有固有大小因?yàn)樗芨鶕?jù)內(nèi)容計(jì)算需要的尺寸。當(dāng)你把這三個(gè)為什么都能順理成章地回答出來(lái)概念題基本就穩(wěn)了。另外我強(qiáng)烈建議用“設(shè)計(jì)題”的方式來(lái)檢驗(yàn)自己比如“讓你設(shè)計(jì)一個(gè)圖片加載框架你會(huì)怎么設(shè)計(jì)”你會(huì)自然想到內(nèi)存緩存、磁盤(pán)緩存、異步下載、解碼優(yōu)化、取消機(jī)制、優(yōu)先級(jí)調(diào)度而這些恰好覆蓋了A卷里網(wǎng)絡(luò)、多線程、內(nèi)存管理三大主題。通過(guò)一道設(shè)計(jì)題把所有知識(shí)點(diǎn)串起來(lái)比一題一題刷效果要好得多。7.3 一個(gè)過(guò)來(lái)人的真心建議最后分享一個(gè)小經(jīng)驗(yàn)。我記得當(dāng)年做這套題的時(shí)候最讓我難受的不是不會(huì)而是有些題“好像見(jiàn)過(guò)但說(shuō)不清楚”。這種感覺(jué)比完全不會(huì)還要惱火。后來(lái)我意識(shí)到原因在于學(xué)習(xí)太碎片化今天看一篇博客講RunLoop明天看一段視頻講Runtime知識(shí)沒(méi)有形成體系到用的時(shí)候自然調(diào)不出來(lái)。所以我給你的建議是花一點(diǎn)時(shí)間把iOS的知識(shí)體系畫(huà)成一張大圖分成系統(tǒng)架構(gòu)、語(yǔ)言特性、UI布局、網(wǎng)絡(luò)、存儲(chǔ)、多線程、性能優(yōu)化這幾塊每一塊再往下細(xì)分把自己會(huì)的內(nèi)容標(biāo)綠不會(huì)的標(biāo)紅然后用紅色部分反推復(fù)習(xí)計(jì)劃。這個(gè)過(guò)程很痛苦但堅(jiān)持兩周以后你面對(duì)面試題的心態(tài)會(huì)完全不同——你不會(huì)再有“考到不會(huì)的就完了”的恐慌因?yàn)槟阒肋@個(gè)紅色區(qū)域在哪個(gè)分支下你大概知道去哪里查、往哪個(gè)方向想。這份把控感比任何“面經(jīng)”都值錢(qián)。