
每年校招季后台都会来一波准备笔试的私信。前几天一个同学把金山办公2020校招iOS开发工程师笔试题一发我让我帮他过一遍答题思路。我花了一晚上把整套卷子的考点逐条拆了拆说实话这套题出得挺见功底题干不绕看着都是iOS开发里的日常知识点但几乎每个点都能往深里追问三层刚好卡在“背过八股能答”和“真正理解才答得好”之间。这篇文章我就从面试官视角把高频题型的答题骨架、底层原理、追问套路完整拆一遍最后再讲两个我在真实项目里因为基础不牢踩过的坑给正在刷题备战的同学做个参考。1. 试卷整体拆解这些考点背后藏着什么样的人才画像1.1 考点分布与分值逻辑在动笔逐题拆解之前我先把整套卷子的考点分布拉了一遍。虽然叫笔试题一但从题量和难度曲线看这套卷子的结构基本符合经典校招笔试模型单选和多选覆盖基础认知填空题补查细节记忆简答题考知识体系的连贯性最后一道或两道编程/设计题看工程思维。整体分值权重大致是这样考点模块预估分值占比典型题型OC/Swift语言基础25%-30%单选、填空、简答内存管理与ARC15%-20%选择、简答、代码改错多线程与并发15%-20%选择、场景题、编程题UIKit与渲染10%-15%选择、简答、性能分析网络与架构10%-15%简答、设计题数据结构与算法10%编程题这个分布很有意思。语言基础和内存管理加起来几乎占了半壁江山说明这套题筛选的并不是“会背多少API”的人而是“能不能在生产环境里写出不出事故代码”的人。尤其是内存管理在ARC普及之后很多候选人会觉得这块不重要了但恰恰是循环引用、autoreleasepool释放时机、Block捕获变量这类细节最能看出一个人是背过还是真的在项目里调过内存。多线程和UI渲染的占比也不低这跟金山办公的产品形态强相关。WPS核心是文档处理一个文档从打开到渲染到编辑到保存处处都是多线程协作和主线程流畅度问题。笔试题愿意在这块多出几道说明团队是真的在关心候选人能不能直接上手干活。1.2 从WPS的业务画像反推命题思路我特意把这套题的考点和金山办公的产品线对照了一下发现命题逻辑非常清晰。金山办公的核心产品是WPS Office覆盖文字、表格、演示、PDF还要做云协作、多端同步、会员增值服务。在iOS端WPS的工程团队每天面对的就是这么几类问题大文档怎么在内存受限的移动端打开而不爆内存。一个几十MB的DOCX文件解析出来的文档对象模型可能占几百MB内存这直接考到内存管理和懒加载设计。渲染引擎怎么保证滚动不掉帧。文档排版、字体渲染、图片加载都是重活UI主线程一旦被占用用户立刻能感知到卡顿。多人协同编辑时本地操作和服务端推送怎么合并。这涉及多线程、数据一致性、锁的粒度设计。iPad分屏和iOS新版本适配。WPS要兼容iOS 11.0及以上系统还要处理不同尺寸iPad的分屏、拖拽、多窗口场景。所以你以为笔试考的是一道道孤立的知识点实际上每道题背后都对应着团队在真实业务里踩过的坑。如果只看题目本身去背答案理解永远是浮在表面的如果能顺着题目往回想想“什么场景下会出这种问题”答题的深度和广度会完全不同。这也是我下面逐题拆解时反复强调底层原理的原因。2. 高频考点逐题击破答题骨架与追问应对2.1 内存管理ARC、循环引用与autoreleasepool内存管理这块几乎每套iOS笔试题都会出现但考法差异很大。这套卷子里的内存题我印象比较深的是那一类“给一段代码判断是否有内存问题”的题。题型一般是一个ViewController持有BlockBlock内部又使用了self问会不会循环引用、怎么改。这种题的完整答题路径分三步。第一步说结论会循环引用因为self - block属性 - block内部强引用self引用计数互相持有ARC下无法自动释放。第二步说解决方案用__weak或者__block弱引用self或者iOS 12之后用__weak加__strong的组合在Block内部做一次局部强引用保证执行期间self不会提前释放。第三步主动延伸如果Block被存储为属性使用copy修饰如果是GCD Block短期执行完不会造成长期循环引用但如果Block捕获了self且self持有这个Block同样会出问题。我面试时最喜欢追问的一个点是ARC下是不是就不用管内存了这个问题能淘汰掉一大批人。正确回答是ARC只是编译器自动帮你在合适位置插入retain/release/autorelease它解决的是“该释放时自动释放”但解决不了“互相持有导致永远无法释放”的循环引用也解决不了大对象峰值内存过高的问题。再往下追问就是autoreleasepool的作用。很多候选人知道autoreleasepool能降低峰值内存但说不清原理——其实它底层是一个个AutoreleasePoolPage节点组成的双向链表当前线程的runloop每次循环结束会统一释放一次所以在for循环里创建大量临时对象时手动加一个autoreleasepool可以让对象尽快进入释放池并得到释放。这里我提醒一句答题时别只背结论要能把“为什么”讲出来。面试官问内存不只是想看你知道weak和strong的区别而是想知道你在写UITableViewCell复用、图片加载、大文件解析时会不会主动去控制内存峰值。回答里带上“我在处理XXX时遇到过内存问题后来怎么解决的”分量完全不一样。2.2 RunLoop为什么主线程永远不会退出RunLoop是iOS面试里的经典“深水区”这套卷子也有一道相关的简答题。常见问法App启动后主线程在做什么为什么主线程不退出Touch事件是怎么被处理的从运行机制上讲主线程启动后会进入一个RunLoop循环这个循环的职责就是“接收事件 - 处理事件 - 进入休眠 - 再被唤醒”。RunLoop内部有多个Mode每个Mode下挂着若干个source0、source1、timer、observer。source0是应用内部手动触发的任务比如Touch事件、performSelectorsource1是系统内核事件比如基于mach port的消息timer就是NSTimerobserver则是用来监听RunLoop状态的比如进入休眠前、即将处理事件前。主线程的RunLoop跑在kCFRunLoopDefaultMode下当用户拖拽ScrollView时系统会切到UITrackingRunLoopMode这时候DefaultMode里的timer和网络回调就不会被执行这就是滚动列表时NSTimer不走的根本原因。这道题答到这一步只能算及格。想拿高分必须主动引出“RunLoop在实际开发中的用途”至少讲三个常见应用场景。第一个NSTimer在滑动时失效的解决方案把timer加到NSRunLoopCommonModes或者换用dispatch_source_t定时器。第二个Autorelease对象的释放时机每个RunLoop循环结束时会释放一次autorelease pool所以在循环体内大量创建对象时手动增加autoreleasepool是有效的优化手段。第三个卡顿监控通过监听主线程RunLoop的kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting两个状态之间的耗时就能判断主线程是否卡顿很多APM工具的核心原理就是这样。我建议复习RunLoop时一定要画一遍“事件循环流程图”不是画给别人看是逼自己把每个状态和执行顺序捋清楚。面试官追问到“RunLoop和GCD的关系”时能答出“GCD不依赖RunLoopdispatch到主队列的任务会交由主线程RunLoop的source0去执行”这类细节的人基本就是有真实项目经验的候选人了。2.3 多线程与锁别把GCD背成了八股这套卷子的多线程题出得比较活不是简单问你GCD和NSOperation的区别而是给一个业务场景让你选合适的并发方案。常见场景题是现在有A、B、C三个异步任务C要等A和B都完成后再执行你会怎么做最直接的方案是dispatch_group_t用group配合dispatch_group_enter和dispatch_group_leave包裹A和B两个异步任务在dispatch_group_notify里处理C。这套组合拳必须记住enter和leave必须成对出现否则notify永远不会回调。除了group也可以用dispatch_semaphore_t信号量控制并发A和B各自完成后signalC等待两个信号还可以用NSOperation的依赖关系[operationC addDependency:operationA]。三种方案里我一般建议优先说group逻辑最清晰。但这里有一个很容易暴露水平的坑如果面试官追问“在主队列上调用dispatch_sync会怎样”你能立刻答出“死锁因为主队列是串行队列当前线程在等待一个排在队列里的任务执行而这个任务又排在当前任务后面永远不会轮到它”才算真正理解队列。很多人只背了“同步异步、串行并发”四个词真遇到组合问题就乱了。我整理了一张队列行为速查表笔试前建议背熟操作串行队列并发队列同步执行sync不新建线程按顺序执行不新建线程按顺序执行异步执行async新建一条线程按顺序执行新建多条线程并发执行同步到当前串行队列死锁正常执行异步到主队列提交到主线程执行提交到主线程执行这个表背下来只是基础关键要理解“同步/异步决定的是要不要阻塞当前线程串行/并发决定的是任务在几条线程上执行”。这套卷子里关于锁的题也值得展开说。常见考法atomic修饰的属性线程安全吗答案是不完全安全atomic只保证getter/setter的原子性也就是读写时不会读到中间状态但不保证业务逻辑的原子性。比如一个字典两个线程同时往不同key写值atomic能防止crash但如果是“先读取、再修改、再写回”这种复合操作原子属性也救不了你。锁的选型也是高频追问点。从被废弃的OSSpinLock讲起它自旋等待时占用CPU遇到优先级反转会出问题随着iOS系统演进推荐使用os_unfair_lock、pthread_mutex、NSLock、synchronized。synchronized本质是递归锁内部还做了异常处理性能一般但最安全读写场景用pthread_rwlock或dispatch_barrier_async效果更好。这些知识点不难但能全部答出来的人不多因为平时写业务基本用不到锁候选人往往只在背题时见过。我的建议很直接自己在demo里把每种锁都跑一遍手工模拟一个并发写数据的场景体会一下谁快谁慢面试时讲出来的感觉完全不一样。2.4 KVO与Runtime动态性的利与弊KVO和Runtime是iOS区别于其他语言体系的特色考点这套笔试题里也占了不小篇幅。KVO的基础题是KVO的实现原理是什么完整回答需要说清几个层次。当一个对象被添加观察者后系统会动态创建一个该对象的子类比如NSKVONotifying_XXX然后将对象的isa指针指向这个子类。子类重写了被观察属性的setter方法在setter内部会先调用willChangeValueForKey:然后执行原来的setter再调用didChangeValueForKey:最后通知观察者observeValueForKeyPath:。这套机制叫isa-swizzling。很多人以为KVO是直接给原对象挂钩子其实原对象的类已经悄悄变了。这里有一个高频追问为什么对NSMutableArray用addObject:直接添加元素观察者收不到回调因为addObject:不是setter方法不会走进重写后的setter逻辑自然也不会触发willChangeValueForKey:。解决办法是用mutableArrayValueForKey:拿到代理数组后操作或者手动调用willChangeValueForKey和didChangeValueForKey。这个坑我在真实项目里踩过后面第5章会展开。Runtime的考点就更密集了常见的是消息转发机制。当给对象发送一个不存在的方法时系统会依次走resolveInstanceMethod:、forwardingTargetForSelector:、methodSignatureForSelector:和forwardInvocation:三个台阶分别对应动态添加方法、快速转发给其他对象、完整消息转发。答这个题的时候最忌讳只背名词要能区分“动态方法解析”和“消息转发”的本质差异前者是给当前类补方法后者是把消息发送给别的对象。Method Swizzling也是必考项。回答时一定要主动提到两个注意事项一是交换操作要保证只执行一次最好放在load方法里并用dispatch_once包裹二是不要交换系统的私有方法否则审核和稳定性都有风险。面试官如果欣赏你的严谨通常会顺着问“关联对象”的使用场景这个知识点用在给分类添加属性时非常方便——objc_setAssociatedObject在不修改类结构的前提下给对象绑定信息底层是通过一个全局的AssociationsManager统一管理的。2.5 UI与渲染从Auto Layout到离屏渲染UI部分的题只要是面向真实项目的笔试卷基本绕不开三块Auto Layout、UITableView优化、离屏渲染。Auto Layout的核心很多候选人以为是“约束怎么写”但笔试更爱考“Auto Layout是怎么工作的”。背后的算法叫Cassowary是苹果早期从ConstraintKit引入的一套约束求解方案本质是把UI的布局转化为一系列线性不等式方程解方程组得到最终的frame。所以你在代码里写的leading、trailing、width xxx这些约束最终都会被转换成数学表达式。理解了这一点就能解释很多现象约束冲突会打印无法满足的日志因为方程无解约束优先级能解决矛盾因为算法会先满足高优先级约束。UITableView这题也是必考考法通常是“怎么让一个列表滚动更流畅”。我列一个答题清单候选人能说全70%就算基础扎实Cell复用池机制dequeueReusableCellWithIdentifier:的效率远高于重复创建。高度缓存不要在heightForRowAtIndexPath里每次计算高度预估高度实际计算缓存更高效。避免离屏渲染不要轻易给Cell加圆角、阴影和shouldRasterize。图片在子线程解码主线程只做显示。减少透明视图数量不要把UIBlurEffect这类特效堆在滚动视图里。提到离屏渲染必须讲清楚什么是“离屏”。正常情况下图层内容直接绘制到屏幕帧缓冲区叫“在屏渲染”如果为了加圆角、阴影、遮罩等效果GPU需要先在另一块缓冲区里把图层画好再合并到屏幕上这个额外缓冲区就是“离屏”。比较常见的离屏渲染操作包括设置cornerRadius加上masksToBounds、设置shadowPath、开启shouldRasterize。笔试题如果给一段代码让你分析性能问题优先看这几个关键词。还有事件传递链也是UI模块的高频题。要讲清两个方法hitTest:withEvent:从父视图往子视图递归找最合适的响应视图pointInside:withEvent:判断触摸点是否在当前视图范围内。答完这两个方法后再把“响应者链”顺一遍从hitTest找到的最深视图开始沿着nextResponder向上传递直到UIApplication。能把这个链条讲顺说明你对用户交互的理解是完整的。2.6 网络与架构综合题决定你能不能进终面网络题在这套卷子里虽然占比不是最大但往往是区分度的关键。考法通常是HTTPS握手过程、TCP三次握手、Cookie与Session区别这些都属于“背了就能答”的基础题。深度题则是给你一个“云文档App”的场景问你会怎么设计它的网络层。先补基础。HTTPS不是一个新协议是HTTP加上TLS/SSL层。握手过程中最关键的两点一是用非对称加密完成身份验证和密钥协商二是后续用对称加密传输数据因为对称加密性能高得多。答这道题时要把RSA或ECDHE密钥交换、证书链校验、会话密钥生成这几个步骤串起来。TCP三次握手则注意别只背“SYN、SYNACK、ACK”三个词要能说明白为什么是三次而不是两次核心是为了防止失效的连接请求突然到达服务端而建立无效连接。架构题是拉开差距的地方。这套卷子如果出“如何设计一个iOS应用的基础架构”我建议的答题框架是三层加两条辅助线底层是基础组件层包含网络库、存储、日志、埋点、工具类这层不依赖任何业务。中间是业务组件层按照文档、表格、演示、PDF、云协作等业务模块拆分模块间通过路由或协议通信尽量避免直接import。上层是壳工程负责组装模块、处理启动流程、管理全局生命周期。两条辅助线一条是MVVM架构模式ViewModel负责把Model的数据转化为View可直接展示的数据结构通过RAC或Block/Delegate回传给View层另一条是CocoaPods私有库管理用私有Podspec把业务模块拆成独立仓库实现组件化。这套答案能完整讲出来基本就达到了“有架构意识”的标准。面试官在这里特别爱追问一个问题MVC有什么缺点为什么很多团队要转MVVM标准答法MVC里Controller承担了过多职责导致“Massive View Controller”问题MVVM把表现逻辑搬到ViewModelController瘦身测试性也更好。我通常会再补一句MVVM也带来了绑定和回调复杂的问题不是银弹小项目用MVC老老实实写反而更稳。这句补充会显得你真有判断力而不是背概念。3. 从笔试到实战把考点串进WPS的真实业务链路3.1 文档App的“打开-渲染-编辑-保存”链路里考了什么笔试的知识点是一颗颗珍珠只有放进真实业务里才能串成项链。我拿WPS这类文档办公App举例把一个文档从打开到同步的完整链路走一遍你会发现上面拆的所有考点全部能对上号。用户点击一个文档第一步是下载和解密。网络层要处理HTTPS请求、断点续传、DNS优化这就是网络题的实战场景。第二步是解析文档格式无论DOCX、XLSX还是PDF本质都是把文件流解析成内存对象模型。这一步对内存管理的要求极高一个大文档如果一次性全部加载内存直接爆掉所以要做懒加载和分页解析只加载当前屏幕能看到的部分。第三步是渲染排版文字排版、字体测量、图文混排都是计算密集任务必须把解析和排版放到子线程主线程只接收计算好的结果去做显示这就是多线程和UI渲染的结合场景。第四步是编辑用户输入文字、插入图片、调整格式每步操作都有可能触发重排版。如果整个文档重新渲染性能必然崩所以要做局部刷新和脏矩形渲染优化。第五步是保存和云同步本地写文件加多线程上传同时要处理多人协同时的冲突合并数据一致性问题就来了。这条链路里有三个点是候选人最容易在面试中被追问出深浅的。第一个是“主线程卡顿怎么定位”这就回到RunLoop和Instruments的使用第二个是“内存峰值怎么降”要答出懒加载、缓存复用、自动释放池第三个是“多人协同怎么保证数据一致”这就涉及锁、串行队列、或者更复杂的CRDT算法。能把这三个问题讲出具体方案基本上笔试和面试都稳了。3.2 区分合格与优秀候选人常被追问的加分细节笔试只能筛掉明显不合格的人真正区分合格与优秀的环节在面试追问。我复盘过很多候选人发现那些最终拿到Offer的人不一定把所有考点都答得完美但在三个“软性细节”上会给面试官留下印象。第一个细节是动手配置能力。很多人在简历上写“熟练使用Xcode”但一问到开发者证书和描述文件就支支吾吾。iOS开发绕不开证书开发证书、发布证书、推送证书分发的App类型不同证书配置方式也不同。我在面试时经常会问“App上架前要做哪些准备”能答出生产证书配置、TestFlight测试、审核材料的人基本是真正走过上架流程的。这个细节刷掉的人比想象中多——不少候选人只写过demo从没完整上架过一说证书就露馅。第二个细节是调试工具的熟练度。拿抓包来说很多候选人知道Charles能抓HTTPS包但具体到“要装根证书、信任证书、配置SSL Proxying”才能看到明文请求就不知道了。类似的还有Instruments的Leaks、Core Animation、Time Profiler三个工具分别干什么用。笔试考不出这些但面试官一追问就能感觉到差距。第三个细节是兼容性意识。WPS要支持iOS 11.0及以上系统还要适配iPad分屏、暗黑模式、不同屏幕尺寸。候选人如果在讲项目时能主动提到“我处理过低版本系统的crash”“我适配过暗黑模式”说明他有生产环境思维。这比任何考点都更能证明你“能干活”。4. 备考路线与常见误区校招过来人的避坑清单4.1 三个月备战节奏知识框架怎么搭如果你现在离校招笔试还有三个月我建议按这个节奏走亲测有效。第一个月主攻语言基础和内存。把Objective-C的面向对象特性、Protocol、Category、Block、KVC/KVO彻底吃透配套读Apple官方文档的Memory Management章节然后再看一遍《Objective-C高级编程》里关于ARC的章节。这一个月不要急着刷题先把地基夯实。第二个月主攻UIKit和系统框架。把UIView生命周期、ViewController生命周期、Auto Layout、UITableView、UICollectionView、多线程、网络请求过一遍。重点不是“用过”而是“搞懂原理”每个知识点都要追问自己一句“为什么是这样”。比如知道了viewDidLoad和viewWillAppear的区别还不够要能回答“为什么在viewWillAppear里调整UI布局不够安全”。第三个月进入冲刺模式。白天刷题晚上读源码。题源方面LeetCode刷Hot 100约60道中等难度题就够用重点练字符串、数组、链表和二叉树iOS方向把牛客网上的历年校招真题刷一遍。源码方面至少精读AFNetworking和SDWebImage的主流程搞懂网络请求从发起到底层回调的完整链路、图片缓存的层级结构。晚上回看自己白天做错的题把错因分成“概念不清”和“边界遗漏”两类分别补齐。4.2 面试官视角下的常见误区我围观过很多校招面试发现候选人翻车的地方惊人地一致这里按频率排序讲。第一位只会背概念不会写代码。笔试简答题写得头头是道一上机写链表反转就卡壳。这种情况面试官往往不会给机会因为iOS开发终究是工程能力不是记忆能力。我的建议是每个知识点都配套写一个demo验证比如学KVO就写一个观察可变数组的demo学锁就写一个并发递增的demo眼见为实。第二位简历项目与岗位严重不匹配。很多候选人项目写的是“校园二手交易App”技术栈是安卓、小程序HR筛简历时可能因为学校背景给了一次笔试机会但面试官看了项目描述基本就不抱期待了。既然投iOS岗项目至少要是iOS端的哪怕是仿写的一个小程序也要把用到的内存优化、架构设计、多线程处理写清楚。第三位不懂装懂硬编答案。这是最减分的行为。面试官问到不会的知识点直接说“这块我还没深入研究过但我的理解大概是……”反而能留下好印象至少显得真诚和学习能力强。硬编一个错误答案被发现后整场面试的可信度都会崩塌。第四位只关注技术忽略产品思维。笔试题答完就结束了但面试环节通常会有一个“如果让你给WPS设计一个新功能”的开放题。这时候能站在用户角度分析需求、讲清楚功能价值、再说技术方案的候选人往往比只懂技术细节的人更有优势。5. 我在实际开发中因为基础不牢踩过的两个坑5.1 一个Cell圆角阴影引发的启动卡顿那是一个信息流页面UI稿里卡片带圆角和阴影设计很好看实现起来也很快每个卡片直接设置了cornerRadius、masksToBounds、shadowOffset、shadowOpacity。真机一测列表滑动时掉帧非常严重。我用Instruments的Core Animation工具一测发现整个页面的离屏渲染区域被标成了黄色范围大到几乎覆盖了整个屏幕。原因是每张卡片同时触发了圆角裁剪和阴影绘制GPU需要在离屏缓冲区反复合成图层性能自然崩。后来把阴影改成了预绘制阴影图圆角改为在图片解码阶段直接裁剪主线程渲染压力骤降列表瞬间丝滑。这就是第二章UI渲染考点的真实版本。如果当时笔试只背了“圆角会造成离屏渲染”到项目里根本不知道怎么定位反过来如果你在项目里亲手排查过一次这个性能问题笔试考到离屏渲染时你回答的自信程度完全不一样。5.2 多线程竞争导致的数据错乱事故另一次事故是在做文档缓存更新时。多个下载任务完成后各自往一个全局的NSMutableDictionary里写入缓存状态我当时想当然地认为用atomic修饰属性就安全了。结果上线后偶发crash崩溃日志指向objc_sync_enter和字典遍历。排查了很久才意识到多个线程同时修改同一个可变字典atomic只保证赋值操作的原子性但不保证字典内部结构的线程安全。两个线程同时扩容或同时修改内部哈希表底层就会出问题。最终解决方案很简单把所有缓存写操作收口到一个串行队列或者加一把pthread_rwlock读多写多分开锁。这次之后我彻底明白了atomic的边界再遇到类似的并发场景都会先问一句“操作的到底是什么对象、每一步是否原子”。5.3 从一道题到一套方法论的沉淀回头再看这套金山办公2020校招iOS开发工程师笔试题一我的感受是校招笔试从来不是终点而是一面镜子。它能照出你的知识体系里哪些地方是真空的哪些地方只浮在表面。我见过太多候选人刷题刷到条件反射但一追问就露馅归根到底是把“背答案”当成了“学知识”。我自己后来养成了一个习惯每遇到一个知识点都问自己三个问题——它解决什么问题它的底层原理是什么如果让我在生产环境里用它我会怎么设计这三个问题串下来一道笔试题就能延伸出一套实战方法论。真心建议正在备考的同学把这份笔试题里的每个考点都按这个方式过一遍再老老实实写几个demo验证。校招拼的从来不是天赋而是谁在基础功上花的时间更扎实。