货拉拉iOS笔试题解析:从内存管理到架构设计的复习主线 📅 发布时间:2026/9/1 20:41:56 👁 浏览次数: 从2018年秋招季收到货拉拉iOS工程师笔试题卷A到现在已经过去好几年了但那份卷子我一直留在网盘里偶尔翻一翻。不是题目有多难而是它出题的角度很有代表性——不追新技术热点不考偏门API而是把Objective-C内存管理、多线程、网络、架构设计这些日常开发天天要面对的东西全部塞进货拉拉自己的业务场景里。这套题适合所有准备iOS岗位笔试和面试的朋友参考它帮你划出了一条清晰的复习主线基础要牢原理要懂更要把知识落到真实业务里去。下面我结合这套题把背后的考点和复习方法逐一拆开讲。1. 笔试整体设计思路与考点分布1.1 货拉拉这套题到底在考察什么能力货拉拉是O2O同城货运平台移动端承担了用户下单、司机接单、全程定位、轨迹回传、在线支付、订单状态流转这些核心流程。所以笔试题目不是单纯考察语法记忆而是围绕“一个iOS工程师进入业务团队后能不能马上顶上去”来设计的。我当时拿到卷子第一反应就是选择题和填空题覆盖语言基础简答题考察内存管理和多线程后面还有一道场景设计题整体非常贴近实战。从题型上看A卷大概是这样的分布基础选择题考察属性关键字、KVC/KVO、Category、Block这些高频概念填空题重点考内存管理和GCD队列简答题解释循环引用、nil与Nil的区别、HTTP和HTTPS的区别最后是一道订单状态机或计费规则的设计题。这套题更看重的是你对核心机制的理解深度而不是刷过多少题。1.2 从考点分布反推复习优先级如果你准备iOS笔试不需要一开始就雨露均沾。我把A卷中出现的知识模块和优先级整理成了一个表参考价值很大考察模块出现频率复习优先级常见题型Objective-C语言基础极高最高选择、填空内存管理与循环引用高最高选择、简答多线程与并发安全高最高选择、简答、代码分析网络基础与HTTPS中高简答iOS架构与设计模式中高设计题业务场景与数据流低但分值高高设计题、开放题我当时就是因为太执着于刷算法题忽略了这些基础概念背后的原理结果在简答题上吃了亏。后来总结发现笔试题目里的选择题往往不是单纯问“以下哪个属性修饰符是正确的”而是给你一段有坑的代码让你判断会不会崩溃、会不会循环引用。这种考法要求你真懂原理而不是背结论。1.3 为什么这套题敢抛开新框架不考2018年那会儿Swift 4已经发布iOS 12也出来了但A卷里几乎没有涉及Swift的新特性也没有考AR、Core ML这些时髦框架。为什么因为货拉拉这种体量的App核心代码还是以Objective-C为主而且业务稳定性远比技术新潮重要。笔试命题人很清楚他们需要的是能维护既有代码、能解决线上问题的人而不是只会写Swift Playground的新人。这给后来人的启示很直接准备iOS笔试时先把Objective-C这块地基打牢再去追新的技术。语言特性每年都在变但内存管理、多线程、网络请求这些底层逻辑换一层壳还是那些东西。你把这些吃透了面试官反而觉得你比那些只会背新API的人更靠谱。2. 高频基础知识考点解析2.1 Objective-C语言特性属性修饰符、Category与BlockA卷里至少有四道题在绕语言基础。我记得有一道选择题问下面这段代码中self.name用的是strong还是copy更合适。表面上是考修饰符选择实际是考深拷贝、浅拷贝和可变对象之间的关系。我们在项目里最常见的写法是property (nonatomic, copy) NSString *name; property (nonatomic, strong) NSArray *tags;为什么要用copy来修饰NSString因为外部可能传一个NSMutableString进来如果用strong持有外部后续再修改这个可变字符串self.name就会跟着变造成难以排查的隐蔽Bug。同样NSArray、NSDictionary这些容器类如果不想让外界通过可变对象间接修改内部数据也建议用copy。这里有一个容易被忽略的细节copy对不可变对象通常是浅拷贝对可变对象才是深拷贝所以用copy修饰一个NSMutableString属性再试图往里追加字符串时会崩溃因为属性指向的实际是不可变对象。Category也是必考点。它会问Category能不能添加属性答案是可以加上setter/getter的声明但不会自动生成成员变量需要借助关联对象Associated Object来实现存储。我在项目里用Category给UIColor添加过十六进制颜色转换的方法这个很实用也是面试时容易拿分的点。如果你能说清“Category是在运行时把方法合并进类对象的方法列表中”然后主动提到Method Swizzling的使用场景和风险这道题基本就能拿满分。Block在A卷里出现的频率很高。除了问你Block底层是什么结构还会考__block关键字的作用。实现原理是默认情况下Block捕获的外部变量是值拷贝你在Block内部修改的是副本外面感知不到加__block后编译器会把变量包装成一个结构体Block内外的操作指向同一个结构体实例所以能改到原变量。这个考点如果你能从“栈上Block还是堆上Block”的角度去答会显得很有深度。2.2 内存管理ARC、循环引用与weak/strong的实战取舍货拉拉的App在司机端有很多地图和轨迹相关的页面地图控件、定时器、Block回调凑在一起是循环引用的高发区。A卷里直接给了一段代码让我判断一个UIViewController持有一个NSTimerTimer又持有了self会不会造成内存泄漏。答案是什么在iOS 10以前NSTimer会强持有target如果你把timer作为属性长期持有确实会造成VC无法释放。这件事最常规的解法是__weak typeof(self) weakSelf self; self.timer [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *timer) { __strong typeof(weakSelf) strongSelf weakSelf; [strongSelf doSomething]; }];注意我在Block里又加了一个__strong这是项目里常用的防御性写法先用weakSelf避免循环引用在进入Block后马上转成strongSelf防止执行过程中self被提前释放同时保证整个作用域内对象存活。这种细节在笔试题里不会直接给你但会在下一轮的面试中追问你要能说得出来。关于weak和strong还有一个常考的点delegate属性为什么用weak如果改成strong会怎样答案很简单delegate是对象之间的回调关系通常持有方不希望反向持有对方否则两个对象互相强持有就会形成闭环。我见过不少初级开发在这个问题上栽跟头你要理解“引用关系决定生命周期”这个根本原则而不是死记什么场景用weak。Autoreleasepool在A卷里也有出现考的是在主线程RunLoop中自动释放池什么时候创建、什么时候释放。这涉及到RunLoop的下一个循环所以我会把Autoreleasepool和RunLoop放在一起复习App启动后主线程RunLoop一直运行每次事件处理前会创建自动释放池事件结束后释放池池中的对象被释放。如果你在循环里创建大量临时对象建议显式加Autoreleasepool降低峰值内存比如处理图片像素数据时。2.3 多线程与并发安全GCD和NSOperation的底层逻辑多线程是iOS笔试的重点A卷就有一道代码分析题问这段代码是否会造成死锁dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(hello); });这题太经典了。在主线程上同步派发一个任务到主队列但主线程正在执行dispatch_sync这条代码任务没有机会执行于是互相等待死锁。我当时差点被绕进去因为觉得“我就是想往主线程加一个任务而已”。正确理解是dispatch_sync提交任务后必须等任务执行完才返回可当前线程就是主线程正在等这个任务而任务又被安排在主队列里需要主线程去执行形成循环等待。GCD的队列类型、任务派发方式、线程和队列的关系这几个概念必须彻底搞清。串行队列、并发队列、主队列、全局并发队列哪些情况下会创建新线程哪些情况下不会笔试不会直接问但代码分析题一定会用到。另一道相关题目是怎样实现多个网络请求全部结束后再统一刷新UI这个用dispatch_group就能解决dispatch_group_t group dispatch_group_create(); dispatch_group_enter(group); [self requestA:^{ dispatch_group_leave(group); }]; dispatch_group_enter(group); [self requestB:^{ dispatch_group_leave(group); }]; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 全部完成后回主线程刷新 });这里关键点是enter和leave必须成对出现一旦少了一次leavegroup的计数永远回不到0notify就永远不会执行。我见过线上版本因为某个请求失败回调里忘了调leave导致后续逻辑一直不触发排查了很久才发现。这种问题在笔试里不会直接让你写代码但会给你一个残缺代码让你找Bug。NSOperationQueue则更适合管理依赖关系。A卷没有直接出NSOperation的大题但简答题里问过“为什么需要NSOperationQueue而不仅仅是GCD”这其实是在考察你有没有抽象封装意识。NSOperation可以设置依赖、可以取消、可以控制并发数这些特性在做复杂的业务调用链时非常有用。比如司机端需要先上传定位点再刷新订单状态再拉取消息列表用NSOperation依赖链就能清晰表达。线程安全也是绕不开的。synchronized、NSLock、atomic、队列同步这些方案的性能和适用场景各不相同。A卷里有一道选择题问atomic是否绝对安全答案是atomic只保证属性的setter/getter本身是原子的不保证多线程读改写整个对象安全。它保护不了比如“先判断为空再赋值”这种复合操作。我在项目里遇到数据竞争问题更倾向于用串行队列配合读写锁来保护可变数据核心思路还是从并发模型角度去设计而不是单纯依赖锁。2.4 网络层HTTP/HTTPS、TCP/UDP与ATS配置网络相关的题目在A卷里不算多但有一条简答题我记得清楚“说下HTTP和HTTPS的区别以及HTTPS的握手流程”。这题可以答得很大只要核心链路清晰就行。从TCP三次握手到TLS握手CA证书验证对称加密密钥协商再到后续HTTP数据加密传输。我建议你把这个过程画一遍能不看资料讲出来才算真正掌握。还有一道题问ATSApp Transport Security是什么遇到HTTP请求怎么处理。2017年苹果就强制要求App使用HTTPS但开发测试环境难免会有HTTP地址。正确做法是在Info.plist里加上NSAppTransportSecurity配置NSExceptionDomains针对特定域名允许HTTP。注意这里不是让你全局关闭ATS那是审核会被拒的会有警告所以配置越精确越好。网络层还有一个高频考点是GET和POST的区别。其实从TCP协议层面看HTTP的GET和POST没有本质区别都能传输数据不同主要体现在浏览器缓存、语义、请求体形式上。A卷给了一个场景上传用户位置信息应该用GET还是POST如果你只是上传一个坐标GET带query也可以但考虑到坐标属于业务数据且不希望被缓存在日志里更建议用POST。答这类题要结合业务场景别只背“URL长度有限制”这种话。3. 进阶核心环节架构设计业务场景实战3.1 iOS架构模式MVC的局限与MVVM的引入A卷最后一道大题是开放式的它给了一个不完整的订单列表页面要求你描述数据流向和类职责划分。这其实就是在考架构思想。很多iOS项目一开始都是MVCController里堆了网络请求、数据解析、UI刷新、事件处理代码量膨胀到几千行后维护成本剧增。笔试答题时如果你只写“用MVC”面试官会觉得你没有深入思考。我当时在项目里沉淀下来的一套做法是Controller只负责页面生命周期和UI事件响应业务数据获取和加工放到ViewModel模型层负责数据结构和持久化。ViewModel暴露给Controller的是已经处理好的“视图状态”Controller拿到后直接赋值刷新。这个模式笔试时很好写重点是你要能说清为什么让Controller变薄让业务逻辑可以单测让多个页面复用同一份数据处理逻辑。MVVM的数据绑定方式也有讲究。用KVO、Block、Delegate、RAC各有优劣。在Swift项目里我用Combine在OC项目里常用Block回调或Delegate。A卷问的是OC场景所以我回答时会强调没有绝对的绑定框架重点是保持单向数据流View - ViewModel - Model避免双向耦合。这个回答比单纯说“我用RAC”更能体现设计能力。3.2 组件化与模块化思想应对多业务线协作货拉拉的业务线很广用户端、司机端、搬家、企业版、同城送移动端往往是一个大App里塞了多个业务模块。A卷虽然没有直接考组件化但设计题里隐含了模块划分的思想订单模块、地图模块、支付模块、消息模块如果全部揉在一起后面任何改动都会拖垮整个工程。笔试答题时可以提一下基于CocoaPods的私有Pod拆分方式。我常用的做法是基础组件层放网络、存储、工具类中间层放业务组件比如订单、地图、用户最上层是壳工程负责组合和路由。组件之间通过路由或协议通讯避免直接依赖。路由方案我推荐简单一点的比如通过URL注册和调起或者使用中间件模式而不是引入太重的框架。答题时要体现出你理解组件化不是技术炫技而是为了解决团队协作和动态化问题。3.3 性能优化启动、卡顿、内存与耗电性能优化大题在A卷里出现在简答题它问了一个很接地气的问题App启动时间过长你会怎么排查。我当时写的思路是先区分是冷启动还是热启动再用Instruments的Time Profiler看主线程耗时函数重点关注启动时是否同步做了网络请求、数据库读取、大文件解压。从业务上看很多启动耗时来自第三方SDK初始化能延迟加载的尽量延时把首屏需要的逻辑提到前面。卡顿优化的核心是RunLoop。如果你的App在滑动时有掉帧要检查主线程是否被同步IO、大图解码、频繁的AutoLayout计算占住。A卷没有深究但面试官在追问时会让你说离屏渲染比如cornerRadius加masksToBounds为什么会导致性能下降。这个知识一定要掌握它会在离屏渲染时创建额外的渲染缓冲增加GPU负担优化方式是使用CAShapeLayer的UIBezierPath裁剪或者直接让UI提供切好圆角的图片。内存泄漏的排查也是必答项。笔试后第一轮面试面试官问我在项目里怎么发现循环引用。我会说先静态检查用Xcode的Instruments LEaks工具动态分析再配合MLeaksFinder这样的开源库在开发阶段就能弹出警告。内存泄漏最大的特点是隐性的页面退出后会话还在但你没感觉直到OOM崩溃。所以我会在UITabBarController里定期检查子控制器的dealloc是否被调用。这些经验写进笔试答案里加分效果很明显。3.4 场景设计题订单状态机与计费规则刚才提到A卷有一道设计题我印象最深的是订单状态机。题目不是让你背状态枚举而是给你一个订单实体的状态字段让你设计“如何保证状态流转合法且安全”。核心是不要写一堆if-else去判断而是设计一个状态表用字典或枚举加映射表来描述合法转移。比如订单状态可能是待支付 - 已支付 - 已接单 - 运输中 - 已完成。每种状态下允许触发的事件不一样。我的做法是维护一个字典key是当前状态value是允许转移到的状态集合。NSDictionary *stateMap { (OrderStatePendingPayment): [(OrderStatePaid)], (OrderStatePaid): [(OrderStateAccepted), (OrderStateCanceled)], (OrderStateAccepted): [(OrderStateDelivering), (OrderStateCanceled)], (OrderStateDelivering): [(OrderStateCompleted)] };然后编写一个统一的状态变更方法- (BOOL)canTransitionFromState:(OrderState)fromState toState:(OrderState)toState在这里查表。真要落到业务里还要考虑并发两个接口同时修改订单状态怎么办所以服务端通常会加版本号或状态条件更新客户端也要在状态变更失败时回滚UI。答题时能提到“边界状态要做幂等判断”面试官会觉得你有生产环境经验。计费规则同样是很经典的场景题。货拉拉的计费包括起步价、里程费、等候费、高速费有时还有优惠券和动态调价。设计时要避免在ViewController里写死规则而是把计价策略抽出来每种费用作为一个计算器支持组合。答题思路是定义计价协议然后用策略模式或者链式调用组合不同费用项。这既符合开闭原则也为后续灵活调整计价规则留下空间。我在实际项目里还会把计价参数化从服务端拉取配置而不是发版更新这样业务调整更灵活。4. 常见问题与排查技巧实录4.1 笔试代码分析题里最爱埋的几个坑A卷的代码分析题有几类陷阱可以说是命中率80%以上。一个是数组越界和字典插入nil key。很多人写[array objectAtIndex:index]时没判断index是否在有效范围内轻则crash重则线上报警。答题时你要能看出问题并说明原因数组越界是NSRangeException字典插入nil会直接崩溃原因是对OC容器来说nil意味着空不能作为元素存储。另一个常见的坑是Block捕获外部可变对象。比如NSMutableArray *marray [NSMutableArray array]; void (^block)(void) ^{ [marray addObject:1]; }; marray [NSMutableArray array];这里block捕获的是marray指针还是指针指向的对象答案是获取了对象所有权但外界重新给marray赋值时block里的marray仍然指向旧对象而外部的marray指向新对象。这是个非常隐蔽的问题笔试里最常作为选择题出现。要避免这种问题可以加__block让别人重新赋值时block能感知到新对象或者设计成不变对象让变量不可重新赋值。还有一个高频坑是KVO的监听者没有及时移除。A卷给了一个场景进入页面时添加KVO页面销毁时要移除但代码写在dealloc里。问题是dealloc可能在KVO移除前触发崩溃。正确写法是在invalidate、viewWillDisappear等时机移除或者利用系统API的交错操作。KVO本身不是线程安全的多线程同时addObserver和removeObserver也可能出错这一点笔试答题时提到会加分。4.2 Frame与Bounds、nil与Nil、KVC与KVO的对比笔试填空和选择题喜欢出这种表面简单但容易混淆的概念。Frame是相对于父视图坐标系的矩形Bounds是相对于自身坐标系的矩形无论bounds怎么变frame在父视图中的位置不变但内容会以bounds的原点为准进行重绘。很多人在处理ScrollView滚动时被这个搞蒙过。nil、Nil、NULL、NSNull四兄弟也是常客。nil是对象的空指针Nil是类对象的空指针NULL是普通C指针的空值NSNull是一个真正占用内存的对象用来表示集合里的空值。A卷有一道选择题问向nil发消息会发生什么答案是OC中会返回nil不会崩溃。这是因为消息发送机制在运行时检测到接收者为nil就直接返回这既是个知识点也是个面试官喜欢顺藤摸瓜追问的入口。KVC和KVO更是必问。KVC是通过字符串key访问对象属性的机制会先查找setter/getter再查找实例变量都找不到时还会调用valueForUndefinedKey通常用来做字典转模型或访问私有成员变量。KVO则基于KVC和isa-swizzling系统会动态生成一个子类并重写setter方法在setter里发送通知。笔试答题时如果能简述“监听后对象的isa指针会指向一个新的中间类”这就是亮点。4.3 方案取舍网络缓存策略怎么设计A卷简答里有一道方案题给你一个订单列表接口让你设计缓存策略。我当时的思路是先确定缓存维度按用户ID加接口路径做key再确定缓存时效订单类数据不适合长时间缓存因为状态变化频繁。最终方案是缓存30秒内的数据超时后强制走网络请求。如果网络请求失败且本地有缓存则先展示缓存并提示用户当前是离线数据。还可以用NSURLCache做HTTP缓存配合服务器的Cache-Control头。不过这种方式更偏向静态资源对接口动态数据并不好用。更常用的是自己用文件或数据库存JSON比如用WCDB或者FMDB存最近一次请求结果。设计缓存策略时要注意缓存数据的一致性订单支付成功后一定要立刻清理缓存否则用户会看到旧状态。我在项目里还会加一个“先行展示缓存后台刷新后比对”的逻辑先返回缓存给UI让用户觉得快然后发起网络请求拿到新数据后对比如果不同再更新UI。这种策略在列表页很常见笔试时可以展开讲。关键是说明白你的取舍依据而不是抛一堆名词。5. 准备建议与答题技巧5.1 从笔试到面试延伸出来哪些考察点笔试只是第一步。很多题目背后都对应着后续面试的追问比如笔试里写了一道状态机设计面试官就会接着问如果状态并发怎么办如果订单状态转移失败要不要回滚本地UI怎么处理。因此你在准备笔试时不要只准备“怎么答得对”还要准备“怎么答得深”。我的复习方法是把每道题当面试题来准备。比如遇到“ARC和MRC的区别”不只要背“ARC是自动引用计数编译期插入代码”还要延伸到“你是如何排查循环引用的用instruments还是静态分析线上有没有遇到过崩溃”。这类延伸问题才真正拉差距。同时建议自己动手把项目里的一个小模块用MVVM组件化的方式重写一遍。很多时候笔试题目不难难的是你有没有真正写过、踩过坑。写一遍之后你才能回答“为什么Controller要变薄”“组件化怎么测”这种实操型问题。没有实践经验光靠背题到面试环节很容易露馅。5.2 拿到试卷后先做这三件事第一快速扫一遍整张卷子把会做的题标记出来优先做自己有把握的稳定得分。不要死磕一道选择题导致后面的设计题没时间想。第二简答题和设计题先写大纲再用整齐的字迹或简短的代码块展示思路阅卷人更容易给你分。第三如果遇到不会的题尽量写出对应知识点和思考路径。比如你不知道某个API的具体参数但你能写出“我会去查头文件”至少能体现解决问题的方法论。答题时的代码规范也很重要。类名、方法名、变量名要见名知意不要写abc。代码的缩进、空行要整洁不要写得像一坨。我会在笔试最后留五分钟检查一下代码块的括号闭合有时一个小小的语法错误会让人觉得你基本功不扎实。5.3 这份卷子背后我更想提醒你的一件事很多人觉得笔试就是刷题但货拉拉A卷真正想找的是那种能独立扛起业务模块的工程师。为什么它会考状态机因为司机端订单状态每时每刻都在变做错了就会造成用户投诉为什么考内存管理因为地图页通常常驻很久泄漏一次就可能导致OOM。这些题目背后都是真实业务里的血泪教训。我后来整理这套题的时候最大的感受是知识点本身不是壁垒壁垒在于面对一个真实业务问题时你能不能快速定位到它背后的基础理论并用这些理论想出一套可靠的设计方案。所以准备iOS笔试不要抱着侥幸心理去押题而是把语言基础、内存管理、多线程、网络、架构设计这条主线系统地过一遍。你可以从一份旧题出发但落脚点一定要放在业务理解上这比任何一套标准答案都管用。最后分享一个小技巧考场上遇到设计题时间再紧也要画一张简单的图。哪怕只是长方形表示模块箭头表示调用关系都能帮你理清思路也能让阅卷老师一眼看出你的方案是完整的。我在那次笔试里就是在订单状态机的图上多画了一条“取消”分支后来面试时业务官专门针对这个分支和我聊了很久。别小看这一笔它恰恰说明你考虑到了真实业务里的异常情况。