iOS校招笔试复盘:Runtime、weak、GCD、HTTPS与MVVM核心考点详解 最近整理旧电脑里的学习资料翻到一份 2018 年校招 iOS 方向的笔试复盘记录正是“第四批”那一轮。说句实话那批题目放到今天来看依然很能打Runtime 消息转发、weak 实现原理、GCD 死锁、HTTPS 握手、MVVM 选型这些知识点到现在依然是 iOS 面试的高频考点只是换了一层更时髦的包装。当时我陪几个学弟准备这轮笔试自己也把 OC 底层、内存管理、多线程和网络通信完整过了一遍踩了不少坑也总结出一些比较实用的答题套路。这篇博文就把我当时拆过的题目、解题思路和后续几轮面试的延伸追问整理出来给正在准备 iOS 校招、或者想系统补一补底层功底的开发者做个参考。如果你只是想背面试题答案这篇内容也能当速查表但我更建议你跟着把原理理顺因为大厂面试官基本都会顺着你的回答往下追问三层只背结论很容易露馅。1. 这批题目背后的出题思路1.1 “第四批”到底在考什么先交代一下当年的背景。2018 年左右Swift 已经出到 4.0Xcode 9 和 iOS 11 是主力组合但很多公司的主力业务代码还是 Objective-C 写的所以校招笔试普遍以 OC 为主Swift 最多作为加分题出现。字节跳动这套题延续了前三批的覆盖面但它有个很明显的特征底层原理比例比前三批更高而且代码题占比明显提升。从我当时整理的题目分布来看大致可以分成五块OC 语言与 Runtime约 20%重点考对象本质、消息发送、消息转发、KVO 原理。内存管理约 20%重点考引用计数、weak 实现、循环引用、Autoreleasepool。多线程与并发约 15%重点考 GCD 队列、死锁场景、线程安全。网络与数据解析约 15%重点考 HTTP/HTTPS 流程、TCP 握手、JSON 序列化。架构设计与综合开放题约 30%包含 MVVM 选型、组件化思路、性能优化方案等。这个分布其实透露了一个信号命题组筛选的不是“会写 UI、会调 API”的同学而是能在生产环境里定位崩溃、排查卡顿、设计模块边界的人。2018 年那会儿大厂业务扩张很快新入职的同学可能很快就要独立负责一个模块所以笔试必须把“能不能写出可维护代码”这件事测出来。1.2 从题目反推候选人画像我复盘完这套题之后最大的感受是它要的不是背诵型选手而是“理解型选手”。比如同样考 weak初级问法是“weak 和 assign 有什么区别”这套题直接考到了“weak 变量为什么能自动置为 nil底层是怎么实现的”。这种题光背结论是答不好的必须理解 SideTable、weak 表、dealloc 的触发流程才能把链路讲完整。另外这套题对“边界条件”特别敏感。笔试里的手写代码题比如用递归实现链表反转、给一个数组实现去重排序这些题本身不复杂但考察点全在边界处理上空数组、单元素数组、已经有序的数组、循环引用风险、递归深度限制。我认识的一位同学当时就因为在链表反转题里没处理 head 为空的情况直接被扣了分很可惜。这种出题思路其实也为候选人画了一幅能力画像基本功扎实、能写代码、会分析问题、有工程意识。如果你正在准备校招可以拿这四个维度自查一下缺哪块补哪块。2. 核心考点详解与解题思路2.1 OC 对象本质与 Runtime 消息机制这套题里有一道非常经典的题简述一次 OC 方法调用的完整流程从objc_msgSend出发直到方法执行完毕。这个题目我现在还经常用来考实习生因为它能把一个人对 Runtime 的理解程度测得很透。完整的链路是这样的调用[obj doSomething]时编译器会将其转换为objc_msgSend(obj, selector(doSomething))。消息发送的第一步是查缓存也就是类的cache_t如果缓存命中就直接跳到方法实现如果没命中就去类对象的class_rw_t里的方法列表中查找找到了就缓存起来再跳转。如果本类找不到会沿着superclass指针逐级向上找直到 NSObject 为止。如果整个继承链都找不到该方法并不会立刻崩溃而是进入消息转发流程。先走resolveInstanceMethod:允许你动态添加方法比如class_addMethod如果返回 NO再走forwardingTargetForSelector:允许你把消息转给其他对象处理如果这一步也返回 nil最后会走forwardInvocation:配合methodSignatureForSelector:做完整的消息转发。到了这一步还没处理才会抛出经典的unrecognized selector sent to instance崩溃。我推荐的回答顺序是先讲缓存、再讲查找、再讲转发最后补充 isa 和类的关系也就是“实例对象的 isa 指向类对象类对象的 isa 指向元类元类的 isa 指向根元类”这条链。这样答既完整又有层次感。面试官如果接着问“消息转发可以用来做什么”可以回答防崩溃库、AOP 日志、多继承替代方案等这些都属于加分项。注意这里有一个很多人忽略的细节缓存查找使用的 key 是SEL 类对象指针的组合不是单纯的方法名。不同类即使有同名方法缓存条目也是分开的。答出这一层面试官会明显觉得你是真读过源码。2.2 引用计数与 weak 实现原理内存管理是第二块重头戏。这套题里有一道典型的代码分析题在 ARC 环境下为什么__weak变量在对象释放后会自动变成 nil答案不在于结论而在于底层机制。每个对象都有对应的SideTable里面包含引用计数表和 weak 表。当你用__weak修饰一个变量指向对象时Runtime 会在 weak 表中以对象地址为 key 注册一条记录value 是指向这个 weak 变量的指针。当对象执行dealloc时会通过弱引用机制从 weak 表中找到所有指向它的 weak 变量把它们统一置为 nil然后清理引用计数表。所以才不会出现“野指针崩溃”。理解了这块原理很多衍生题就能顺下来。比如“为什么 delegate 要用 weak”本质上是为了避免循环引用因为 delegate 通常属于代理方而被代理方持有 delegate 引用后如果 delegate 又持有被代理方就会形成循环导致双方都无法释放。再比如 block 的循环引用。这道题里给出了一个典型的场景ViewController 持有 block 属性block 内部使用self而 self 又持有这个 block 属性形成 self - block - self 的环。解决办法是用__weak typeof(self) weakSelf self;在 block 内部用 weakSelf 替代 self。但这里有个隐藏坑如果 block 内部涉及异步操作用 weakSelf 可能导致对象在 block 执行期间被提前释放所以有时需要__strong typeof(weakSelf) strongSelf weakSelf;在 block 开头 strong 一下保证生命周期。我在准备这套题时自己画了一张对象生命周期的图创建、引用计数1、-1、释放、weak 置 nil把这块知识全串起来了。后来面试时讲循环引用脑子里就是这张图不会漏掉任何环节。2.3 GCD 队列、任务组合与死锁问题多线程部分出过一道非常经典的手写题在主队列上同步执行一个主队列任务会发生什么为什么答案是死锁。原因要从 GCD 的队列和任务组合讲起。主队列是一个串行队列它里面执行任务的方式是 FIFO。当你调用dispatch_sync(dispatch_get_main_queue(), ^{ ... })时当前主线程正在执行这段同步派发代码而同步派发要求“等新任务执行完再继续往下走”。于是主线程一边等着新任务执行一边又被主队列的串行特性卡住因为主队列当前正在执行的任务还没结束新任务不可能被派发到主线程。两边互相等待就死锁了。回答完这个现象后建议再补一层死锁的本质是“串行队列 同步任务 在同一线程上互相等待”。如果换成并发队列比如dispatch_get_global_queue(0, 0)就不会死锁因为并发队列不要求当前任务结束后才能执行下一个任务。这个对比能体现你理解的是机制而不是背答案。除了死锁这套题还考了dispatch_group和dispatch_semaphore的使用场景。印象中有一道题是“如何等待多个异步网络请求全部完成后再刷新 UI”。标准答案是用dispatch_group_enter和dispatch_group_leave配合dispatch_group_notify。注意enter必须和leave成对出现如果leave次数比enter多会导致dispatch_group_notify提前回调这是个非常隐蔽的 bug。另一个方案是信号量dispatch_semaphore_wait但如果在主线程等待网络回调会造成界面卡顿所以一般只在并发子线程内使用信号量做同步不建议直接在主线程里 wait。我后来在实际项目里遇到过类似问题发现用dispatch_group时最容易踩的坑是网络请求回调不在同一个队列里导致leave和enter的调用时机不确定。解决方案是进入请求前enter在请求的完成回调里leave并且保证回调一定执行否则 group 永远等不到结束。2.4 Runloop 与 UI 刷新机制Runloop 这么偏底层的内容这套题里也考了题目类似“NSTimer 在 UIScrollView 滚动时为什么不准怎么解决”。这个问题需要先解释 Runloop 的 Mode 机制。iOS 的 Runloop 有多种 Mode例如kCFRunLoopDefaultMode和UITrackingRunLoopMode。App 正常运行在主线程的 Runloop 里当用户滑动 ScrollView 时Runloop 会切换到UITrackingRunLoopMode此时 default mode 下的 Timer 不会被触发所以 NSTimer 就会出现“推迟执行”的现象看起来就不准了。解决办法有两个。第一把 Timer 注册到NSRunLoopCommonModes这样无论 Runloop 切换到哪个 ModeTimer 都能被分发执行。第二用dispatch_source_t里的DISPATCH_SOURCE_TYPE_TIMER它不依赖 Runloop直接由内核触发精度更高也是现在比较推荐的方案。这里有个容易忽略的点NSTimer被添加到 Runloop 时是被强引用的如果 Timer 的 block 里又使用了self同样会形成循环引用。就算把 Mode 改成 CommonModes这个问题也无法避免所以使用 Timer 时最好在viewWillDisappear或dealloc里调用invalidate把 Timer 从 Runloop 中移除。我当年准备这块时把 Runloop 和 Autoreleasepool 的释放时机、performSelector 的线程依赖、以及 CADisplayLink 的刷新机制放在一起复习。因为这几块都围绕“事件循环”这个核心串起来理解比单独背要记得牢。2.5 HTTP/HTTPS 与网络安全网络部分考了一道很经典的“HTTPS 握手流程”题而且会追问证书校验。这道题放到现在每一个做 iOS 开发的人都应该能答清楚。HTTPS 的核心是在 HTTP 和 TCP 之间加了一层 TLS。完整握手流程可以通俗理解为三步客户端先发送ClientHello告诉服务器自己支持的 TLS 版本和加密套件服务器返回ServerHello、自己的证书和密钥交换参数客户端验证证书合法性生成一个随机对称密钥并用服务器的公钥加密后发送给服务器双方各持一把相同的对称密钥后续通信都走对称加密。整个过程里非对称加密只用来协商密钥真正的数据加密用的是对称加密这样兼顾了安全和性能。中间人攻击的防范关键在“客户端如何信任服务器证书”。正规链路是证书由 CA 机构签发系统内置了根证书。客户端收到证书后会沿着证书链逐级验证直到找到内置的根证书同时校验域名是否匹配。如果服务端返回的是自签名证书没有经过可信 CA 校验客户端就会拒绝连接。面试官如果顺着问“Charles 等代理工具为什么能抓 HTTPS 包”答案其实是在客户端安装了代理工具的根证书客户端信任了它发来的证书所以中间人可以解密和重加密流量。理解到这里就 OK 了不要展开讲太多和工具使用相关的内容重点还是算法和机制。注意TLS 1.3 已经简化了握手流程把往返次数从 2-RTT 降到了 1-RTT部分场景甚至 0-RTT。面试时如果能提一句“TLS 1.3 还支持会话恢复”会显得你对新技术保持关注这在 2018 年那会儿算很大的加分项。2.6 架构设计、MVVM 与组件化思路最后一块是开放题也是这套题里占分比重最高、但很多同学最容易失分的部分。题目类型一般是“你们项目的架构是怎么组织的MVC、MVVM、MVP 分别适合什么场景如果要重构你会怎么选”这种题没有标准答案但答得好不好其实很容易分辨。最忌讳的回答是直接背书“MVC 是 Model-View-ControllerMVVM 是 Model-View-ViewModelMVVM 更好。”面试官想听的是你在真实场景里的权衡过程。我当时准备了一套回答结构先讲 MVC 的痛点ViewController 职责过重也就是大家常说的 Massive View Controller一旦业务复杂起来Model 和 View 的状态同步会变得非常混乱。然后讲 MVVM 怎么解决这个问题ViewModel 负责把 Model 转换为 View 可以直接展示的数据状态View 和 ViewModel 之间通过绑定关系自动同步Controller 只负责搭建和导航职责变轻了。关键点在于MVVM 不是银弹它的前提是需要一个可靠的数据绑定机制在 OC 里可能是 KVO RAC 或者 MVVM 框架在 Swift 里可能是 Combine 或第三方绑定库如果团队不熟悉这些技术引入 MVVM 的初期成本会很高。组件化也是一样的逻辑。2018 年那段时间很多团队都在搞组件化把大工程拆成一个个独立的 pod 仓库。组件化解决的是多人协作的编译速度、模块复用、版本管理问题但它不是免费的需要设计好模块间的通讯机制比如路由、协议、或命令模式否则很容易拆成“原子化但不连通的孤岛”。回答这类题的时候我建议按“场景 - 方案 - 收益 - 风险”四步来讲。不要一上来就选型先分析当前业务处在什么阶段、团队规模多大、痛点是什么然后再提出你的选型建议。面试官听完一般都会点一下头因为这是真实的架构决策过程而不是面试表演。3. 实操过程限时笔试的答题策略与代码细节3.1 拿到试卷后的前 10 分钟怎么分配这套题题量不小我当时给学弟们建议的节奏是前 10 分钟绝不写代码先把所有题目扫一遍按“会做、部分会、完全不会”三档标记。会做的题优先写因为这类题分数稳完全不会的题最后再蒙把时间留给中等难度的题。这里有一个技巧先写简答和代码分析题再写大段的算法题。原因很简单简答题只要踩中关键词就能得分而算法题就算你思路对了代码写不完也只能得一半分。先把能拿的分拿满再拿时间耗在复杂题上。我当年陪练时经常遇到一个情况同学在算法题上耗了 40 分钟结果后面几道内存管理简答题因为时间不够只写了一行结论损失很惨。如果反过来先把 Runtime 那套流程默写完整再回去写算法总分反而会更高。校招笔试不是竞赛排名而是通过性考试拿满基础分是你最大的目标。3.2 手写代码题最容易忽略的边界条件这套题里的手写代码题难度其实不算高比如“用递归实现单链表反转”“给字符串去重”“判断两个矩形是否相交”这类。但失分点非常集中几乎全在边界条件上。举一个例子递归实现单链表反转。很多同学的答案是先写一个递归函数然后在主函数里调用但漏掉了“链表为空”和“链表只有一个节点”的情况。面试官看到的代码一上来就访问head-next直接崩溃。我的习惯是函数第一行先处理两个 base case不管是递归还是迭代写法都先判断!head || !head-next然后再进入主逻辑这样既安全又展示了你对边界条件的敏感。再比如写NSString类别的字符去重不少人直接用一个 NSMutableSet 收集字符然后遍历输出。这个思路没问题但要注意NSString内部可能是 UTF-16 编码拆成一个个unichar处理时遇到 emoji 这类需要双字符组合的符号就可能拆错。如果能想到用enumerateSubstringsInRange:options:NSStringEnumerationByComposedCharacterSequences按用户感知字符来遍历就是一招很好的加分回答。还有一个细节是注释。手写代码题不要求跑通但面试官要能看懂你的思路。强烈建议在关键步骤处写一行注释比如“这里是递归终止条件”“这里用 weak 避免循环引用”。不要小看这个动作它直接影响阅卷人对你代码能力的评价。3.3 开放题怎么答才不会跑题前面提过“场景 - 方案 - 收益 - 风险”的答题结构这里再展开说一下。如果你在笔试里遇到“如何设计一个 IM 消息列表页的架构”这类开放题千万别直接开始列 UI 组件也别从数据库表结构开始面试官想看的是你的思考框架。我当时的标准答法是先明确业务场景比如消息列表需要频繁增删改查、可能有未读数角标、需要分页加载然后提出整体方案比如 Model 层用轻量数据模型ViewModel 负责状态映射View 层用 UITableView 加 diff 算法来更新数据持久化用 SQLite。接着讲这么做的好处比如界面和数据逻辑解耦便于单元测试。最后主动提风险比如 diff 算法在超大数据量下可能性能不够需要做分页和预计算。这套结构能让你在没有任何标准答案的情况下依然拿到不错的分数因为面试官能从中看到你的工程思维。笔试阅卷时间有限你能不能在有限篇幅里把答案组织得有逻辑直接决定他愿不愿意多给你一点分。4. 常见问题与踩坑实录4.1 笔试中常见的失分点我把当时带同学刷题过程中见过的典型失分点整理成了一张表方便大家对照自查。题目类型常见错误正确姿势Runtime 消息转发只答到objc_msgSend就结束补全缓存查找、类/元类链、三级消息转发流程weak 自动置 nil只说“系统自动处理”讲清 SideTable、weak 表、dealloc 触发链路GCD 死锁只写“会崩溃”说明主队列串行特性与同步派发互相等待的本质NSTimer 不准时直接说“把 Timer 加到 CommonModes”补充根本原因是 Runloop Mode 切换HTTPS 握手只答“客户端发请求、服务器回证书”讲清 ClientHello、ServerHello、证书验证、密钥协商架构开放题背书式对比 MVC/MVVM按“场景-方案-收益-风险”结构分析手写算法不处理空输入和单元素输入函数开头先写边界条件block 循环引用只提 delegate 和 block补充 NSTimer、Notification、GCD 里的 retain cycle 场景这张表其实也是面试前的复习大纲。我建议你把每一行都展开讲一遍能讲够 3 分钟说明你对知识点的理解已经到位了讲不满 30 秒那就说明还有漏洞赶紧翻书补。4.2 我踩过的几个具体坑第一个坑是循环引用只回答了代理和 block漏了 NSTimer。当时有一道题问“项目中遇到过哪些循环引用”我洋洋洒洒写了 delegate 和 block结果面试官追问“那 Timer 呢”我一下子愣住了。后来才意识到NSTimer的 target 是被 timer 强引用的如果 target 又持有 timer 属性就会形成循环。正确处理是在viewWillDisappear或dealloc里invalidate并尽量使用 block 版本 API 配合 weak self。第二个坑是手写 GCD 死锁题时只写了“主线程同步执行主队列任务会卡死”的现象没有解释为什么。后来我去读了相关源码才意识到关键点是dispatch_sync需要等待任务执行完毕而主队列的串行性又阻止任务在当前线程空闲前被派发。只有把这个“互相等待”的逻辑讲透面试官才会觉得你是真理解而不是背过答案。第三个坑是网络题只答了握手流程没提证书校验。面试官接着问“那客户端怎么知道这个证书是可信的”我又懵了。后来我把 HTTPS 拆成四个层次来记对称加密保证数据安全、非对称加密保证密钥协商安全、CA 证书保证公钥归属可信、签名和摘要保证内容不被篡改。这四层全说清楚网络安全题基本就没有死角了。4.3 从笔试到面试后续轮次的延伸考点通过了笔试不要以为就万事大吉了。从这批笔试的实际流程看笔试只是第一关后面还有至少两轮技术面试很多面试官会直接拿你笔试里的答案做延伸追问。比如笔试里考了weak的底层实现面试官可能会追问“weak 变量为什么必须在对象释放时由 Runtime 统一置空而不是在访问时判断如果对象内存地址被复用会发生什么”这种问题其实就是考察你对dealloc触发时机和objc_destructInstance理解得深不深。再比如笔试里写了 block 的循环引用解决方式面试官可能会问“在 block 里用 weakSelf 之后如果异步任务执行到一半 self 被释放了会发生什么你如何保证任务执行期间 self 一定存活”这就涉及__strong局部变量的使用以及业务上是否需要延长对象生命周期的判断。所以我在带同学准备时一直强调一个理念笔试里写下的每一个结论都要做好被追问的准备。你在复习时多问自己一层“为什么”面试时就能少一分慌张。不要只背“标准答案”而是把知识链补完整。5. 这套题折射出的 iOS 开发者成长路径5.1 怎么把零散知识点串成体系我复习完这套题之后最大的收获是明白了 iOS 的知识点不是一个一个孤立的而是围绕“对象生命周期”和“线程事件循环”两条主线展开的。对象生命周期主线从对象创建、引用计数变化、weak 引用、block 捕获、到 dealloc 释放涉及内存管理、Runtime、循环引用。你只要把这条线从头到尾走一遍基本上就把 OC 最核心的知识点都覆盖了。线程事件循环主线从 Runloop 启动、事件分发、Timer 触发、UI 刷新到 GCD 队列派发、线程同步、网络回调涉及 Runloop、多线程、网络、UI 性能。这两条线交叉的部分就是 App 运行时最复杂也最容易出问题的地方。建议你也按照这两条主线去整理自己的知识体系比按“内存管理”、“多线程”、“网络”这种教科书目录去背要高效很多。因为面试官的问题很少直接说“请回答内存管理”而是给你一个场景比如“TableView 滑动卡顿你怎么排查”这时候你脑子里必须能快速把 Runloop、AutoLayout、图片解码、离屏渲染这些点全部串起来。5.2 当年热词与今天的对比2018 年那会儿iOS 圈子里最流行的话题是组件化、MVVM、React Native、动态化热修复。放到现在这些词里有些已经不那么热了比如热修复因为各种原因在很多团队被叫停有些仍然是主流比如组件化和模块化几乎每个中型团队都有一套自己的方案。而今天的新热词已经变成了 SwiftUI、Swift Concurrency、async/await、Swift 6 严格并发、以及 iOS 26 带来的各种新 API。比如 iOS 26 里UIApplication.shared.setAlternateIconName已经支持动态替换应用图标面试时可能会从系统弹窗、调用时机、权限变更等角度来提问。但不管题目怎么变底层仍然是基于 UIKit 的应用生命周期和事件响应机制。我一直觉得iOS 开发有一个很特殊的规律上层的技术栈三五年就换一茬但底层原理反而越来越稳定。你花三个月时间把 Runtime 和 Runloop 啃透再过五年依然有用但你如果只追新的 UI 框架可能每两年就要重新学一批 API。所以对于还在学校或者刚工作不久的同学我的建议是优先打好底层基础再横向去学 SwiftUI、跨端方案这些新东西不要本末倒置。我个人在实际复习这套题时的体会是笔试最大的价值不是那个分数而是逼着你把整个知识体系拉通一遍。每道题背后都是一条完整的技术链你能顺着其中一条链讲清楚后面几轮面试就会有底气很多。最后再分享一个小技巧笔试前半个月每天抽半小时对着表格默写一个核心知识点不要看资料写完之后再对照差漏补缺。坚持下来效果很明显至少我认识的几个学弟都顺利拿到了后续面试机会。希望这篇复盘能对正在准备 iOS 方向校招的同学有点帮助。