
货拉拉2018秋招iOS工程师笔试题卷二A复盘一套能真正筛出“工程感”的题最近不少读者在后台问我要一套适合系统性自测的iOS面试题我第一反应就是货拉拉2018秋招的这套iOS工程师笔试题卷二A。这卷子在当年iOS圈子里流传度就很高现在翻出来看依然有价值。它不考偏题怪题而是把iOS日常开发中天天打交道的基础知识点拆成了一道道看似简单、实际暗藏陷阱的题目覆盖内存管理、多线程、网络、数据持久化、UI性能这些核心方向。不管你是准备社招还是校招或者只是想检验一下自己的基础是否扎实这套题都值得认真做一遍。但更值得做的是透过题目看它背后的考察逻辑。这篇文章我会逐块拆解这套题的考察重点结合我实际开发中踩过的坑把每类考点背后“为什么这么考”讲清楚。1. 试卷整体认知与考察方向拆解1.1 这套笔试题考了什么这套题的整体风格是“基础为主、工程落地为辅”。拿2018年那个时间点来说iOS 11刚普及不久Swift和OC混编已经常见但大部分业务团队的核心代码仍然是OC。所以卷二A的考点分布非常有代表性主要集中在这几个方向OC语言基本功属性修饰符、深浅拷贝、对象生命周期、block内存管理ARC的底层逻辑、循环引用、内存泄漏多线程与并发GCD的队列与任务、死锁、线程安全网络基础HTTP/HTTPS、TCP/UDP、接口安全设计数据持久化归档、数据库选型对比UI与性能Auto Layout、列表滑动流畅度、离屏渲染从题型设计来看它没有故意刁难人的脑筋急转弯几乎每一道题都能在真实的业务开发中找到对应场景。这也是为什么这套题虽然过去这么多年了依然适合拿来复盘。它验证的不是你背了多少面试题而是你在日常开发中是否真的思考过底层原理。1.2 为什么这类题目值得反复复盘很多开发者有一种误区题目有“时效性”2018年的题拿到2025年还有啥用但你要知道iOS面试中真正高频考察的基础知识迭代速度并没有你想象的那么快。无论当年的iOS 11还是现在的iOS 17、18对象的引用计数管理逻辑没有变化GCD的同步异步和死锁原理没有变化block捕获外部变量的机制也没有变化。这套题另一个值得复盘的点在于它比较早地开始考察“工程实践与原理的结合”。举个例子它考属性修饰符不是直接问strong和copy的区别而是给出一段存在潜在崩溃风险的代码让你分析问题并给出修改方案。这类题目考的是你在实际问题排查中的敏感度而不是简单的概念背诵。我后面带团队面试的时候也经常参考这种考法因为写代码的习惯和对内存问题的敏感度很难靠背题装出来。2. 重点题目解析与知识点延伸2.1 内存管理从ARC到block循环引用这套题里内存管理部分的核心考点就是ARC和循环引用。ARC不是垃圾回收机制它本质上是编译期自动在合适的位置插入retain、release、autorelease调用。很多人在这个问题上理解有偏差以为有了ARC就可以完全不关心对象生命周期这是大忌。循环引用是ARC时代最典型的面试考点。我先说一个非常经典的题干变体#import ViewController.h interface ViewController () property (nonatomic, copy) void (^myBlock)(void); end implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.myBlock ^{ NSLog(%, self.view); }; } end这段代码有没有问题有。self被block捕获而block又被self的属性强持有形成环。在MRC时代大家对引用计数的感知很强到了ARC时代反而容易忽视这种隐式持有。正确的常规解法是使用weak修饰__weak typeof(self) weakSelf self; self.myBlock ^{ __strong typeof(self) strongSelf weakSelf; if (strongSelf) { NSLog(%, strongSelf.view); } };这里面有一个经常被追问的细节为什么在block内部还要转成strongSelf因为weakSelf在block执行过程中随时可能被置为nil如果block执行期间需要连续访问self的多个属性或调用多个方法直接用weakSelf可能导致执行到一半对象被释放出现不可预期的问题。加一层strongSelf保证block执行期间对象生命周期稳定。2.2 属性修饰符的语义陷阱属性修饰符是这套卷子的高频考点也是非常容易丢分的地方。它考察的不仅是记忆更是理解。首先是最经典的strong vs copy。很多开发者知道“NSString要用copy”但说不清为什么。我说一个真实踩坑经历有一段时间团队里一个同事用strong修饰了一个NSString属性后面从NSMutableString赋值进去。结果这个可变字符串在其他地方被修改了我们的属性值也跟着变了排查了很久才发现是这个原因。property (nonatomic, strong) NSString *name; NSMutableString *mStr [NSMutableString stringWithString:hello]; self.name mStr; [mStr appendString: world]; // self.name 变成了 hello world这并不是期望的结果所以NSString、NSArray、NSDictionary这些有可变子类的类型我们一般会使用copy这样赋值时强制做一次不可变拷贝避免外部修改影响内部状态。再来看container的copy语义。这里有个容易混淆的点对不可变对象执行copy通常是指针拷贝浅拷贝因为对象本身不可变没必要复制内容而对可变对象执行copy是内容拷贝深拷贝得到一个新的不可变对象。区分这些在回答“copy是浅拷贝还是深拷贝”这类问题时会更有把握。另外要补充一下weak的适用场景。weak只能修饰OC对象不能修饰基本数据类型和C语言结构体。它有两个特点不增加引用计数以及当被指向对象释放时自动置为nil。正是因为自动置nilweak在delegate、block捕获、以及避免循环引用这些场景中非常常用。2.3 runtime与消息机制作为iOS面试的“深水区”runtime在这套题里的出现频率也不低。它考察的通常是消息传递机制和消息转发流程。OC的方法调用本质是发送消息objc_msgSend(receiver, selector, ...)。当对象的isa指针找到类对象类对象的method_cache里没命中会继续沿继承链查找最终没找到就会进入消息转发流程。这个流程有三个机会让你“补救”1. resolveInstanceMethod: 动态添加方法 2. forwardingTargetForSelector: 将消息转发给其他对象 3. methodSignatureForSelector: forwardInvocation: 完整的消息转发其中第1个和第2个是高频考点。动态添加方法的典型场景是懒加载某些方法实现或者在某些ORM框架里做动态属性映射。消息转发给其他对象的典型场景是代理模式的底层实现、以及多继承的替代方案。关于method swizzling这套题如果延伸到面试问答环节大概率会被问到。我的建议是回答时主动说明三点一是不要在load中使用因为加载顺序不可控容易引发未知问题二是交换方法时要注意调换IMP后调用的顺序三是在继承关系中滥用swizzling会带来巨大维护成本。能讲清楚这三点说明你真的在项目里用过而不是只背了个概念。3. 从笔试题目到真实项目开发3.1 多线程选型与GCD死锁多线程部分的题目设计也很典型。核心考察点是GCD中“队列”和“任务”的组合方式以及死锁的形成条件。我最常被问到的一道题是在主队列上执行同步任务会怎样答案会死锁。原因在于主队列是串行队列同步任务会阻塞当前线程等待任务执行完成而当前线程正是主线程主队列里的任务又必须等主线程空闲才能执行这就形成了互相等待。dispatch_async(dispatch_get_main_queue(), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(这里永远不会执行); }); });我见过不少开发者在实际项目中写出类似的代码典型场景是在主线程回调的某个方法里又同步往主队列丢任务。排查时会发现主线程被卡住但Xcode的调用栈又看不出明显问题最后才发现是死锁。另一个容易搞混的知识点是并发队列同步任务。不少人以为并发队列就一定会开多线程其实同步任务依然不会开启新线程任务还是一个接一个执行。只有在并发队列中使用异步任务才可能创建新的线程。理解了这一层很多关于线程数量和执行顺序的问题都能推导出来。3.2 网络层考察从HTTP到网络安全网络部分在2018年的笔试中更加侧重于基础主要围绕HTTP与HTTPS、TCP与UDP的区别。但在面试追问环节通常会深入到实际开发中。我给出的建议是先回答“HTTPS在HTTP基础上加了一层TLS/SSL加密”然后说明握手的大体过程客户端发起请求服务端返回证书客户端验证证书合法性然后协商会话密钥之后通过会话密钥加密通信。多提一句“对称加密非对称加密混合使用”的原因因为对称加密效率高但密钥分发困难非对称加密解决了密钥分发问题但性能较差两者结合是工程上的最优解。关于TCP和UDP要抓住核心差异TCP是面向连接的可靠传输有三次握手、四次挥手、拥塞控制、重传机制UDP是无连接的不可靠传输没有这些机制但头部开销小、延迟低适合直播、游戏、音视频通话等场景。这里要提醒一点讲网络基础的时候尽量结合自己做过的东西。比如你做视频直播就讲为什么选UDP而不是TCP电商App的结算接口为什么必须走HTTPS。这比干巴巴背协议定义要有说服力得多。3.3 性能优化是隐藏考点卷二A里有一类题目没有直接写“性能优化”这四个字但问的是列表卡顿、图片加载、离屏渲染这类现象本质上就是在考察性能优化能力。列表滑动卡顿的典型原因有以下几种cellForRowAtIndexPath中做了大量计算或数据解析图片在主线程解码和缩放约束冲突导致Auto Layout计算量过大cell复用标识写错导致频繁创建新cell图片加载是另一个高频深入话题常见问题是加载大图时直接使用imageWithContentsOfFile: 在主线程解码导致UI卡顿。优化方向一般是在子线程解码并缩放图片、使用适当的缓存策略、避免在主线程进行位图上下文绘制。离屏渲染的问题我多说两句因为很多人只停留在“知道这个名词”的层面。所谓离屏渲染就是本该直接绘制到屏幕上的内容因为图层的某些属性比如圆角加maskToBounds、阴影、不透明层重叠等被迫先在屏幕外的缓冲区里合成再提交到屏幕。这个过程的代价远高于普通渲染。所以开发时使用圆角要注意设置layer.cornerRadius不一定触发离屏渲染只有同时设置masksToBounds或与阴影叠加时才可能触发。而列表cell中的大量圆角图片和阴影是卡顿的重灾区。4. 答题误区与高频失分点4.1 题干陷阱越简单的题越容易丢分这套笔试题有一套很有意思的出题风格就是在看似简单的代码片段里埋坑。比如数组遍历删除元素这类题很多人第一眼觉得太简单结果就掉坑了。NSMutableArray *array [NSMutableArray arrayWithArray:[1, 2, 3, 4]]; for (NSInteger i 0; i array.count; i) { [array removeObjectAtIndex:i]; } // 你以为会全部删除实际崩溃或者漏删原因在于每次removeObjectAtIndex后数组的count已经改变后面的元素下标往前移了直接用递增的i遍历会越界或漏掉元素。正确做法是倒序遍历或者使用copy出来的数组遍历原数组再操作原数组。这道题想考察的不是你会不会遍历而是你有没有在实际开发中遇到过类似问题。这种问题在真实项目里常有比如清理消息列表里的多条选中数据时如果不注意顺序你就会发现界面上的数据删不干净。4.2 代码题的标准答题姿势很多考生笔试时思路是对的但代码写出来一看就“没工程感”这也是失分点。我举个例子。题目让你实现一个带缓存的内存管理器有人写了一大坨逻辑全部堆在实现文件里没有分层没有注释有人能想到用单例、用NSMutableDictionary做缓存、设置最大容量、在超过阈值时清理并考虑线程安全加锁。高下立判。笔试答题时要注意几个细节先写清楚思路再写代码。很多阅卷人会先看你有没有分析过程属性用点语法或下划线加self访问保持一致性是否需要考虑线程安全要主动判断并说明边界条件要处理比如参数为nil的情况还有一个容易被忽略的加分项在代码中主动写注释说明设计意图。不少阅卷人会根据这个来判断你是不是真的理解代码而不是背模板。5. 面试追问的应对方法和表达框架5.1 面试官追问的逻辑笔试通过之后面试官经常会拿着笔试题里的某道题继续追问。这一步其实比笔试更重要因为你写的答案往往是“背过的答案”而追问才是真正检验你理解深度的时刻。举一个典型例子。你答了用weakSelf解决block循环引用。面试官会立刻追问“如果block执行前self已经被释放了会怎样”如果你没想过这个问题可能会说“那就不执行了呗”。但更深层的答案是weakSelf会变成nil如果你只是用weakSelf调用方法那消息会发给nil不会有问题但如果weakSelf是nil你在block里做了强解包或者强制调用了某个必须非空的方法就会出问题。所以追问的本质是看你对一个知识点是否能从“是什么”延伸到“边界条件是什么”。我的经验是回答时尽量不要只给结论要把思考链展现出来。比如这样回答“我用weakSelf是为了避免循环引用但如果self在block执行前已经被释放weakSelf会是nil。为了安全我在block内部用strongSelf包一层先判空再使用。这样既避免循环引用也避免执行到一半self被释放。”5.2 结构化表达结论先行场景跟上我建议所有备考的同学都练习一种“结论先行”的表达框架无论是笔试写答案还是面试口头回答都适用。它就三步给出明确答案解释背后的原因或原理结合场景说明这个知识点在项目里怎么用举个例子如果被问到“为什么用copy修饰NSArray”你可以这样回答“用copy是为了切断赋值时的可变引用关系。因为NSArray有NSMutableArray这个可变子类如果用strong外部可变数组后续修改内容内部属性也会跟着变容易引发数据错乱。而copy在赋值时执行一次不可变拷贝保证内部状态可控。我在实际项目里处理数据模型时所有对外暴露的集合属性都统一用copy已经形成了一个团队规范。”这个回答既给出了结论也说清了原理还展示了工程经验。这种表达方式不需要你口才多好只要内容扎实、结构清晰面试官就能快速判断你的水平。6. 备考建议与个人经验6.1 如何高效利用这套题自测如果你打算用这套题自测我建议不要直接看题背答案而是按以下几个步骤走第一步先不看答案用纸笔或编辑器把答案完整写一遍。这一步是暴露问题的最好时机。写不出来、写不全、写错了都是收获。第二步对照答案逐题分析错因。分类整理是概念没记住还是原理不理解还是应用场景没遇到过。第三步针对薄弱点回到官方文档和源码把原理彻底搞明白。第四步趁热打铁把相关知识点讲给身边的人听或者写一篇笔记能讲清楚才算真的掌握了。这套题的难度分布其实比较平滑前面的题偏基础后面的题逐渐叠加场景复杂度。做的时候不需要追求每道题满分但要追求每一道错题背后的知识点都能关联到至少一个真实开发场景这样刷题才不是无用功。6.2 一些发自内心的建议回想起2018年我第一次拿到这套卷子时自己也顺手做了一遍。当时我已经有几年iOS开发经验但居然在一些细节上栽了跟头比如copy和strong的坑、GCD死锁的经典代码。后来我带团队面试新人也喜欢用这套题作为参考因为它确实能筛出一个开发者基础是否扎实、有没有主动思考过底层原理。如果你正准备秋招或者打算跳槽我不建议你把精力全花在刷“面经”上。面经只是结果不是原因。真正有效的准备方式是把iOS的基础知识体系梳理一遍然后配合类似这套笔试题的实战练习把每个知识点彻底搞明白。这套题对我来说最大的价值就是让我重新审视了自己对iOS基础的掌握程度也让我在后面多年的开发中养成了“知其然更知其所以然”的习惯。希望这份复盘也能帮你看到自己知识体系里的盲区有针对性地补强。