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