奇藝iOS校招筆試全復(fù)盤:核心考點(diǎn)、解題思路與備戰(zhàn)策略)
開(kāi)篇一次校招筆試為什么值得專門復(fù)盤先交代一下背景。2018年秋季愛(ài)奇藝啟動(dòng)了當(dāng)年的校招iOS工程師崗位分為多場(chǎng)進(jìn)行第三場(chǎng)筆試是面向投遞較晚、或者第一批筆試沒(méi)趕上的同學(xué)。我當(dāng)年完整參加了這場(chǎng)筆試后來(lái)也帶著這份經(jīng)驗(yàn)帶過(guò)不少學(xué)弟學(xué)妹準(zhǔn)備校招所以想把這套題背后的考察邏輯、每類考點(diǎn)的準(zhǔn)備方式、以及我在考場(chǎng)上踩過(guò)的坑完整整理出來(lái)。先說(shuō)說(shuō)這篇文章適合誰(shuí)看。如果你是正在準(zhǔn)備iOS校招、或者準(zhǔn)備跳槽大廠iOS崗位的工程師這篇文章能幫你理清愛(ài)奇藝這類視頻業(yè)務(wù)大廠到底想考什么。如果你只是剛學(xué)iOS不久還沒(méi)到面試階段也可以把這份復(fù)盤當(dāng)作學(xué)習(xí)路線圖——筆試中反復(fù)出現(xiàn)的知識(shí)點(diǎn)就是你接下來(lái)半年最值得投入精力去啃的內(nèi)容。有意思的是距離2018年已經(jīng)過(guò)去好幾年但把當(dāng)年的筆試題拿出來(lái)看核心考點(diǎn)并沒(méi)有過(guò)時(shí)。內(nèi)存管理、多線程、網(wǎng)絡(luò)請(qǐng)求、UI布局、基礎(chǔ)算法這些到今天依然是iOS面試的高頻區(qū)。變化的只是技術(shù)的具體形態(tài)——比如現(xiàn)在面試更愛(ài)問(wèn)Swift Concurrency當(dāng)年問(wèn)的是GCD和OperationQueue現(xiàn)在都在聊SwiftUI當(dāng)年還在死磕Auto Layout和frame布局。但底層的計(jì)算機(jī)基礎(chǔ)和工程思維始終沒(méi)有變過(guò)。所以這篇復(fù)盤我盡量只講那些換一身外殼依然會(huì)考的東西。1. 整體考察思路與備考策略1.1 愛(ài)奇藝校招筆試想篩選什么樣的人先說(shuō)結(jié)論這批題目不是單純考你背了多少iOS API而是在考你有沒(méi)有軟件工程師的基本素養(yǎng)。整個(gè)筆試分為兩個(gè)大部分——一部分是客觀題和基礎(chǔ)編程題一部分是iOS相關(guān)的專題??陀^題覆蓋了數(shù)據(jù)結(jié)構(gòu)、計(jì)算機(jī)網(wǎng)絡(luò)、操作系統(tǒng)、Objective-C/Swift基礎(chǔ)專題部分涉及iOS特有機(jī)制比如RunLoop、內(nèi)存管理、多線程、界面布局、網(wǎng)絡(luò)層封裝。這和很多公司的校招筆試結(jié)構(gòu)相似但愛(ài)奇藝的題目有個(gè)明顯特點(diǎn)偏向?qū)嶋H業(yè)務(wù)場(chǎng)景而不是純粹的理論默寫。舉例來(lái)說(shuō)它不會(huì)直接問(wèn)你什么是引用計(jì)數(shù)而是給你一段有循環(huán)引用的代碼讓你找出內(nèi)存泄漏發(fā)生在哪一行不會(huì)直接問(wèn)什么是死鎖而是給你三個(gè)并發(fā)任務(wù)讓你判斷這個(gè)調(diào)度方式會(huì)不會(huì)卡死主線程。這種套路明顯是希望招進(jìn)來(lái)的人能直接上手寫業(yè)務(wù)代碼而不是還要先教一遍工程習(xí)慣。另外很關(guān)鍵的一點(diǎn)是愛(ài)奇藝是視頻業(yè)務(wù)公司播放頁(yè)、首頁(yè)信息流、彈幕、評(píng)論這些場(chǎng)景在題目中反復(fù)出現(xiàn)。如果你有視頻類App的開(kāi)發(fā)經(jīng)驗(yàn)或者至少想過(guò)一個(gè)視頻列表頁(yè)應(yīng)該怎么做內(nèi)存管理在審題的時(shí)候會(huì)天然有優(yōu)勢(shì)。1.2 覆蓋range與時(shí)間分配兩個(gè)小時(shí)怎么拿分我記得那場(chǎng)筆試的總時(shí)長(zhǎng)是120分鐘題量不大但每道題都有一定的深度。最忌諱的做法是平均分配時(shí)間——基礎(chǔ)選擇題每道花三五分鐘結(jié)果后面的大題只剩二十分鐘寫到一半就被迫提交。我當(dāng)時(shí)的時(shí)間分配策略是選擇題和填空題控制在40分鐘內(nèi)完成中間的主觀題比如讓你補(bǔ)全代碼、解釋輸出結(jié)果控制在30分鐘最后的綜合設(shè)計(jì)題和編程題留下至少50分鐘。綜合題通常分值最高而且它的評(píng)分是按思路分步給的即使最后沒(méi)跑通只要思路清晰、代碼結(jié)構(gòu)完整也能拿到60%到70%的分?jǐn)?shù)。如果前面拖太久最后的大題直接空著那基本就沒(méi)希望了?,F(xiàn)在回看這個(gè)策略仍然適用于絕大多數(shù)公司的筆試不管是iOS崗還是其他開(kāi)發(fā)崗。筆試的分?jǐn)?shù)密度不是均勻分布的最后的核心大題才是拉分的關(guān)鍵前面的題保證正確率即可不需要做到完美。1.3 別把寶押在刷原題上我知道很多人在筆試前會(huì)去找愛(ài)奇藝iOS校招原題來(lái)背。但實(shí)際經(jīng)歷過(guò)就知道除非是特別近期的題目否則很難遇到完全一樣的原題。大廠的題庫(kù)通常是在一個(gè)大的題目池里隨機(jī)抽取組合你背到的可能是某道題的一個(gè)變體選項(xiàng)順序變了、數(shù)值變了、甚至考察角度直接從讓你寫出代碼變成讓你指出代碼哪里有問(wèn)題。更有效的準(zhǔn)備方式是把知識(shí)點(diǎn)本身的原理吃透。比如你理解了autoreleasepool在什么時(shí)候釋放對(duì)象的原理無(wú)論題目以什么樣的姿勢(shì)去包裝它——是面試題、是選擇題還是代碼補(bǔ)全——你都能識(shí)別出來(lái)。這也是我這篇復(fù)盤這么強(qiáng)調(diào)為什么的原因。2. 核心考點(diǎn)拆解與解題思路2.1 內(nèi)存管理以ARC為背景的循環(huán)引用分析愛(ài)奇藝筆試對(duì)內(nèi)存管理的考察幾乎可以確定是繞不開(kāi)的??疾斓慕嵌韧ǔ2皇侵苯訂?wèn)什么是循環(huán)引用而是給你一個(gè)對(duì)象關(guān)系圖讓你判斷某個(gè)對(duì)象是否被正確釋放或者給你一段閉包代碼讓你指出閉包里捕獲了哪些變量會(huì)不會(huì)造成循環(huán)引用。那時(shí)候iOS已經(jīng)全面進(jìn)入ARC時(shí)代所以題目不會(huì)讓你手動(dòng)管理retain和release但你需要理解ARC到底做了什么。ARC本質(zhì)上是編譯期插入retain/release調(diào)用的機(jī)制它只能在編譯期確定對(duì)象生命周期。遇到閉包、delegate、NSTimer這些場(chǎng)景編譯器無(wú)法自動(dòng)推斷就可能會(huì)出現(xiàn)循環(huán)引用。舉一個(gè)當(dāng)年考過(guò)的類似場(chǎng)景一個(gè)ViewController持有UITableViewUITableView的delegate和dataSource都指向這個(gè)ViewController。與此同時(shí)這個(gè)ViewController在viewDidLoad里創(chuàng)建了一個(gè)閉包并且把它保存到自己的一個(gè)屬性里閉包內(nèi)部又用到了self。問(wèn)這個(gè)ViewController會(huì)不會(huì)被正確釋放。答案是如果閉包屬性是強(qiáng)引用默認(rèn)的strong閉包捕獲的self也是強(qiáng)引用那么就會(huì)形成 self - closure - self 這一條循環(huán)引用鏈導(dǎo)致dealloc永遠(yuǎn)不會(huì)被調(diào)用。解決辦法是在閉包里使用[weak self]捕獲列表或者在外面用weak var weakSelf self。這類題看似簡(jiǎn)單但特別容易在細(xì)節(jié)上丟分。比如很多人知道閉包要用weak但當(dāng)閉包是DispatchQueue的block時(shí)如果沒(méi)有被self持有其實(shí)不會(huì)循環(huán)引用。筆試中需要仔細(xì)判斷閉包是否被self間接持有而不能看到閉包就用weak。2.2 多線程與并發(fā)同步/異步、隊(duì)列、死鎖多線程是另一個(gè)重量級(jí)考點(diǎn)。2018年的時(shí)候Swift 4已經(jīng)發(fā)布了但大部分iOS項(xiàng)目的核心代碼還是Objective-C與Swift混編。所以筆試題通常會(huì)同時(shí)考察兩個(gè)層面的內(nèi)容一是GCD、NSOperationQueue的使用二是并發(fā)模型背后的執(zhí)行順序判斷。常見(jiàn)的出題方式是給你一段代碼里面有DispatchQueue.main.async、DispatchQueue.global().async、以及一個(gè)信號(hào)量或者DispatchGroup讓你判斷最后的輸出順序。這類題目表面看是考API實(shí)際上在考察你是否真正理解同步/異步和串行/并發(fā)這兩組維度的組合。很多沒(méi)準(zhǔn)備好的同學(xué)一看到dispatch就會(huì)暈。這里我分享一個(gè)我當(dāng)時(shí)總結(jié)的心法先判斷執(zhí)行方法sync還是async再判斷隊(duì)列類型串行還是并發(fā)然后逐步推理不要憑感覺(jué)作答。只要嚴(yán)格按這個(gè)順序去分析大部分GCD題目都是紙老虎。還要注意另一個(gè)高頻考點(diǎn)——死鎖。經(jīng)典題目是在主隊(duì)列里調(diào)用DispatchQueue.main.sync會(huì)發(fā)生什么答案是因?yàn)橹麝?duì)列是串行隊(duì)列當(dāng)前block正在主隊(duì)列上執(zhí)行sync又向主隊(duì)列提交了一個(gè)新的block而當(dāng)前block必須等sync返回新的block又必須等當(dāng)前block結(jié)束兩個(gè)任務(wù)互相等待于是死鎖。筆試?yán)镞@個(gè)場(chǎng)景經(jīng)常被包裝成用戶點(diǎn)擊按鈕后執(zhí)行某個(gè)操作的形式出現(xiàn)考察你是不是真的明白sync對(duì)串行隊(duì)列的阻塞行為。2.3 網(wǎng)絡(luò)層從HTTP到TCP的層層追問(wèn)視頻類公司對(duì)網(wǎng)絡(luò)層的考察一定會(huì)比普通App公司更深。我當(dāng)時(shí)遇到的不只是GET和POST的區(qū)別而是從HTTP請(qǐng)求的完整生命周期開(kāi)始問(wèn)起DNS解析過(guò)程、TCP三次握手、TLS握手、HTTP頭字段的作用、HTTP/2的新特性、斷點(diǎn)續(xù)傳的實(shí)現(xiàn)原理。其中有一個(gè)考察方向讓我印象很深如何設(shè)計(jì)一個(gè)支持?jǐn)帱c(diǎn)續(xù)傳的視頻下載模塊。這道題涉及到HTTP Range頭字段、文件IO、內(nèi)存管理、以及任務(wù)狀態(tài)機(jī)的設(shè)計(jì)。即使放在今天這也是一個(gè)很優(yōu)秀的綜合題因?yàn)樗瑫r(shí)考察了網(wǎng)絡(luò)協(xié)議理解和客戶端工程能力。我當(dāng)時(shí)的思路是這樣下載模塊維護(hù)一個(gè)任務(wù)隊(duì)列每個(gè)下載任務(wù)有三個(gè)狀態(tài)——等待中、下載中、已完成。下載開(kāi)始時(shí)先向服務(wù)器發(fā)送HEAD請(qǐng)求或者帶Range的GET請(qǐng)求從響應(yīng)頭里拿到文件總大小。然后根據(jù)一定的分片大小比如1MB把文件切分成多個(gè)range每個(gè)range發(fā)起獨(dú)立的下載請(qǐng)求寫入文件時(shí)通過(guò)NSFileHandle的seekToFileOffset定位到對(duì)應(yīng)的偏移量去寫入。所有分片完成后驗(yàn)證文件完整性再把任務(wù)狀態(tài)置為已完成。這個(gè)方案當(dāng)然有很多可優(yōu)化的細(xì)節(jié)比如斷點(diǎn)續(xù)傳時(shí)如何記錄已下載的分片信息、下載過(guò)程中如何應(yīng)對(duì)網(wǎng)絡(luò)切換。但在筆試中只要你能把這個(gè)框架搭建出來(lái)再補(bǔ)上幾個(gè)關(guān)鍵邊界條件的處理就已經(jīng)能拿到很好的分?jǐn)?shù)了。2.4 UI與布局從frame到Auto LayoutUI布局在愛(ài)奇藝筆試中占的比重也不小。2018年的時(shí)候Auto Layout已經(jīng)成為主流但筆試對(duì)這種純代碼寫布局的題目仍然很感興趣。常見(jiàn)出題方式有兩種一種給你一段手動(dòng)計(jì)算frame的代碼讓你指出在什么情況下會(huì)出問(wèn)題另一種給你一個(gè)目標(biāo)布局的示意圖讓你用Auto Layout實(shí)現(xiàn)出來(lái)。第一種考察的是對(duì)不同屏幕尺寸的適配意識(shí)。手動(dòng)計(jì)算frame時(shí)如果硬編碼了某個(gè)寬度和高度在iPhone SE和iPhone Xs Max上就會(huì)出現(xiàn)不同的表現(xiàn)。正確做法是讓frame基于superview的bounds去計(jì)算或者直接用Auto Layout。筆試中很多同學(xué)會(huì)忽略一點(diǎn)即使你用Auto Layout也需要注意約束的優(yōu)先級(jí)和固有內(nèi)容大小否則在內(nèi)容長(zhǎng)度變化時(shí)一樣會(huì)出問(wèn)題。我還記得有一道題和UIStackView有關(guān)。2018年正好是UIStackView開(kāi)始普及的階段愛(ài)奇藝的題目很趕時(shí)髦讓考生說(shuō)出UIStackView的幾種distribution的區(qū)別——fill、fillEqually、fillProportionally、equalSpacing、equalCentering。如果只是用過(guò)UIStackView但沒(méi)深入理解過(guò)很容易把fillEqually和fillProportionally搞混。簡(jiǎn)單來(lái)說(shuō)fillEqually是讓每個(gè)子視圖獲得相同尺寸fillProportionally是根據(jù)子視圖的固有內(nèi)容大小按比例分配兩者在子視圖內(nèi)容不一致時(shí)的表現(xiàn)完全不同。2.5 算法與數(shù)據(jù)結(jié)構(gòu)視頻列表場(chǎng)景下的實(shí)戰(zhàn)題算法題是篩選門檻但愛(ài)奇藝的算法題不會(huì)很偏主要是鏈表、二叉樹(shù)、字符串處理、動(dòng)態(tài)規(guī)劃這類經(jīng)典題型。和純算法崗不同iOS崗的算法題經(jīng)常會(huì)套一層業(yè)務(wù)殼。比如給定一個(gè)視頻列表每個(gè)視頻有開(kāi)始時(shí)間和結(jié)束時(shí)間如何計(jì)算最大同時(shí)播放數(shù)這本質(zhì)上是一道掃描線算法題再比如實(shí)現(xiàn)一個(gè)LRU緩存用于圖片加載這本來(lái)就是iOS開(kāi)發(fā)中的高頻場(chǎng)景。我印象最深的是一道和字符串相關(guān)的題目大概意思是給定一個(gè)字符串找出最長(zhǎng)的不含重復(fù)字符的子串長(zhǎng)度。這道題在LeetCode上屬于中等難度但如果在筆試的iOS試卷里出現(xiàn)很多人會(huì)愣一下——因?yàn)樗㈩}時(shí)一般不會(huì)把這道題和iOS關(guān)聯(lián)起來(lái)。但如果仔細(xì)想在視頻搜索、彈幕關(guān)鍵詞過(guò)濾這些場(chǎng)景中字符串處理本來(lái)就很常見(jiàn)。算法題考的不是你背了多少題解而是你有沒(méi)有在業(yè)務(wù)代碼中鍛煉過(guò)思維。準(zhǔn)備這類題目我的建議是不要把精力花在那些偏難怪的算法上而是把經(jīng)典的數(shù)據(jù)結(jié)構(gòu)操作寫熟。鏈表反轉(zhuǎn)、二叉樹(shù)遍歷、快排、二分查找、動(dòng)態(tài)規(guī)劃的經(jīng)典模型這些寫熟之后無(wú)論算法題怎么變體你至少能寫出一個(gè)可運(yùn)行的版本而不會(huì)在考場(chǎng)上卡死。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 用代碼演示循環(huán)引用的檢測(cè)與修復(fù)筆試中經(jīng)常要求你直接寫代碼來(lái)證明你理解了循環(huán)引用。最好的做法是寫一個(gè)可以運(yùn)行的小demo同時(shí)在注釋里標(biāo)注出修復(fù)的關(guān)鍵點(diǎn)。我整理了一個(gè)典型的示例你們可以當(dāng)模板來(lái)用。class VideoPlayer { var title: String var onPlayFinished: (() - Void)? init(title: String) { self.title title } func startPlay() { // 模擬播放結(jié)束后回調(diào) DispatchQueue.global().asyncAfter(deadline: .now() 1.0) { [weak self] in guard let self self else { return } self.onPlayFinished?() } } deinit { print(\(title) deallocated) } } class PlayerManager { var currentPlayer: VideoPlayer? func setup() { let player VideoPlayer(title: 愛(ài)奇藝獨(dú)家) // 關(guān)鍵點(diǎn)閉包內(nèi)部使用 [weak self]避免 self - player - closure - self 的循環(huán)引用 player.onPlayFinished { [weak self] in self?.handlePlayFinished() } currentPlayer player player.startPlay() } func handlePlayFinished() { print(播放完成更新UI) } }在筆試時(shí)我建議在關(guān)鍵代碼行旁邊用注釋寫出為什么這里要weak以及如果不weak會(huì)發(fā)生什么循環(huán)引用鏈。一方面方便閱卷人快速看到你的思路另一方面也是提醒自己不要漏掉關(guān)鍵步驟。這道題除了考察weak之外順帶考察了DispatchQueue.global().asyncAfter的用法一箭雙雕。3.2 用代碼演示GCD 輸出順序判斷的標(biāo)準(zhǔn)分析流程有一類經(jīng)典題是給一段代碼問(wèn)你輸出順序我直接把一個(gè)高頻示例貼在這里let queue DispatchQueue(label: com.example.serial) queue.async { print(1) queue.sync { print(2) } print(3) } print(4)很多同學(xué)第一次看到這段代碼第一反應(yīng)是輸出1 2 3 4。但真實(shí)結(jié)果是queue是一個(gè)串行隊(duì)列外層async代碼塊運(yùn)行在queue上當(dāng)執(zhí)行到內(nèi)部queue.sync時(shí)它向同一個(gè)串行隊(duì)列提交了一個(gè)新任務(wù)而當(dāng)前任務(wù)必須等新任務(wù)執(zhí)行完才能繼續(xù)新任務(wù)又必須等當(dāng)前任務(wù)執(zhí)行完——和主隊(duì)列的main.sync死鎖是一樣的道理所以程序會(huì)在執(zhí)行到queue.sync時(shí)直接崩潰或者卡死。分析這類題目時(shí)我強(qiáng)烈建議你們嚴(yán)格按照隊(duì)列類型 執(zhí)行方法這個(gè)順序來(lái)推理不要憑直覺(jué)。第一看看這段代碼是在哪個(gè)隊(duì)列上執(zhí)行第二看看調(diào)用sync/async的隊(duì)列是不是同一個(gè)第三判斷是否有等待關(guān)系。只要這三點(diǎn)理清了就不會(huì)出錯(cuò)。3.3 手工推演斷點(diǎn)續(xù)傳下載模塊的設(shè)計(jì)這是當(dāng)年綜合題里最接近實(shí)際業(yè)務(wù)的一道我詳細(xì)說(shuō)說(shuō)我在筆試時(shí)的解答結(jié)構(gòu)。首先是需求分析用戶觀看視頻時(shí)可能中途退出再次點(diǎn)擊觀看時(shí)希望從上次的位置繼續(xù)播放而不需要重新請(qǐng)求整個(gè)文件。這背后需要支持HTTP Range請(qǐng)求也就是在HTTP請(qǐng)求頭里帶上Range: bytesstart-end服務(wù)器返回206 Partial Content并攜帶Content-Range響應(yīng)頭。然后是客戶端設(shè)計(jì)我分了幾個(gè)模塊任務(wù)管理模塊維護(hù)一個(gè)下載任務(wù)的集合每個(gè)任務(wù)有唯一標(biāo)識(shí)比如視頻ID任務(wù)狀態(tài)包括waiting、downloading、paused、finished、failed。這個(gè)模塊的核心是狀態(tài)轉(zhuǎn)換的合法性判斷防止從failed直接跳到finished這種非法狀態(tài)。分片下載模塊將文件按照固定大小比如1MB分成多個(gè)分片每個(gè)分片發(fā)起獨(dú)立的HTTP請(qǐng)求。每個(gè)分片下載完成后將數(shù)據(jù)寫入文件對(duì)應(yīng)的偏移位置。使用NSFileHandle的seekToFileOffset來(lái)定位寫入位置避免用NSData拼接整個(gè)文件減少內(nèi)存占用。進(jìn)度記錄模塊每完成一個(gè)分片就把分片的完成信息寫入本地plist或數(shù)據(jù)庫(kù)。這樣即使App被殺死下次啟動(dòng)時(shí)也能從斷點(diǎn)恢復(fù)。記錄的信息包括文件總大小、已完成的分片序號(hào)集合、文件在沙盒中的路徑。這個(gè)設(shè)計(jì)放到今天的面試中依然能打因?yàn)樗w現(xiàn)了一個(gè)iOS工程師對(duì)整個(gè)下載鏈路的理解。筆試中不用寫出完整代碼但畫出模塊圖、給出關(guān)鍵代碼段、說(shuō)清楚狀態(tài)機(jī)設(shè)計(jì)就已經(jīng)很出彩了。3.4 工程習(xí)慣筆試代碼里也要體現(xiàn)規(guī)范很多同學(xué)覺(jué)得筆試代碼只要能跑就行其他不重要。但實(shí)際上筆試代碼的規(guī)范性會(huì)影響閱卷人對(duì)你的整體印象尤其是在大廠校招這種簡(jiǎn)歷多、需要快速篩選的場(chǎng)景下。我見(jiàn)過(guò)不少代碼雖然能通過(guò)測(cè)試用例但函數(shù)命名隨意、魔法數(shù)字到處都是、沒(méi)有任何注釋這類答案在分步給分時(shí)很吃虧。我建議在筆試時(shí)養(yǎng)成三個(gè)習(xí)慣第一用有意義的命名比如downloadVideoTaskWithID:而不是func a()第二把關(guān)鍵的邊界條件和設(shè)計(jì)意圖寫成注釋哪怕只是兩三行第三盡量把邏輯拆成獨(dú)立的小函數(shù)保持每個(gè)函數(shù)的長(zhǎng)度在20行以內(nèi)。這三個(gè)習(xí)慣在面試手寫代碼環(huán)節(jié)也同樣重要。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 筆試環(huán)境與工具鏈先解決跑不起來(lái)的問(wèn)題當(dāng)年的筆試是在線OJ系統(tǒng)完成的支持C、C、Java、Objective-C等語(yǔ)言但Swift的支持還不夠完善。很多同學(xué)習(xí)慣用Xcode寫代碼結(jié)果到了OJ環(huán)境發(fā)現(xiàn)沒(méi)有自動(dòng)補(bǔ)全、沒(méi)有語(yǔ)法高亮、編譯器版本也和本地不一樣一下子就不適應(yīng)了。我先說(shuō)一個(gè)通用的應(yīng)對(duì)思路提前去目標(biāo)公司的筆試平臺(tái)做模擬題。絕大多數(shù)校招筆試平臺(tái)都提供往年的模擬題或者練習(xí)入口花半小時(shí)熟悉一下代碼編輯器的操作方式能避免考場(chǎng)上浪費(fèi)大量時(shí)間在怎么把代碼粘貼進(jìn)去System.out.println應(yīng)該怎么寫這種低級(jí)問(wèn)題上。另外建議答算法題時(shí)優(yōu)先選擇熟悉的語(yǔ)言。如果你平時(shí)iOS開(kāi)發(fā)用Objective-C但筆試OJ對(duì)Swift支持不好可以嘗試熟悉一下C或Java的語(yǔ)法因?yàn)榇蠖鄶?shù)OJ對(duì)這兩門語(yǔ)言的支持最完善。不要臨時(shí)換語(yǔ)言硬寫很容易在語(yǔ)法細(xì)節(jié)上翻車。4.2 選擇題里的陷阱別被細(xì)節(jié)帶偏客觀題是拿分的基礎(chǔ)但選擇題的出題人經(jīng)常會(huì)故意設(shè)置一些看似正確、實(shí)則錯(cuò)誤的選項(xiàng)。比如遇到API相關(guān)的題目?jī)蓚€(gè)選項(xiàng)只差一個(gè)關(guān)鍵詞比如copy和strong、atomic和nonatomic、lazy和assign如果不熟悉的話真的很容易選錯(cuò)。我的建議是遇到不確定的題先用排除法排除明顯錯(cuò)誤的選項(xiàng)再對(duì)比剩余選項(xiàng)之間的差異點(diǎn)。如果是在內(nèi)存管理類題目中把每個(gè)選項(xiàng)背后的引用計(jì)數(shù)變化畫出來(lái)比憑感覺(jué)選要可靠得多。畫圖雖然耗時(shí)但在內(nèi)存管理和多線程題目中畫圖往往是一步到位的最快方式。4.3 編譯不過(guò)怎么辦第一個(gè)要查的不是語(yǔ)法筆試中經(jīng)常出現(xiàn)的情況是時(shí)間只剩20分鐘代碼寫完但編譯不過(guò)提示一個(gè)看不懂的錯(cuò)誤。這時(shí)候很多人的第一反應(yīng)是盯著錯(cuò)誤信息看半天改來(lái)改去還是不對(duì)。我總結(jié)了一個(gè)更高效的排查順序第一檢查括號(hào)是否匹配。手寫代碼最常見(jiàn)的錯(cuò)誤就是多了一個(gè)右括號(hào)或者少了一個(gè)右括號(hào)尤其是嵌套較多的方法調(diào)用時(shí)。第二檢查是不是少了import或頭文件引用。OJ環(huán)境不會(huì)像Xcode那樣自動(dòng)幫你導(dǎo)入框架忘記#import UIKit、import Foundation這種低級(jí)錯(cuò)誤相當(dāng)常見(jiàn)。第三檢查變量名是否拼寫一致。手寫代碼時(shí)變量名前后不一致的坑比想象中更頻繁。如果以上三項(xiàng)都排查完還是編譯不過(guò)直接放棄這道題把時(shí)間留給后面的題目。筆試是限時(shí)賽糾結(jié)在一道題上后面的大題可能一分都拿不到。這一點(diǎn)我特別要提醒追求完美的同學(xué)——拿到70%的分?jǐn)?shù)遠(yuǎn)比追求100%卻做不完要強(qiáng)得多。4.4 崩潰和超時(shí)在線OJ的兩種典型反饋在線OJ的反饋一般有兩種運(yùn)行崩潰和超時(shí)。運(yùn)行崩潰通常意味著代碼在某個(gè)輸入下訪問(wèn)了非法內(nèi)存比如數(shù)組越界、空指針調(diào)用、重復(fù)釋放。超時(shí)則說(shuō)明算法時(shí)間復(fù)雜度過(guò)高。針對(duì)這兩種反饋?zhàn)钣行У淖龇ㄊ窃诒镜叵扔肵code調(diào)試一遍。把OJ給出的測(cè)試用例拿過(guò)來(lái)在Xcode中單步執(zhí)行觀察哪一行出了問(wèn)題。即使沒(méi)有Xcode環(huán)境也可以在代碼里加一些打印語(yǔ)句輸出關(guān)鍵變量的值定位問(wèn)題區(qū)間。遇到超時(shí)問(wèn)題優(yōu)先檢查是不是用了遞歸但沒(méi)有終止條件、或者是不是有死循環(huán)。如果代碼邏輯沒(méi)問(wèn)題那就要考慮優(yōu)化算法比如用哈希表替代線性查找、用緩存記錄中間結(jié)果。愛(ài)奇藝的視頻業(yè)務(wù)中列表頁(yè)肯定會(huì)涉及大量數(shù)據(jù)的排序和過(guò)濾這一塊的算法優(yōu)化能力筆試中也會(huì)間接考察。4.5 答題順序與心態(tài)管理兩個(gè)小時(shí)的真實(shí)節(jié)奏最后聊一個(gè)看起來(lái)和技術(shù)無(wú)關(guān)、但實(shí)際影響很大的話題——答題節(jié)奏。很多同學(xué)一上來(lái)就看到最后的大題很難心里一慌開(kāi)始從難題做起結(jié)果前面的基礎(chǔ)題也來(lái)不及做了。我的經(jīng)驗(yàn)是先通讀一遍所有題目標(biāo)記出每道題的預(yù)估難度和分值然后從自己最有把握的題開(kāi)始做。這能確保你先把能拿到的分?jǐn)?shù)拿到手。遇到一道卡了超過(guò)10分鐘的題先空著往下做后面如果有時(shí)間再回來(lái)補(bǔ)。心態(tài)上始終提醒自己校招筆試不是要考滿分而是要在所有人當(dāng)中排到前面。穩(wěn)定發(fā)揮、拿滿基礎(chǔ)分你的排名通常就已經(jīng)很可觀了。最后分享一個(gè)我自己的小習(xí)慣現(xiàn)在回頭看這場(chǎng)筆試最讓我受益的不是背下了哪道題而是在準(zhǔn)備過(guò)程中養(yǎng)成的一個(gè)習(xí)慣每學(xué)一個(gè)iOS知識(shí)點(diǎn)都會(huì)問(wèn)自己“這個(gè)機(jī)制如果出成筆試題會(huì)以什么形式考我”。把RunLoop、ARC、GCD、Auto Layout這些知識(shí)從“會(huì)用”變成“能講清楚”再用筆試題來(lái)驗(yàn)證自己的理解程度這套方法在后來(lái)的面試?yán)镆矌土舜竺ΑH绻阏跍?zhǔn)備iOS校招不妨也試試這個(gè)思路——找一兩套大廠的真題不看答案先自己計(jì)時(shí)做一遍然后把做錯(cuò)的題對(duì)應(yīng)的知識(shí)點(diǎn)找出來(lái)回歸源碼和官方文檔去弄懂原理。這個(gè)過(guò)程雖然慢但對(duì)基礎(chǔ)能力的提升是最扎實(shí)的。祝你們筆試順利。