先说个实话货拉拉这套2018秋招的iOS笔试题放到今天来看依旧有不少值得琢磨的地方。虽然题目背景带着那个年代特有的技术栈味道但里面考察的内存管理、多线程、网络层设计、架构选型这些底层功底恰恰是现在很多三年以内经验的iOS开发最容易翻车的地方。如果你正准备跳槽或者想系统梳理一下自己的iOS知识体系这套卷子的复习价值相当高。我前前后后把这套题翻了好几遍也结合自己带人、面试别人时踩过的坑把里面的考点、答题思路和常见误区完整拆一遍。1. 先从货拉拉这套笔试看整个考察布局1.1 货拉拉的业务场景决定了考题走向拿到这套卷子第一反应是它不像某些公司那样纯考算法或纯考语法而是把题目往业务场景上靠。货拉拉作为货运匹配平台核心链路是用户下单、司机接单、地图定位、轨迹上报、在线沟通这就注定他们非常看重iOS开发者在实际业务中的问题解决能力。所以卷子里会出现大量跟列表性能、网络请求、数据缓存、定位权限相关的题目而不是只问你“copy和strong有什么区别”这种纯八股。我当时看完整套题的感受是它想筛选的不只是“会写iOS代码的人”而是“能接手一个真实App模块、知道怎么排查问题、知道怎么做技术选型的人”。这一点在2018年很多中小型公司和独角兽公司的笔试题里已经比较常见了。货拉拉的题量不算特别大但每道题都留了足够的发挥空间你写简单答案能得分写深入了也能得分这就能把不同水平的人明显拉开差距。1.2 三个梯队题目的筛选逻辑货拉拉这套卷子大致可以分成三个梯队。第一梯队是基础语法和内存管理题比如Block循环引用、ARC下对象生命周期、属性修饰符的使用场景。这些题考察的是日常coding的基本功如果这里丢分后面基本没戏。第二梯队是网络与多线程题比如AFNetworking的实现思路、GCD与NSOperation的对比、如何保证线程安全。这些题目考察的是你对iOS系统框架的理解深度以及线上问题频发的领域有没有实战经验。第三梯队是架构与性能优化综合题比如MVVM的优缺点、UITableView卡顿怎么处理、如何设计图片缓存。这些题没有标准答案面试官想听的是你的思考过程和技术取舍逻辑。从筛人逻辑来看第一梯队用来刷掉基础不牢的第二梯队用来区分有没有真实项目经验第三梯队看的是技术视野和架构能力。建议做这套题的时候先拿满第一梯队的分数第二梯队尽量写出实现思路第三梯队哪怕不会也要把分析框架写出来这样至少能给面试官一个“有思路、可培养”的印象。2. 高频考点逐个拆解从语法到实战2.1 内存管理与Block循环引用是送分题也是送命题内存管理这块几乎是所有iOS笔试的必考项货拉拉这套题里同样不例外。很多人背了ARC和引用计数的概念就觉得自己会了但一到了Block循环引用就说不清楚。Block在Objective-C里会捕获外部变量如果Block被一个对象强持有而Block内部又强引用了这个对象就会形成 retain cycle。经典场景是控制器持有Block属性Block里又使用了self或者self持有定时器定时器Block里强引用self。我当时指导过的一个候选人在这道题上写的是“用__weak修饰self”但问他为什么这样就一定能解决他却答不上来。其实核心原因是__weak不会增加引用计数打破的是“self - block - self”这条强引用链。但注意如果Block内部执行的是异步任务异步执行期间self可能已经被释放了所以在Block里需要用__strong再修饰一下传入的弱引用防止执行过程中被提前释放。写答案时把这个细节写出来就是加分项。注意检测循环引用不要只靠肉眼看代码直接用Xcode的Memory Graph Debugger跑一遍或者用Instruments的Leaks模板检查三步之内就能定位。线上排查时我见过太多人靠猜来解决循环引用最后发现是通知没移除、或者闭包持有导致的。另外NSTimer也是循环重灾区。我见过不少项目里直接在viewDidLoad里创建一个TimerTarget设为self结果页面pop了Timer还在跑。正确做法是iOS 10以后用block形式的scheduledTimerWithTimerInterval:repeats:block:然后在dealloc里invalidate或者用weak proxy包装target。2018年这套题的时代背景里很多人还在手写weakProxy现在直接用系统API就够了但原理必须要懂。2.2 多线程GCD只要掌握这四类考点就够了多线程题目货拉拉的笔试题里考察得比较经典。一个是问GCD和NSOperation的区别另一个是在面试环节会让你手写线程安全的单例。先说区别GCD是纯C接口的轻量级并发框架使用起来简洁适合一次性任务和简单的并发队列NSOperation是OC层面的抽象支持取消、依赖关系和KVO监听状态适合比较复杂、需要精细控制的任务组合。实际项目里网络请求封装、图片异步下载用GCD多而一些复杂的离线任务流程、需要按依赖关系串行的模块用NSOperation更合适。答题时最好结合场景说明别只说“NSOperation基于GCD”因为面试官想听的是你知道哪个场景选哪个工具。再说线程安全。GCD中保证线程安全的核心手段是串行队列、barrier、信号量。我特别想说一下栅栏函数dispatch_barrier_async它在并发队列里能实现“读并发、写独占”的效果比每次操作都加锁性能好很多。很多人会用synchronized或者NSLock但并发量大的场景下锁的竞争开销不容忽视。用barrier实现多读单写代码可读性也好笔试题里如果让设计一个线程安全的缓存这基本是最优解之一。关于死锁有一道经典坑题在主队列调用dispatch_sync到主队列或者在一个串行队列里再dispatch_sync到它自己都会死锁。原因是sync会阻塞当前线程等待任务执行而任务又排在同一个串行队列里等于自己等自己永远等不到。这个考点在笔试里出现率很高因为很多人没实际踩过只在面试题里见过回答起来很僵硬。2.3 网络层设计从AFNetworking底层到弱网优化网络题也是货拉拉的考察重点毕竟货运App的司机端和用户端对网络依赖极高。卷子里的问法是“简述AFNetworking的底层实现原理”这种题属于“用过但说不清”类型。AFNetworking 3.x以后是基于NSURLSession封装的核心是AFURLSessionManager它内部用NSURLSessionDataTask做HTTP请求通过block回调数据而AFHTTPSessionManager在其基础上封装了GET/POST等方法并默认开启了JSON序列化和响应序列化。答题时如果能提到AFNetworking如何通过delegate和block两种方式回调、如何处理后台下载、如何做安全策略ATS、SSL Pinning分数会明显不一样。尤其SSL Pinning这个在货运这类对数据安全要求高的场景里很重要。它能防止中间人攻击原理是客户端内置服务器的公钥或证书每次连接时对比服务端返回的证书是否匹配不匹配就直接断开连接。我当时还见过一道变形题“让你设计一个网络层你会怎么设计”这种题其实是考分层思想。比较稳的回答框架是底层用NSURLSession / Alamofire做网络请求中间层做公共参数注入、签名加密、统一错误码处理上层按业务模块封装API配合泛型和Codable做数据模型映射再在外围加缓存层和日志系统。把这几层说明白面试官就能确认你有过项目架构经验。弱网处理这个点货拉拉的业务场景很典型司机在地库、隧道、高速上网络时好时坏。我们当时做的方案是请求超时时间分级关键接口用短超时快速失败普通接口可以稍长失败后区分网络错误和业务错误网络错误要做指数退避重试重试次数要有限制防止雪崩请求结果要加本地缓存兜底至少保证页面能展示上次的成功数据。实操心得DNS解析在弱网下也容易成为瓶颈。2018年那会儿大家还在用系统DNS后来越来越多的App开始接入HTTPDNS来解决运营商DNS劫持和解析延迟问题。如果笔试题里问“线上请求超时怎么排查”你可以从DNS解析、TCP建连、TLS握手、首字节时间这几个分段来回答千万别只丢一句“网络不好”。3. 实战演练货拉拉笔试中出现的典型题与最优解3.1 手写一个线程安全的单例别踩这些坑这道题我几乎在所有iOS面试中都见到过货拉拉的笔试题里也出现了。标准的正确写法是用dispatch_once它保证整个App生命周期内代码只执行一次并且是线程安全的。Swift里就是static let shared ClassName()因为Swift的静态常量本身就是懒加载且线程安全的。但是这道题有不少隐藏坑。第一不要在init里做太多耗时操作否则首次访问单例时会卡住调用线程。我见过有项目在单例的init里初始化数据库连接、注册推送、上报启动日志导致首帧加载特别慢。正确做法是单例只负责管理状态重活放到异步线程或者按需创建。第二单例的“全局可变状态”本身就是一把双刃剑。在多人协作时如果大家都往单例里塞数据状态管理会越来越混乱最后变成一瓶浆糊。笔试里如果问到你使用单例时要注意什么能说出“控制可变状态粒度、避免跨层依赖”这种话面试官会觉得你真的在大型项目里踩过坑。3.2 UITableView卡顿优化从cell复用到异步绘制货运App的首页和订单列表全部依赖UITableView列表流畅度直接决定用户体感。货拉拉笔试里有一道题问“UITableView滚动卡顿的原因和优化方案”这是个老生常谈但依然能拉开差距的题目。基础答案人人都会cell复用、高度缓存、图片异步加载、减少离屏渲染。但真正让答案有区分度的是细节。第一cell复用注册要统一用registerClass或registerNib在dequeueReusableCellWithIdentifier:forIndexPath:时系统会自动创建和复用不要在cellForRowAtIndexPath里手动判断nil再创建。第二高度计算是最容易卡顿的元凶。iOS 8以后虽然支持了self-sizing cell但遇上复杂的富文本、图片比例不固定还是要手动计算高度并缓存。2018年那批项目里很多人习惯用frame手动算然后用一个字典按indexPath存高度。现在你可以用UITableViewDiffableDataSource结合snapshot来管理数据减少reloadData带来的整表刷新开销。第三图片加载是列表流畅度的最大杀手。主流方案是SDWebImage或YYKit但笔试里如果你能说出SDWebImage的完整流程——先查内存缓存、再查磁盘缓存、都没有就异步下载、下载完解码后回调主线程刷新并配合占位图、渐进式加载、预处理圆角避免离屏渲染——这道题基本就稳了。第四异步绘制属于加分项。如果cell非常复杂可以在后台线程用CoreGraphics把内容绘制成一张位图再赋值给layer.contents。这个技术现在很多高性能列表比如长文本阅读、信息流还在用虽然维护成本高但你能说出来说明你真的研究过性能瓶颈。我当时做即时通讯的会话列表就采用过类似方案滚动帧率从40帧提到了满帧。3.3 手写简易图片加载器考察的不只是API调用如果面试官觉得你SDWebImage背得熟可能会让你“不用SDWebImage手写一个图片异步加载器”。货拉拉的题里就有类似的设计题。这道题考的是并发控制和缓存设计代码不难但考虑要全面。我建议的骨架是这样的定义一个图片缓存类内存缓存用NSCache支持自动清理磁盘缓存用文件存储key用图片URL的MD5值定义一个下载管理器内部用NSOperationQueue管理下载任务maxConcurrentOperationCount设为4避免同时发起太多请求加载图片的入口方法先查内存缓存再查磁盘缓存都没有则创建下载任务下载完成后解码图片并写入两级缓存回到主线程更新UIImageView时注意cell复用带来的错位问题比较当前显示的URL和任务回调的URL是否一致。这段实现看起来简单但能考察出一个人对线程安全、缓存策略和用户体验细节的理解。写的时候不要用锁直接用队列和派发方式规避资源竞争代码更优雅。如果能在答案里顺便提一嘴“用NSCache而不是NSDictionary做内存缓存因为系统在内存紧张时会自动清空成本较高的对象”面试官会觉得你确实读过文档而不是背题。4. 架构与混合开发面试官真正想考察的视野4.1 为什么MVVM在业务复杂的App里比MVC更顺手架构题几乎是所有中高级iOS岗位的必问题货拉拉的笔试题里也有“谈谈你常用的架构及原因”。MVC在iOS开发里是默认起点但一旦业务复杂起来Controller会变得越来越臃肿最终变成Massive View Controller。这就是为什么很多团队转而使用MVVM把ViewModel抽出来处理业务逻辑和数据映射ViewController只负责视图生命周期和交互绑定。MVVM的核心是数据绑定。在2018年那会儿OC和Swift 4时代大家常用ReactiveCocoa或RxSwift做绑定后来Swift里出现了Combine再后来大家发现其实用简单的closure回调或者didSet也能实现轻量绑定非响应式框架的项目直接引入RxSwift反而增加学习成本和调试难度。我在自己的项目里一般这样取舍小模块用MVC足够清晰中大型页面用MVVM轻量绑定不用全家桶。答题时如果能给出这套“按场景选择”的思路比无脑吹MVVM高级得多。货拉拉的业务场景里订单详情页就是一个特别典型的MVVM应用场景状态非常多待支付、待接单、运输中、已完成、已取消每种状态下按钮组、金额、文案都不一样。如果用MVCViewController里会堆满if-else状态判断用MVVM就可以把状态机逻辑放到ViewModel里页面只管根据状态刷新UI测试也方便很多。4.2 iOS混合开发WKWebView与JSBridge的细节2018年那个时间点Hybrid方案是所有大厂App的标配货拉拉这种重运营的平台也会用H5承载大量活动页和部分业务页面。笔试题里问WKWebView与UIWebView的区别是高频题核心答法是WKWebView基于WebKit内核渲染性能更好内存占用更小支持更多的HTML5特性并且UIWebView已经被废弃。细节上要注意WKWebView是独立进程渲染所以App崩溃不会影响网页它的Cookie管理和NSURLProtocol拦截方式跟UIWebView不一样跨域请求也受ATS影响。JSBridge的原理解析是另一个高概率考点。常用方案是通过WKScriptMessageHandler在WebView和JS之间建立双向通信。JS调用原生时通过postMessage发送消息原生侧注册处理原生调用JS时用evaluateJavaScript执行一段JS代码把数据序列化成JSON传入。关键点是要约定好统一的协议格式比如事件名、参数、回调ID这样两边维护起来才不混乱。热词里提到的unzip的uniapp打包iOS流程也是混合开发的一种形态核心是原生壳工程加载离线包或在线资源再通过桥接层调用原生能力。做混合开发最怕的是本地资源加载失败导致白屏。我们的经验是所有本地资源包先做版本管理启动时异步检查云端版本有更新就静默下载下载完成后原子性切换H5页面加载失败要有一个兜底的原生错误页并提供重试按钮关键路径页面尽量下沉到原生不要全部塞进H5。4.3 地图定位、蓝牙等硬件能力在货运场景里的落地热词里出现了“iOS ble连接参数规范”“iOS CBCentralManager系统级蓝牙状态”这些内容。货拉拉的司机端会用到蓝牙——比如蓝牙打印机、蓝牙耳机、车载设备连接定位更是核心司机位置实时上报、轨迹回传全依赖它。笔试里如果考到定位通常会问什么时候请求定位权限、后台定位怎么保活、定位精度怎么选。我建议的定位方案是启动时先请求“使用期间”权限不要一上来就请求“始终”权限否则用户拒绝率会很高如果业务确实需要后台定位在Info.plist里配置UIBackgroundModes为location并说明使用场景定位精度的选择上城市道路导航用kCLLocationAccuracyBestForNavigation但省电模式下要降级到kCLLocationAccuracyHundredMeters因为持续高精度定位非常耗电。热词里提到“iOS开发 电池优化”这确实是做LBS类App必须考虑的问题。常用的降功耗策略包括定位频率自适应车速快时加密、静止时降低、使用region monitoring替代持续定位、在前台才开启高精度、后台只获取粗略位置。蓝牙这块核心要注意CBCentralManager的状态回调。很多人以为只要系统蓝牙关闭回调里就会返回poweredOff但实际上系统蓝牙状态扫描回调和应用层授权状态是两回事。笔试里如果问“蓝牙连接失败怎么排查”要从这几步入手确认系统蓝牙开关状态、确认App的蓝牙权限iOS 13以后有专门权限、确认外设广播是否正常、确认连接参数interval、latency、timeout是否符合外设要求。能说出“system state和app state区分”的候选人说明真的连过蓝牙设备。5. 笔试常见问题与避坑指南5.1 笔试题里那些隐形扣分点我做面试官时有几个高频扣分点仔细观察几乎每批笔试都有类似问题。第一代码规范。有人手写单例时方法命名大小写不规范属性没有用self访问甚至有人连分号都漏了。虽然笔试不是编译运行但面试官一眼就能看出你是不是平时写码习惯很好。第二边界条件处理。比如手写数组去重不考虑空数组手写图片缓存不考虑URL为nil手写单例不考虑多线程并发调用。这些细节暴露的是工程思维不足而不只是算法不行。第三滥用高级特性。有人为了展示能力在简单的题目里强行套用RAC、链式编程、泛型结果写出来又长又难懂。笔试答案应当用最简单、最稳妥的方式表达高级特性只在你需要它解决实际问题时才使用。提示手写代码时如果题目没有明确要求优先用大家常用的系统API和框架不要自创一个命名风格独特的工具类。面试官期待看到的是“可维护、团队能接手”的代码而不是“个人风格浓重”的代码。5.2 我的复习路线笔试前一个月怎么准备最有效如果你现在正好在准备iOS面试我建议把复习重心放在三个方面。第一系统API和官方文档是根基。UIViewController生命周期、RunLoop、Auto Layout、TableView优化、内存管理这些内容必须做到能闭卷写出原理。第二源码阅读是拉开差距的关键。把SDWebImage、AFNetworking、YYModel的源码各读一遍不用逐行看重点看它们的类结构、接口设计和核心实现思路。面试官问任何“它怎么实现的”相关问题你都能说出个一二三来。第三项目沉淀最重要。把自己做过的项目里最复杂的一个模块整理成一篇小复盘包括你当时遇到的技术难点、解决过程、最终方案、有没有更好的做法。面试官比技术栈更看重的是你的复盘能力和技术品味。我当时准备面试时还有一个习惯把遇到的每个问题都写进一个Markdown文档分门别类整理每周过一遍。这样笔试前翻起来特别高效而且很多东西写着写着就理解了比死记硬背强得多。5.3 这套题对今天的iOS开发者还有什么参考价值有人可能觉得2018年的题太旧了Swift都迭代到6了UIKit也出了许多新特性老题还有啥参考价值我的看法是iOS底层的内存管理、多线程模型、网络协议栈这些核心机制并没有本质变化反而因为Swift的不断演进你需要更清楚地理解底层原理才能写出安全的Swift代码。比如Swift的闭包捕获列表、actor模型、Sendable协议这些新特性和旧的循环引用问题本质上是一脉相承的。面试官如果问“Swift的actor怎么解决数据竞争”你如果连GCD的队列和锁都说不清楚那还是白搭。另外货拉拉这套题的价值在于它很“务实”不搞偏题怪题每一道都能在真实项目中找到对应场景。这种风格其实代表了一类业务驱动型公司的面试主基调——架构选型要经得起业务考验技术方案要能落地。你在准备任何公司面试前先研究一下对方的产品形态和业务链路再针对性地准备相关技术点命中率会高很多。我个人在实际操作中还有一个体会笔试阶段虽然不能面对面沟通但它恰好是你展示工程素养的窗口。把你的命名、注释、边界条件都处理好就像在GitHub上开源一个精品项目一样面试官对你的印象分会有很大提升。参加完这轮笔试不管结果如何把每一道错题都吃透你的iOS基础能力一定会上一个台阶。