iOS校招笔试高频考点拆解:从内存管理到GCD底层原理

发布时间:2026/8/31 7:36:14
iOS校招笔试高频考点拆解:从内存管理到GCD底层原理 有段时间没聊校招的事了刚好有位读者发来一份《哔哩哔哩2020校园招聘 iOS 方向笔试卷二》的回忆版让我帮忙把里面的知识点拆一拆。这份卷子虽然是2020年的但看完之后我的第一反应是它里面考的底层原理到今天依然是 iOS 校招笔试的高频主线。如果你正在准备 iOS 方向的技术笔试或者刚开始系统复习 iOS 基础这份拆解绝对值得收藏。我当时拿到这份卷子之后最大的感受是它几乎不考具体框架的 API 调用所有题目都在追问“为什么”。为什么 ARC 下还会内存泄漏为什么这样写会死锁为什么这里要用 weak它考的不是你用过多少次 UITableView而是你有没有真正理解 iOS 底层那几条核心脉络对象生命周期、线程调度、网络栈、数据持久化。这篇文章我不想一句一句报答案而是把整张卷子的考点地图、出题逻辑、答题思路和避坑经验完整梳理一遍方便你直接照着复习。1. 这张卷子到底在考什么2020年校招iOS笔试卷的整体观察1.1 出题逻辑不是考你会不会用而是考你懂不懂原理这份二卷整体题量不小选择、简答、代码题都有覆盖范围非常集中集中在几个方向Objective-C 的语言特性、内存管理、GCD 与多线程、网络层基础、数据持久化以及一道偏架构设计的开放题。如果你只背了几个框架 API 就去笔试大概率会栽在原理题上。从出题风格上看2020 年的校招 iOS 笔试卷已经进入了一个相对固定的套路基础语言题目拉满中等偏难题目集中最后放一道需要现场设计的题。二卷比一卷更狠的地方在于它的细节题占比更高。Autorelease Pool、Runloop、atomic 与 nonatomic 的内存语义、GCD 同步异步组合这些平时容易被忽略的边角知识点在二卷里反而成了重头戏。我印象最深的是一道关于 autoreleasepool 的题问的是在循环中创建大量临时对象为什么内存会持续上涨。这题看起来不起眼但能精准区分出“真正理解对象生命周期”和“只会用 ARC 写业务”的两类候选人。把这份卷子的题目全部过完你基本就等于把 iOS 面试题库里“基础扎实”那一档全部过了一遍。它不是靠偏题怪题难为你而是靠一系列你“以为自己知道、但细想说不清楚”的知识点来筛人。1.2 为什么笔试不考 Swift 不考 SwiftUI仍然考 Objective-C 底层有些同学会问2020 年都快进入 SwiftUI 时代了为什么笔试还在考 Objective-C 的东西。这个问题当年我也纠结过但后来在校招群里问了一圈又和做技术面试官的同事聊了几次渐渐明白了校园招聘看的是基础底盘不是框架熟练度。SwiftUI 和 Swift 语言本身当然重要但一份笔试要覆盖成百上千候选人必须选择“门槛公平”的题目。Objective-C 仍然是 iOS 系统框架底层的主要接口语言而且很多内存管理逻辑、运行时机制和它强绑定。如果只考 Swift那些做了两年 iOS 实习的人优势会过大纯科班但没实习过的同学就失去机会了。所以笔试卷选择 Objective-C 底层作为主轴一方面是因为几十年积累的经典题目全是这些另一方面也是为了在全员中拉出区分度。再说iOS 开发里最核心的几条线索比如引用计数、消息转发、线程与 Runloop 的联动、事件响应链这些概念用 Objective-C 解释是最清晰的。明白了这些再去理解 Swift 的内存管理、actor 等新语法反而是降维打击。所以针对这份卷子我的建议是不必纠结 Swift 还是 Objective-C先老老实实把 Objective-C 的底层基础啃明白这是性价比最高的备考策略。2. 高频考点逐项拆解内存管理、循环引用与 Autorelease Pool2.1 ARC 和 MRC引用计数不是背出来的是推出来的内存管理相关题目在历年 iOS 笔试卷里基本必考这套二卷里的核心地位更是明显。它通常会从一个看似简单的问题进入比如“ARC 下对象的 retainCount 是多少”然后逐步加深直到你无法答出为止。我见过很多同学在这类题上疯狂丢分原因只有一个他们把引用计数当结论背而不是当动态过程去推。先理清引用计数的本质。Objective-C 里每一个对象都有一个引用计数当计数从 0 变成 1 时说明被持有从 1 变成 0 时对象被释放。ARC 不是垃圾回收它是在编译期自动插入 retain/release 相关调用。理解这个“编译期插入”非常关键因为很多题就在这一点上做文章。比如说这样一道题在 ARC 模式下写了一段代码创建了一个对象并调用CFGetRetainCount打印计数问打印多少。很多人会脱口而出“1”但实际结果可能是 1也可能是 2、3因为 ARC 可能在编译期优化掉部分临时引用也可能因为对象被加入 autorelease pool 而多了一个引用。这类题的坑点在于你背“retainCount 是几”没有意义题目考的是你是否理解“引用计数始终是一个受编译器优化和运行环境影响的动态结果”。所以这块复习不能死记硬背而是要能手动推导谁对对象做了 retain谁又做了 releaseARC 在返回对象时采取哪些优化什么情况下会走objc_retainAutoreleaseReturnValue什么情况下会走objc_autoreleaseReturnValue。把这些推导搞顺了比刷一百道选择题都管用。笔试时遇到内存题我习惯先在草稿上把引用关系画出来再答题正确率明显高很多。2.2 循环引用的经典场景以及答题时的正确“姿势”循环引用也是跑不掉的题目。笔试里最常出现的例子就是 block 与 self 相互持有。这题看起来简单但想要拿满分需要答到三个层面。第一层是描述现象block 内部使用了 selfblock 被 self 持有于是 self 持有 block、block 持有 self两者都无法释放。第二层是给出解决方案使用__weak typeof(self) weakSelf self在 block 里使用 weakSelf打破引用环。很多人在第二层就停了但如果阅卷人再追问一句“如果 block 内部有延迟执行weakSelf 会不会出问题”你就得补上第三层在 block 内部把 weakSelf 重新转成 strongSelf 再使用避免执行期间对象被提前释放也就是常见的__strong typeof(weakSelf) strongSelf weakSelf的写法。这份卷子还出现过类似“NSTimer 是否会造成循环引用”的变体题以及“delegate 为什么用 weak 修饰”的判断题。在写答案的时候不要只写结论要把“谁是持有者、谁是被持有者、引用关系如何断开”这个过程写清楚。答题的格式感在笔试中有隐形加分面试官看到你能结构化地分析问题基本就会在技术面时对你高看一眼。2.3 Autorelease Pool 与 Runloop 的纠缠二卷里最容易被忽略的角落Autorelease Pool 是很多人的知识盲区。用 ARC 之后很多人根本感觉不到它的存在但笔试不考使用考理解。最典型的一道题是在 for 循环中创建了大量 autorelease 对象内存为什么暴涨答案并不复杂自动释放池在没有手动创建的情况下会由 Runloop 在每一次事件循环结束后统一释放。如果你在一个大循环里不断产生 autorelease 对象它们都会进入当前线程的主 autorelease pool一直等到 Runloop 循环结束才释放。也就是说循环执行期间这些临时对象全都在内存里挂着积少成多就飙起来了。正确做法是autoreleasepool { for (NSInteger i 0; i 100000; i) { // 创建临时对象 } }把大循环包在 autoreleasepool 里让临时对象及时释放。这里还有一个细节iOS 主线程的 Runloop 默认会在每次循环末尾 drain 一次 autorelease pool但子线程如果不手动创建 Runloop则需要显式管理 autorelease pool。所以子线程中创建大量临时对象更容易踩内存峰值。这一块建议复习时配合 Runloop 的生命周期一起看。Runloop 在 sleep 之前会释放一次 autorelease pool在 wake up 之后会创建新的 pool。理清这个节奏不仅笔试能用面试聊到 Runloop 时也是亮眼的加分项。3. 并发与线程GCD、死锁和线程安全笔试里最容易被扣分的部分3.1 串行队列、并发队列、主队列先把模型图在脑子里画出来GCD 相关题目在校招笔试中出现频率极高因为它是 iOS 多线程编程的地基同时它又特别适合出“运行结果题”。给你一段代码问你输出顺序是什么或者会不会死锁。这类题做错的人多不是因为不会写代码而是队列模型没理清。做这类题的第一步是先把队列模型搞明白。系统有三个队列需要重点关注主队列是串行的且必须跑在主线程上全局并发队列默认是并发的另外你可以通过dispatch_queue_create创建自定义队列并指定串行或并发。注意四个便捷队列和主队列的层级关系不要搞混。举例题目问dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSLog(1); dispatch_sync(queue, ^{ NSLog(2); }); NSLog(3); });这段代码会输出什么答案是 1、2、3不会死锁。为什么因为全局队列是并发队列dispatch_sync到同一个并发队列时系统可以再开一条线程来执行新 block当前 block 不用等它就能返回所以不会相互阻塞。但如果把 queue 换成自定义串行队列就完全是另一番场景了当前 block 正占用这个串行队列再往里同步提交一个新 block新 block 永远等不到执行机会就会造成死锁。做题时务必要“看队列而不是看线程”。串行队列同一时刻只允许一个 block 执行你在当前串行队列内部再 sync 提交就是在等自己并发队列没有这个问题。答题时建议顺便写一句“因为当前队列是并发队列/串行队列所以…”这样阅卷人能看出你真的理解了机制而不是背了一堆输出顺序。3.2 线程安全、atomic 与 nonatomic笔试里坑点最多的组合线程安全这个考点笔试试卷里几乎每套都会有一道。选择题可能会问“atomic 修饰的属性是否线程安全”很多人会选“是”但正确答案是“不完全是”。atomic 只能保证属性的 getter/setter 方法线程安全也就是读写本身不会因为多线程同时访问而崩溃但它不能保证业务逻辑的原子性。比如说一个 atomic 的 NSMutableArray 属性两个线程同时去 addObject 和 removeAllObjectsarray 本身还是可能因为并发操作而崩溃。正确做法是给操作加锁或者换用串行队列进行读写隔离。答出这一点就比“用 atomic 就好了”高出一个段位。再常见的是关于 dispatch_semaphore 的题目。我见过校招笔试里的小题用 GCD 实现一个只允许 3 个并发任务的信号量。答案无非是dispatch_semaphore_t sem dispatch_semaphore_create(3); for (NSInteger i 0; i 100; i) { dispatch_async(concurrentQueue, ^{ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 执行任务 dispatch_semaphore_signal(sem); }); }这类题不难但要注意dispatch_semaphore_wait和dispatch_semaphore_signal必须成对出现而且 semaphore 的初始值要根据题目要求设置。我在阅读答卷时见过不少考生在 wait 之前忘记把队列声明成全局并发队列导致任务全部串行执行答案虽然不报错但显然没理解题意。信号量初始值越大同时执行的并发任务数越多这个点要记住。3.3 常见的死锁场景速查这里顺手整理一个“死锁场景速查表”方便你考前过一眼。如果你能用一两句话说明每条的原因笔试里碰到类似题基本就不会错了。场景是否死锁原因主队列中同步提交 block 到主队列是主队列串行当前 block 占用队列新 block 无法执行串行队列中同步提交到同一串行队列是队列等待自己释放互相挂起并发队列中同步提交到同一并发队列否并发队列可提供新线程执行主线程dispatch_async到主队列否异步提交当前 block 执行完再执行新 blockdispatch_group_wait在同一个串行队列内调用有风险若 group 包含当前队列的 block等待会阻塞这张速查表如果你能默写出来GCD 类题目基本就稳了。真正理解每一条背后的原因比背十道例题都管用。4. 网络层与数据持久化从协议细节到读写安全的延伸题4.1 TCP/UDP、HTTP/HTTPS 在笔试中的常见问法网络层在二卷里没有占大量篇幅但每年都会有送分题关键是别丢分。常见的问法是TCP 和 UDP 的区别是什么HTTP 和 HTTPS 的区别是什么这些问题看似基础但在笔试中想拿高分不能只写三行字。以 TCP 和 UDP 为例答题可以分三层。第一层TCP 是面向连接的可靠传输协议有三次握手、四次挥手、超时重传、流量控制等机制UDP 是无连接的不可靠传输协议没有上述机制。第二层对应到使用场景TCP 适合文件传输、网页浏览、远程登录这些对完整性有要求的场景UDP 适合实时音视频、游戏帧同步、DNS 查询这些对时延更敏感的场景。第三层可以补充iOS 中大部分业务网络请求基于 TCP而视频直播或语音对讲可能采用 UDP 的私有协议。写到这份上阅卷人会认为你是真正做过多媒体业务的人。HTTP 与 HTTPS 的问题则要答到加密流程。HTTPS 不是独立协议而是 HTTP over TLS。可以简述 TLS 握手过程客户端发起 ClientHello服务端返回证书和 ServerHello客户端验证证书并协商密钥之后用对称加密传输数据。如果再提一句“证书校验失败会触发警告”以及 iOS 开发中如何处理 ATSApp Transport Security配置这题就答得很有说服力了。不过注意别展开太远笔试时间有限先保证核心点在再谈加分项。4.2 缓存策略与本地持久化不只考 API更考方案选型本地持久化这一块二卷的题目风格偏向方案选型。比如它会问你一份用户信息、一批搜索结果、一版离线数据分别适合用什么方式存储我的答题思路是先给分类法用户偏好设置用 NSUserDefaults轻量结构化数据用 plist 或 NSKeyedArchiver需要按条件查询的数据用 SQLite或者直接用 FMDB、WCDB、GRDB 等封装库大量二进制文件则直接写文件系统可以在沙盒目录下建 Library/Caches 来缓存用 tmp 来放临时文件。这个分类本身就能看出你是否有工程经验比单纯背 API 有说服力得多。关于网络缓存策略iOS 里有 NSURLCache也有第三方库如 SDWebImage 针对图片的自定义缓存。笔试题常问“内存缓存和磁盘缓存如何协同”经典答案是用 NSCache 做内存缓存内存不足时可以自动清理用文件或数据库做磁盘缓存启动时再加载到内存。如果再考深一点问“缓存穿透、缓存击穿怎么解决”就需要用到“多层存储”和“过期校验”的思路你在实际项目里怎么写的就怎么答不要背抽象的概念。4.3 沙盒目录与数据安全细节题也很容易翻车还有一类细节题考察沙盒目录结构。比如问 Library/Caches、Library/Preferences、Documents、tmp 分别适合放什么。二卷里出现过类似的题我记得是让你判断哪些数据会被系统自动清理。答错的人不少因为平时对这些目录的使用停留在直觉层面。正确的理解是Documents 会被 iTunes 备份适合放用户生成的数据Library/Caches 保存缓存文件系统可能清理不适合放重要数据tmp 是临时目录随时可能被清空Library/Preferences 保存偏好设置也可能被备份。准备笔试时最好能把每个目录的“是否备份、是否会被清理、适合存储什么”整理成一张小表当作必背项做题时一眼就能定位。5. 实战题从手写代码到架构设计的开放型考察5.1 手写题链表反转、LRU 缓存、线程安全单例至少拿下一道大部分 iOS 笔试试卷的最后大概率有一道手写代码题。二卷的手写题大概是三道挑一道或两道最常见的是链表反转、LRU 缓存、线程安全单例。这类题在 LeetCode 上是简单到中等难度但在笔试的限时环境下很多人反而写不顺。链表反转是数据结构题用迭代法写ListNode *reverseList(ListNode *head) { ListNode *prev nil; ListNode *cur head; while (cur) { ListNode *next cur.next; cur.next prev; prev cur; cur next; } return prev; }这里最容易被扣分的点是忘了保存 next 指针。很多考生一紧张先执行cur.next prev然后想访问原来的cur.next时已经没了。写的时候最好把三行核心逻辑先列出来保存 next、反转指向、移动 prev 和 cur用注释把步骤标清楚阅卷人看着也舒服。LRU 缓存是另一种高频手写题。它考的是双向链表 哈希表get 和 put 都要 O(1)。笔试如果只要求思路描述可以把“哈希表提供快速查找、双向链表维护访问顺序”写清楚如果要求完整实现就老老实实写节点类、头尾节点、moveToHead 和 removeTail 两个方法。我实际操作中发现很多候选人能写出思路但写不完代码原因是入口判断不完整。建议多加几层判断先处理 key 不存在的情况再处理容量满的情况逻辑就不会乱。线程安全单例则是最简单的保底题 (instancetype)sharedInstance { static id instance nil; static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ instance [[self alloc] init]; }); return instance; }这道题必须满分加手速因为它几乎不用思考。如果连这题都写不出来前面答得再好都会让人怀疑动手能力。写得时候顺手检查一下 dispatch_once_t 是不是 static有没有加dispatch_once这两点经常有人漏。5.2 架构题设计一个图片加载库怎么答才显得有经验架构设计题是笔试里最开放的题型也是最能拉开差距的题型。常见的题目是“设计一个支持异步加载、缓存、并发控制、取消操作的图片加载库画一下模块划分并说明关键流程。”我先说一个绝大多数考生会犯的错直接输出一张数据流图然后解释每一层是干什么的。这种答案不算错但显得太模式化。更好的做法是先把关键流程写出来请求图片时先查内存缓存查不到再查磁盘缓存还没有则发起网络请求拿到数据后解码并写入缓存最后回到主线程调用回调。这个过程完全可以用箭头链写清楚逻辑顺序清晰比贴一张看不懂的架构图强得多。接着再从模块划分去讲缓存模块负责内存缓存和磁盘缓存网络模块负责发起请求和响应回调解码模块负责把 NSData 转成 UIImage 并处理缩放调度模块负责并发控制与取消。每一个模块都可以说明它与旁边模块的交互方式比如缓存模块对外只暴露 get/set 接口调用方不关心缓存策略。能说到这一层面试官就知道你做过独立模块设计而不是只会调 SDWebImage 的 API。开放题最忌空谈概念适当的“工程化细节”也很加分。比如提到图片解码应该放到子线程避免主线程卡顿提到内存缓存收到UIApplicationDidReceiveMemoryWarningNotification时要清理提到缓存 key 可以基于 URL 和裁剪参数拼接。这些细节你在项目里踩过的坑都是现场最有说服力的答案比背十个设计模式管用。5.3 答题时的表达策略先写结论再分层展开开放题的书写格式也会影响分数。我自己的经验是不要把答案写成一整段叙述而是采用“先结论后分层”的结构。比如架构题可以先写“我采用三级缓存方案内存缓存、磁盘缓存、网络加载”然后再分别扩展。这种写法不仅让阅卷人好给步骤分也让你有明确的行文方向不会越写越散。如果你对某个概念只有一个模糊印象也千万不要直接放弃。先把你能确定的部分写下来再补充一个“在此基础上还需要考虑 xxx”的句子有时候这反而能帮你争到部分分数。笔试和面试一样能主动暴露思考过程的人得分往往高于那些沉默的人。答题时用短句、分点、关键词加粗都能提高阅卷体验。6. 备考路线与排错技巧从笔试到面试的最后一公里6.1 时间规划校招笔试突击别贪多先打牢三大板块如果现在距离笔试只有三到五周没有必要去啃 Swift 新语法或者 SwiftUI那不是这套卷子的重点。应当把时间分配在三大板块上Objective-C 语言特性与内存管理、GCD 多线程、网络与数据持久化。这三块占分高、题型稳定复习性价比最高而且不管哪家公司的 iOS 笔试基本都会覆盖。具体节奏可以这样安排第一周把 Objective-C 基础过一遍重点看消息发送、isa 指针、引用计数、属性修饰符边看边做题第二周主攻 GCD 题把上面提到的死锁场景逐个手推第三周做网络、持久化、沙盒相关的题把答法练熟如果还有余力再刷一套数据结构题和一两道系统设计题。这个计划不需要很满但务必每天保持手写代码一天不写手就生了一到考场上特别容易卡壳。6.2 答卷技巧不空题、多写关键句、学会分点作答笔试答卷最怕的不是不会而是会但写不出来。尤其是简答题一道 8 分的题如果只写一句话哪怕那句话是对的最多也就拿两三分但如果你能用“结论 原因 场景举例”的方式答满分数会好看很多。再分享一个小技巧遇到不会的题就把题目里的关键词抄一遍然后围绕关键词展开你能想到的所有相关知识。比如“KVO 的内部实现原理”不会你可以先答 KVO 基于运行时动态生成子类并重写 setter 方法再答如何手动触发 KVO最后答 KVO 在 iOS 10 以后新增的 NSKeyValueObservation 新接口。这样就算拿不到满分也能拿到大部分步骤分。千万别空着空题一定零分写了至少有机会。6.3 避坑清单从阅卷角度看最容易被扣分的行为我在帮公司校招看过一部分笔试答卷之后总结出几个特别普遍的扣分点。第一代码缩进混乱关键方法名写错比如dispatch_semaphore_create写成了dispatch_semaphore_creat这种低级错误直接送掉送分题。第二简答题答非所问题目问“为什么”答案却是“是什么”逻辑根本没对上。第三所有题目都答得很短哪怕知道的知识也不展开考官想给分都找不到给的理由。这些坑都可以通过考前做两三套真题来规避务必在限时环境下完整写完一套题再对照答案逐题复盘。还有一点容易被忽略笔试往往使用网页答题系统不支持自动纠错。写代码时最好先在草稿上过一遍语法再复制进答题区不要直接在文本框里乱敲。不要因为大小写错误、符号错误这种低级失误丢了送分得不偿失。最后说一个我自己的体会。我当年备考这份卷子的场景到现在还记得晚上在宿舍一遍一遍手推 GCD 死锁第二天早上看到自己的笔记全是箭头。真正把原理想通之后后面无论是笔试还是面试问到底层原理都不会再慌。这份 2020 年的卷子虽然年头不短但它的出题框架到现在都还在被很多公司沿用。能把它完整吃透你的 iOS 基础能力基本就立住了。