點(diǎn)全解析:從Runtime到性能優(yōu)化)
求職旺季又到了最近后臺(tái)私信里全是關(guān)于 iOS 面試的問題。有的是剛轉(zhuǎn)行的小白問“Runtime 到底要不要死記”有的是工作三年的老手問“怎么跟面試官聊架構(gòu)設(shè)計(jì)不翻車”。說實(shí)話iOS 面試考察的東西年年都在變從早期的 OC 語法細(xì)節(jié)到后來 Swift 成為主賽道再到如今性能優(yōu)化、架構(gòu)設(shè)計(jì)、底層原理幾乎成了高級(jí)崗的標(biāo)配關(guān)卡。我整理了這幾年面試別人和被別人面試的經(jīng)驗(yàn)挑出最有代表性的題目按知識(shí)塊拆開講。這篇文章盡量不堆概念術(shù)語重點(diǎn)講清楚每道題背后的原理、答題思路和容易踩的坑希望能幫你在面試前把知識(shí)體系捋順。1. 基礎(chǔ)語言與運(yùn)行時(shí)面試必問的第一關(guān)1.1 Objective-C 與 Runtime 底層機(jī)制面試官只要問到 OC幾乎繞不開 Runtime。很多人背了“消息發(fā)送、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)”三部曲但真被問到底層流程時(shí)就說不太清了。你需要清楚OC 的方法調(diào)用本質(zhì)上不是直接調(diào)用而是發(fā)送消息。編譯時(shí)編譯器會(huì)把[obj doSomething]轉(zhuǎn)換成objc_msgSend(obj, selector(doSomething))然后運(yùn)行時(shí)系統(tǒng)根據(jù)對(duì)象的 isa 指針找到所屬類再沿著繼承鏈去查方法列表。這個(gè)過程的關(guān)鍵在于緩存機(jī)制。每個(gè)類都有 method_cache第一次查找比較慢查到之后會(huì)緩存起來后續(xù)再調(diào)用同一方法就直接命中緩存速度接近函數(shù)指針調(diào)用。這就是為什么 Runtime 的 objc_msgSend 在熱路徑上能保持高性能的原因。面試時(shí)你把這個(gè)鏈路講清楚比單純背“消息發(fā)送三步走”要有說服力得多。關(guān)于消息轉(zhuǎn)發(fā)機(jī)制我建議你把它拆成三個(gè)環(huán)節(jié)來答第一步動(dòng)態(tài)方法解析resolveInstanceMethod第二步快速轉(zhuǎn)發(fā)路徑forwardingTargetForSelector第三步完整轉(zhuǎn)發(fā)路徑methodSignatureForSelector 和 forwardInvocation。面試官追問“如果 target 是 nil 會(huì)怎樣”時(shí)你要能答出消息會(huì)被忽略并解釋為什么 nil 發(fā)消息不會(huì)崩潰因?yàn)?objc_msgSend 對(duì) nil 做了空指針判斷。這些細(xì)節(jié)在實(shí)戰(zhàn)中排查崩潰時(shí)非常有用。另一個(gè)高頻考點(diǎn)是 KVO 的實(shí)現(xiàn)原理。KVO 本質(zhì)上是通過 Runtime 動(dòng)態(tài)生成子類并重寫被觀察屬性的 setter 來實(shí)現(xiàn)的。當(dāng)你對(duì)一個(gè)對(duì)象添加觀察者時(shí)系統(tǒng)會(huì)生成一個(gè)NSKVONotifying_XXX的子類把對(duì)象的 isa 指針指向這個(gè)子類然后在 setter 里調(diào)用willChangeValueForKey和didChangeValueForKey。這也解釋了為什么直接修改成員變量不會(huì)觸發(fā) KVO因?yàn)楦緵]有走 setter。你可以借此聊一聊 KVO 的自動(dòng)移除機(jī)制、上下文參數(shù)的作用以及 iOS 11 之前觀察者忘記移除會(huì)崩潰的坑。1.2 Swift 語言的核心考點(diǎn)Swift 和 OC 的考察側(cè)重點(diǎn)不一樣。Swift 面試更看重值類型與引用類型的理解、可選型設(shè)計(jì)和泛型協(xié)議編程。一個(gè)經(jīng)典問題結(jié)構(gòu)體和類的本質(zhì)區(qū)別是什么不能只答“結(jié)構(gòu)體是值類型類是引用類型”要深入說到寫時(shí)復(fù)制Copy-on-Write說到結(jié)構(gòu)體在數(shù)組、字典等集合中使用時(shí)的內(nèi)存行為說到 Swift 中 String、Array、Dictionary 這些標(biāo)準(zhǔn)庫(kù)類型其實(shí)都是結(jié)構(gòu)體。Swift 的可選型也是必問項(xiàng)。面試官通常會(huì)問 Optional 的內(nèi)存布局和底層實(shí)現(xiàn)。Optional 本質(zhì)是枚舉some和none兩種情況。而OptionalInt在內(nèi)存里的實(shí)際占用因?yàn)椴捎昧嗣杜e的優(yōu)化策略一個(gè)字節(jié)的 tag 和值本身的內(nèi)存可以做到條件優(yōu)化Int?在大部分情況下和Int占用相同內(nèi)存。能答到這個(gè)層次說明你對(duì) Swift 編譯器有一定理解。閉包捕獲變量是另一個(gè)容易翻車的考點(diǎn)。Swift 閉包捕獲的是變量本身捕獲列表里寫[weak self]可以避免循環(huán)引用。面試官經(jīng)常給一段代碼問打印結(jié)果是多少考察閉包對(duì)變量的捕獲時(shí)機(jī)。我記得有個(gè)經(jīng)典題目for 循環(huán)里創(chuàng)建閉包循環(huán)結(jié)束后調(diào)用閉包打印的 i 是最后一個(gè)值還是當(dāng)前值。這牽涉到捕獲列表和逃逸閉包的概念答錯(cuò)了會(huì)很傷。建議你在 Xcode Playground 里把這些場(chǎng)景親手跑一遍比自己空想靠譜得多。Swift 的泛型和面向協(xié)議編程POP在面試中也越來越常見。你要能說出為什么 Swift 提倡 Protocol-Oriented Programming而不是濫用繼承能說出使用 associatedtype 時(shí)怎么處理泛型約束能解釋some和any關(guān)鍵字在 Swift 5.7 之后的作用。這不僅是語言特性題更是架構(gòu)設(shè)計(jì)題的引子。2. 內(nèi)存管理與并發(fā)考察工程能力的硬骨頭2.1 ARC、循環(huán)引用與內(nèi)存泄漏排查內(nèi)存管理在 iOS 面試中出現(xiàn)的頻率非常高幾乎每個(gè)崗位都會(huì)問到。首先要理清 ARC 的工作機(jī)制ARC 不是垃圾回收而是在編譯期插入 retain/release 代碼。編譯器根據(jù)對(duì)象的引用計(jì)數(shù)生命周期在合適的位置生成內(nèi)存管理代碼。循環(huán)引用是最常見的考察點(diǎn)。你需要能舉出至少五種循環(huán)引用場(chǎng)景代理屬性用 weak、block 里引用 self、NSTimer 對(duì) target 強(qiáng)持有、自定義 delegate 沒設(shè) weak、以及底層 Gravatar 之類的單例引用外層對(duì)象。每一種都要能說清楚為什么產(chǎn)生循環(huán)、怎么解決。比如 NSTimer 的坑那就是 target 被 timer 強(qiáng)持有timer 又被 runloop 持有你無法在 dealloc 里 invalidate timer因?yàn)?dealloc 根本不會(huì)觸發(fā)。經(jīng)典解法是改用 block 方式創(chuàng)建 timer或者在 viewWillDisappear 里手動(dòng) invalidate。面試官如果問“怎么排查循環(huán)引用”你要能說出 Instruments 的 Leaks 工具、Debug Memory Graph 的使用方法。Memory Graph 是真的好用在 Xcode 里直接點(diǎn)內(nèi)存圖標(biāo)的層級(jí)關(guān)系可以清楚地看到對(duì)象的持有鏈條哪里出現(xiàn)了環(huán)一目了然。還能在 console 里輸入po object來查看對(duì)象的持有者信息。這個(gè)技能在線上問題排查效率非常高。Autoreleasepool 也是容易被忽視的考點(diǎn)。面試官會(huì)問ARC 下為什么要用 autoreleasepool什么時(shí)候需要手動(dòng)創(chuàng)建 autoreleasepool你需要理解autoreleasepool創(chuàng)建一個(gè)自動(dòng)釋放池作用域在作用域結(jié)束時(shí)向池內(nèi)對(duì)象發(fā)送 release。在 for 循環(huán)大量創(chuàng)建臨時(shí)對(duì)象、或者處理圖片像素?cái)?shù)據(jù)時(shí)手動(dòng)添加 autoreleasepool 可以及時(shí)釋放內(nèi)存峰值?;卮饡r(shí)最好結(jié)合一個(gè)具體例子比如加載幾百兆的 data 轉(zhuǎn) UIImage如果不在循環(huán)內(nèi)加釋放池內(nèi)存會(huì)一路飆升直到被系統(tǒng)殺掉。2.2 多線程、GCD 與鎖iOS 并發(fā)編程的考法特別多光是 GCD 就能問出十幾種變體。核心要掌握幾個(gè)方面隊(duì)列的類型、同步/異步的區(qū)別、任務(wù)執(zhí)行順序、線程死鎖、信號(hào)量。經(jīng)典的必問題主隊(duì)列用 sync 會(huì)怎樣答案很明確——會(huì)死鎖。因?yàn)橹麝?duì)列是串行隊(duì)列sync 往當(dāng)前正在執(zhí)行任務(wù)的隊(duì)列里提交一個(gè)新任務(wù)而這個(gè)任務(wù)要等當(dāng)前任務(wù)結(jié)束才能執(zhí)行當(dāng)前任務(wù)又在等待新任務(wù)完成形成互相等待于是死鎖。串行隊(duì)列、并發(fā)隊(duì)列、全局隊(duì)列、主隊(duì)列四者的區(qū)別一定要清楚。還要理解 barrier 的作用在一個(gè)并發(fā)隊(duì)列里用dispatch_barrier_async提交的任務(wù)會(huì)等待之前所有任務(wù)完成然后獨(dú)占執(zhí)行之后的任務(wù)再繼續(xù)非常適合實(shí)現(xiàn)讀寫鎖。另外一個(gè)高頻考點(diǎn)是dispatch_group多個(gè)網(wǎng)絡(luò)請(qǐng)求并發(fā)發(fā)起等全部完成后統(tǒng)一刷新 UI這就是典型的 group 場(chǎng)景。你可以順便提一下dispatch_group_enter和leave需要成對(duì)出現(xiàn)避免 group 永久等待。鎖相關(guān)的題目通常出現(xiàn)在高級(jí)崗。自動(dòng)用 synchronized、NSLock、NSRecursiveLock、os_unfair_lock、信號(hào)量都能實(shí)現(xiàn)線程安全但要能說清區(qū)別和適用場(chǎng)景。os_unfair_lock是現(xiàn)在性能最好且推薦的方案但注意它不能遞歸加鎖用不好會(huì)死鎖。NSRecursiveLock允許同一線程多次加鎖解決遞歸時(shí)的死鎖問題。面試時(shí)被問到“怎么保證同一資源只被一個(gè)線程寫”你可以給出讀寫鎖場(chǎng)景下的多種解法再說明各自的代價(jià)這樣的答案水平比只背一種方案要高很多。3. UI 渲染與屏幕適配區(qū)分初級(jí)和高級(jí)的分水嶺3.1 事件傳遞、響應(yīng)鏈與渲染機(jī)制UI 相關(guān)的問題最容易檢驗(yàn)一個(gè)開發(fā)者的工程功底。最常見的題目點(diǎn)擊屏幕上一個(gè) view事件是怎樣傳遞到它的從UIApplication開始事件通過hitTest:withEvent:方法層層下發(fā)從 window 到根視圖再?gòu)母晥D向下遍歷子視圖返回離用戶手指最近且能響應(yīng)的那個(gè)視圖。這個(gè)過程可以理解為“從上到下找再?gòu)南碌缴蟼鳌薄业臅r(shí)候從根視圖往子視圖遞歸傳給的時(shí)候從命中視圖往父視圖冒泡。面試官如果讓你“點(diǎn)擊某個(gè)區(qū)域讓下層按鈕響應(yīng)”就是考察響應(yīng)鏈的干預(yù)方法。常見解法包括重寫 hitTest 返回指定視圖、在子視圖的 pointInside 里做區(qū)域判斷、或者在響應(yīng)鏈中讓父視圖實(shí)現(xiàn)相應(yīng)的方法。每種做法的效果和局限不一樣選哪種要看具體場(chǎng)景。渲染機(jī)制上的問題就更深一層了。iOS 視圖渲染的完整流程屏幕顯示內(nèi)容由 GPU 渲染Core Animation 在每一幀開始前通過 CADisplayLink 觸發(fā)布局和繪制。你需要知道 display link、RunLoop 的 Observer 在即將休眠前的時(shí)機(jī)做了哪些事這是 main runloop 中kCFRunLoopBeforeWaiting階段提交 transaction 的地方。理解了這個(gè)才能理解為什么過大的視圖層級(jí)或復(fù)雜的離屏渲染會(huì)導(dǎo)致卡頓。3.2 Auto Layout 性能與布局優(yōu)化有關(guān) Auto Layout 的問題常踩的坑是“為什么不卡但約束多到一定程度就明顯掉幀”。原因是每條約束都會(huì)被 SolverCassowary 求解器轉(zhuǎn)換成線性方程約束太多時(shí) CPU 在布局階段的計(jì)算量會(huì)成指數(shù)增長(zhǎng)。面試時(shí)主動(dòng)把這個(gè)痛點(diǎn)拿出來分析能加分。接著給出優(yōu)化方向能不用約束的地方盡量用 frame 定位列表 cell 高度固定時(shí)提前算好復(fù)雜卡片將局部?jī)?nèi)容用 stack view 包裹減少頂級(jí)約束的數(shù)量iOS 12 之后的改進(jìn)是降低了某些系統(tǒng)的布局緩存開銷但你仍然不能任性地堆約束。自定義 View 的 layoutSubviews 和繪制關(guān)系也是??嫉摹C嬖嚬賳枴皊etNeedsLayout 和 layoutIfNeeded 的區(qū)別”時(shí)簡(jiǎn)單答“前者標(biāo)記需要布局后者立即布局”不夠要說明調(diào)用時(shí)機(jī)以及在動(dòng)畫里怎么用。典型場(chǎng)景調(diào)整約束后要刷新 frame得調(diào)用layoutIfNeeded并且要在動(dòng)畫 block 里先調(diào)用一次再修改約束再調(diào)一次layoutIfNeeded動(dòng)畫才會(huì)平滑過渡。不這么寫的話界面會(huì)瞬間跳到新 frame根本看不出動(dòng)畫效果。還有一個(gè)高頻題是離屏渲染。必須說清楚什么是離屏渲染、為什么會(huì)影響性能、哪些情況會(huì)觸發(fā)。cornerRadius masksToBounds 組合、shadowPath 缺省、組透明度、文字描邊都會(huì)造成離屏渲染。把這些場(chǎng)景和解決方案用 UIBezierPath 畫圓角、光柵化、用陰影路徑答全了基本能過關(guān)。4. 網(wǎng)絡(luò)、數(shù)據(jù)存儲(chǔ)與安全不問就可惜的送分題4.1 網(wǎng)絡(luò)層高頻考點(diǎn)網(wǎng)絡(luò)層題目相對(duì)固定但變化也很多。HTTP 和 HTTPS 的區(qū)別是基礎(chǔ)中的基礎(chǔ)要能說清楚 HTTPS 的 TLS 握手過程對(duì)稱加密和非對(duì)稱加密分別承擔(dān)什么角色。面試官喜歡順著你的回答繼續(xù)追問證書是用來防什么的中間人攻擊怎么防客戶端證書校驗(yàn)怎么做特別是后者在金融類 App 的面試?yán)飵缀醣貑?。你要能說出 HTTPS 抓包工具為什么在開了 SSL Pinning 之后就抓不到數(shù)據(jù)了因?yàn)榭蛻舳诵r?yàn)了證書的公鑰或指紋。GET 和 POST 的區(qū)別看起來簡(jiǎn)單但很多人停留在“GET 參數(shù)在 URL 上POST 在 body 里”這個(gè)層。更全面的回答應(yīng)該包括冪等性、可緩存性、報(bào)文結(jié)構(gòu)差異和實(shí)際應(yīng)用場(chǎng)景。這題還能引申出網(wǎng)絡(luò)層的設(shè)計(jì)思想RESTful 風(fēng)格如何選擇方法冪等接口如何設(shè)計(jì)。Cookie 與 Session 的關(guān)系、Token 的優(yōu)劣對(duì)比也是??純?nèi)容。要能說明 HTTP 是無狀態(tài)協(xié)議為了維持登錄態(tài)才有了 Session-Cookie 機(jī)制以及移動(dòng)端現(xiàn)在更常用 Token 或 JWT 的原因。加上 App 斷網(wǎng)重試、超時(shí)時(shí)間設(shè)置、DNS 解析優(yōu)化、HTTP/2 多路復(fù)用等內(nèi)容把這些串成一個(gè)體系來答面試官會(huì)對(duì)你刮目相看。4.2 數(shù)據(jù)存儲(chǔ)選型與數(shù)據(jù)庫(kù)設(shè)計(jì)本地存儲(chǔ)類問題要分清場(chǎng)合回答。UserDefaults 適合存輕量級(jí)偏好配置不能存大數(shù)據(jù)Keychain 適合存密碼和敏感 token系統(tǒng)級(jí)加密且卸載重裝仍保留文件存儲(chǔ)適合緩存圖片、音頻等大資源數(shù)據(jù)庫(kù)適合結(jié)構(gòu)化和需要查詢的數(shù)據(jù)。SQLite 在面試中的考察頻率不低。最好能手寫標(biāo)準(zhǔn)的增刪改查語句并說清楚索引的利與弊。索引能加速查詢但會(huì)拖慢插入和更新因?yàn)槊看螌懖僮鞫家骄S護(hù)索引。答到這里面試官可能會(huì)接著問“索引為什么能加速”、“什么情況索引會(huì)失效”你得了解 B-Tree 的基本結(jié)構(gòu)。復(fù)雜的業(yè)務(wù)場(chǎng)景可以聊 FMDB、WCDB、CoreData 的選型對(duì)比說明 WCDB 的 WINQ 語法為什么能避免拼 SQL 字符串容易出錯(cuò)的問題以及它的加密能力。存儲(chǔ)方案的考察經(jīng)常和架構(gòu)題綁定。舉例一款 IM 聊天 App 的消息記錄需要快速查詢、多條件篩選還要考慮分頁(yè)。這時(shí)候直接拋 SQLite 是沒問題的但要說清楚表結(jié)構(gòu)、索引字段、分頁(yè)方式用時(shí)間戳游標(biāo)而不是 offset以及消息去重策略。這類擴(kuò)展性回答比背概念更體現(xiàn)經(jīng)驗(yàn)。4.3 簽名機(jī)制與 App 安全iOS 的簽名機(jī)制也是面試官鐘愛的題因?yàn)樗軝z驗(yàn)?zāi)闶欠裾嬲斫?App 從開發(fā)到上架的整個(gè)流程。你需要能畫出大致的簽名原理圖Mac 本地生成密鑰對(duì)用私鑰對(duì) App 的摘要信息簽名然后將證書和描述文件打包進(jìn) .ipa設(shè)備安裝時(shí)用公鑰驗(yàn)證簽名同時(shí)向 Apple 服務(wù)器校驗(yàn)描述文件和設(shè)備 ID。追問環(huán)節(jié)常見的坑為什么會(huì)報(bào)“未受信任的企業(yè)開發(fā)者”因?yàn)槠髽I(yè)證書簽名的 App 需要用戶在設(shè)置里手動(dòng)信任。為什么要用描述文件而不是直接往 App 里寫設(shè)備 ID因?yàn)橐С謩?dòng)態(tài)添加測(cè)試設(shè)備而不重新簽名。還能擴(kuò)展到重簽名、逆向防護(hù)、越獄檢測(cè)等話題但注意面試時(shí)不要主動(dòng)涉獵灰色內(nèi)容把自己懂的知識(shí)點(diǎn)框架搭好即可。5. 性能優(yōu)化與架構(gòu)設(shè)計(jì)從“能干活”到“干得好”5.1 啟動(dòng)優(yōu)化與卡頓優(yōu)化實(shí)戰(zhàn)啟動(dòng)優(yōu)化在高級(jí)崗面試中幾乎是必問項(xiàng)。需要從 pre-main 階段開始說dyld 加載動(dòng)態(tài)庫(kù)時(shí)越多的 dylib 意味著越慢所以要減少不必要的依賴庫(kù)針對(duì) OC Runtime 階段class 注冊(cè)和分類加載是有開銷的減少啟動(dòng)時(shí)加載的類能在一定程度上降低耗時(shí)main 階段之后要檢查首屏渲染路徑上的耗時(shí)操作。具體手段可以聊冷啟動(dòng)檢測(cè)用 Instruments 的 App Launch 模板動(dòng)態(tài)庫(kù)轉(zhuǎn)靜態(tài)庫(kù)把未必要?jiǎng)討B(tài)加載的庫(kù)吃進(jìn)來load方法能不用就不用因?yàn)樗趩?dòng)早期執(zhí)行而且無法控制順序最好改成initialize懶加載把啟動(dòng)必用的配置數(shù)據(jù)在后臺(tái)線程預(yù)加載避免主線程 I/O??D優(yōu)化的核心是“主線程不要做耗時(shí)操作”。如何發(fā)現(xiàn)卡頓通過 Instruments 的 Time Profiler 查看主線程堆棧用 FPS 監(jiān)控工具比如 CADisplayLink 方式自定義檢測(cè)或者接入第三方 APM 看線上卡頓堆棧。修復(fù)策略無非是耗時(shí)操作下移到子線程圖片解碼放到后臺(tái)避免過度繪制控制布局復(fù)雜度。要能結(jié)合一個(gè)具體的項(xiàng)目案例來講比如“某頁(yè)面滾動(dòng)時(shí)掉幀用 Time Profiler 發(fā)現(xiàn)是 collectionView 的 cell 里圖片縮放在主線程解碼導(dǎo)致的修復(fù)方式是用預(yù)解碼和強(qiáng)制解壓將耗時(shí)轉(zhuǎn)移到后臺(tái)”。5.2 架構(gòu)模式與組件化設(shè)計(jì)架構(gòu)題通常以開放性問題出現(xiàn)。面試官會(huì)問“你們項(xiàng)目是怎么分層組織的”或“MVVM 相比 MVC 解決了什么問題”。這時(shí)候不要背概念要結(jié)合項(xiàng)目講。MVC 在 iOS 里的問題在于 Controller 過于臃腫ViewController 既負(fù)責(zé)生命周期管理、又是網(wǎng)絡(luò)請(qǐng)求調(diào)度、還要處理數(shù)據(jù)組裝和 UI 交互代碼大量堆積之后很難維護(hù)。MVVM 通過 ViewModel 把數(shù)據(jù)和 UI 綁定邏輯抽出來但要小心引入響應(yīng)式框架后調(diào)試成本上升。組件化是每個(gè)大廠項(xiàng)目必然面對(duì)的話題??疾禳c(diǎn)包括組件拆分粒度、組件間通信方案、基礎(chǔ)組件層怎么規(guī)劃。被問到“Router 模式和直接 import 有什么區(qū)別”時(shí)可以聊解耦的必要性運(yùn)行時(shí)路由可以通過 URL 映射實(shí)現(xiàn)跨組件跳轉(zhuǎn)。但也要說清徹底的路由化會(huì)犧牲編譯期檢查和代碼可讀性所以不是所有模塊都值得拆。設(shè)計(jì)模式同樣會(huì)穿插在架構(gòu)題里。單例的使用場(chǎng)景要警惕濫用比如全局的 UserManager、NetworkManager 如果狀態(tài)太多反而會(huì)帶來測(cè)試?yán)щy和并發(fā)風(fēng)險(xiǎn)。工廠模式適合創(chuàng)建復(fù)雜對(duì)象。觀察者模式可以用 NotificationCenter 或 Combine 解耦事件交互但要注意移除觀察者避免內(nèi)存泄漏。5.3 包體積優(yōu)化與鏈路監(jiān)控App 體積直接影響轉(zhuǎn)化率所以在性能優(yōu)化題里很常考。二進(jìn)制層面通過對(duì)資源的瘦身、無用的圖片和文件清理、啟用 Bitcode不過現(xiàn)在基本不啟用、審計(jì)第三方 SDK 中的重復(fù)代碼、使用 Asset Catalog 做圖片壓縮都能顯著減小體積。還可以把 App Thinning 掛在嘴上說明你理解 App Store 分發(fā)時(shí)按設(shè)備能力裁剪資源包的過程??蓤?zhí)行文件層面的優(yōu)化難度更高。常見手段包括通過編譯選項(xiàng)Dead Code Stripping去掉沒有被引用的代碼、把重復(fù)的工具函數(shù)抽取到共享庫(kù)、避免大量公有方法導(dǎo)致外聯(lián)符號(hào)表變大?;卮鸬迷劫N近實(shí)際的構(gòu)建產(chǎn)物越能體現(xiàn)你不是紙上談兵。周期監(jiān)控也是個(gè)加分話題。項(xiàng)目里如果接入了 CI/CD可以在流水線里統(tǒng)計(jì)安裝包大小、啟動(dòng)耗時(shí)、崩潰率等指標(biāo)形成一條可量化的優(yōu)化曲線。聊到這層面試官基本能判斷你具備成本意識(shí)和工程質(zhì)量意識(shí)。6. 常見問題快問快答與避坑指南6.1 面試高頻陷阱集中解答這里整理幾個(gè)我面試中經(jīng)常拿來“釣魚”的題提前給答案背下來即可。在主線程同步調(diào)用dispatch_sync到主隊(duì)列會(huì)發(fā)生什么答死鎖。因?yàn)橹麝?duì)列串行同步任務(wù)必須等前面的任務(wù)執(zhí)行完前面的任務(wù)又在等待這個(gè)同步任務(wù)返回互相等待。NSString 的 copy 和 strong 有什么區(qū)別答strong 只是增加引用計(jì)數(shù)若源對(duì)象是 NSMutableString外部修改會(huì)改變屬性copy 會(huì)做一次不可變拷貝保證屬性值不會(huì)被外部篡改。nil、Nil、NULL、NSNull的區(qū)別答nil 是 OC 中 nil 指針Nil 是類指針為空NULL 是通用 C 空指針NSNull 是占位對(duì)象用于集合里表示空值。Block 的存儲(chǔ)位置在哪答分為三種全局 block不捕獲變量、棧 block捕獲變量但未在堆上拷貝、堆 block被 copy 過。ARC 下 block 屬性一般用 copy 修飾目的是把它從棧區(qū)移到堆區(qū)保證在作用域結(jié)束后仍有效。是深拷貝還是淺拷貝[mutableCopy]得到的一定是可變對(duì)象[copy]對(duì)不可變對(duì)象是淺拷貝對(duì)可變對(duì)象則產(chǎn)生新的不可變副本內(nèi)容會(huì)拷貝但對(duì)象類型變了。6.2 面試答題的節(jié)奏與表達(dá)技巧技術(shù)面試不單是知識(shí)問答更是一場(chǎng)溝通表達(dá)。我見過很多候選人知識(shí)點(diǎn)都懂但答得沒有結(jié)構(gòu)想到哪兒說到哪兒給人感覺不夠扎實(shí)。練習(xí)時(shí)可以刻意采用“結(jié)論先行 展開原理 結(jié)合實(shí)踐”三步策略。比如問“什么是 Auto Layout 的 Cassowary 算法”可以先一句總結(jié)說這是線性方程組的約束求解器再簡(jiǎn)單展開求解器如何把約束表達(dá)成等式然后說在 iOS 里約束太多時(shí)的性能影響以及你怎么應(yīng)對(duì)。這三步走下來既有條理又有深度。另一個(gè)常見問題是不懂裝懂。面試官反感的往往不是“不會(huì)”而是“亂答”。遇到不會(huì)的問題比較職業(yè)的做法是坦誠(chéng)說這塊沒深究過再嘗試從已知的類似原理去推導(dǎo)或者反問面試官“我能從 XX 角度理解嗎”。這種思路展示的是學(xué)習(xí)能力比硬編一個(gè)答案要好得多。6.3 我的個(gè)人經(jīng)驗(yàn)與復(fù)盤建議最后再分享一個(gè)小技巧。每次面試完把被問到的問題整理到備忘錄里按“答得好”“答得一般”“完全不會(huì)”三檔打標(biāo)。兩周后重新刷一遍標(biāo)為“答得一般”的題寫出自己的標(biāo)準(zhǔn)答案。這個(gè)過程效率很高因?yàn)樗颇阌幂敵龅贡戚斎攵皇侵豢磩e人的面經(jīng)。iOS 技術(shù)棧更新確實(shí)快每年都有新特性和新工具但底層核心的面試考點(diǎn)其實(shí)變化不大——語言特性、內(nèi)存、并發(fā)、UI、網(wǎng)絡(luò)、架構(gòu)。把這些基礎(chǔ)打牢了以不變應(yīng)萬變面試的底氣自然就來了。希望這篇文章能幫你把知識(shí)體系梳理清楚面試順利拿到心儀的 offer。