公司动态

货拉拉iOS笔试题复盘:从内存管理到多线程的考点解析

📅 2026/9/1 20:58:15
货拉拉iOS笔试题复盘:从内存管理到多线程的考点解析
1. 拿到这份卷子先看它在考什么2018年秋招季我帮几个学弟复盘过货拉拉的iOS笔试题卷二A这个编号到现在还有印象。那张卷子放在当时的行情里不算难但出得很有代表性考的都是iOS开发日常必须面对的东西没有那种“脑筋急转弯式”的偏题怪题。这一点本身就很能说明问题——货拉拉当时的业务是货运匹配平台App承载的是真实交易和物流调度他们招人最关心的不是你会不会背八股而是你能不能在一个真实、高压、多并发场景里把代码写稳。先说结论如果你是iOS岗位的求职者不管是不是冲着货拉拉去的这种题都值得认真过一遍。它的考察点覆盖了OC语言基础、内存管理、UIKit界面、多线程网络、数据存储几个模块恰好对应一个初期iOS工程师日常工作的核心技能树。对校招生来说这份卷子更像一张“技术体检表”能帮你快速看清自己的薄弱环节在哪对有经验的人来说它也是一次很好的知识体系自查机会。我当年复盘这套题的时候就在内存管理和并发部分发现自己有几个盲区后来在工作里确实踩了对应的坑回想起来有点后怕。1.1 货拉拉的业务形态决定了技术考察方向货运和客运不太一样乘客端、司机端两套App承载了下单、接单、定位、路径规划、计费、支付、消息推送等一系列链条。这些业务场景对iOS端提出的要求首先是稳定其次才是体验。地图SDK要一直跑、定位要持续回调、订单状态要实时刷新这些场景背后涉及的就是位置权限、前后台切换、内存占用、网络异常处理这些基础能力。所以你会发现在这套笔试题里凡是涉及生命周期、通知机制、内存管理的内容都出得很细。这不是出题人想故意为难你而是因为地图、定位、长连接这些模块一旦出现内存泄漏或者线程问题整台手机就会卡顿、发热用户体验直接崩掉。考察“为什么weak修饰delegate而不是strong”这种题目背后的实际意义是防止delegate持有关系造成循环引用进而导致ViewController无法释放——这在嵌入地图页、列表页频繁跳转的导航类App里是非常现实的故障来源。还有一个容易被忽略的点货运物流场景下司机端的网络环境很不稳定可能频繁进出地下车库、高速路段弱网和断网重连是常态。所以网络相关的题目不会只停留在“GET和POST的区别”而是会往超时处理、重试机制、缓存策略上靠。这种从业务反推技术考察点的思路我后来自己在面试别人时也一直在用效果比照搬面试题库要好得多。1.2 笔试题目背后的四个考核维度我把这份卷子A卷以及同类型的货拉拉iOS笔试题放在一起复盘之后发现所有题目可以归纳成四个维度。第一个维度是OC语言本身比如属性关键字的区别、block、KVC/KVO、协议与分类。第二个维度是内存管理ARC、引用计数、循环引用、autoreleasepool这些是重头戏。第三个维度是UIKit与事件传递从UIView和CALayer的关系到hitTest再到Auto Layout的约束冲突考察的是你对界面体系的理解是否成体系。第四个维度是并发与网络GCD、NSOperation、线程安全、HTTP状态码、网络库的原理。这四个维度不是随便挑的。它们正好对应你入职以后每天都会碰到的代码类型属性写不写得对关系到数据传递是否安全内存管理是否过关关系到App能不能长期稳定运行UIKit理解得深不深关系到界面能不能高效复用网络与多线程掌握得好不好直接决定异步数据加载能不能顺畅。换句话说过了这套题基本等于证明了你有独立完成一个iOS模块开发的基础能力。我经常跟新人说笔试不是目的它是帮你把平时零散的知识点串成一条线的工具这套题就很好地起到了这个作用。2. 逐块拆解从OC基础到UIKit的必考区笔试题不像面试聊天它没法跟你聊项目只能通过题目来测你的“硬底子”。所以这一块我把当年考得最频繁、也最容易丢分的几个知识块拆开讲一遍每个都附带我自己的复盘心得以及后来我在实际项目中验证过的理解。2.1 OC语言基础属性、block与KVC/KVO属性关键字是必考中的必考。atomic和nonatomic的底层区别strong、weak、copy、assign分别在什么场景下用这是第一道分水岭。很多新手会把copy等同于不可变这是典型的理解偏差。copy真正的作用是复制一份数据让赋值双方互不影响常见于NSString和Block属性而NSMutableString用strong修饰就很容易在外部被偷偷改掉这个坑在项目里非常常见。我见过不止一次线上数据错乱最后定位到根源是某个可变字符串属性被外部对象改了就是因为修饰符选错了。属性关键字的对比我建议你按下面这张表去记忆笔试选择题基本稳拿分修饰符关键行为适用场景strong持有对象引用计数1绝大多数对象属性weak不持有对象对象释放后自动置nildelegate、IBOutlet、父子关系中的子copy拷贝一份新对象赋值双方互不影响NSString、Block、NSArray需要不可变时assign不持有只做简单赋值基本数据类型、CGFloat、NSIntegeratomic读写加锁线程安全但有性能损耗需要多线程读写的属性实际项目少用nonatomic不加锁性能高但不保证线程安全绝大多数业务属性Block也是绕不开的。除了考察“block能不能修改局部变量”这种经典问题更常考的是block的三种类型全局Block、栈Block、堆Block。在ARC下栈Block会自动拷贝到堆上所以你在面试时如果只回答“为什么循环引用”而不解释block从栈拷贝到堆的过程答题深度就会差一截。2018年那会儿Swift已经出了但货拉拉的iOS技术栈还是OC为主所以这套题的OC比重很高block这类语法在OC里用得非常频繁属性里存block、网络回调传block、动画完成回调也传block几乎无处不在。KVC和KVO考得相对基础但深挖起来很能拉开差距。KVO触发条件、直接赋值成员变量时会不会触发KVO、手动触发KVO的方法这些都是细节。很多候选人知道KVO怎么用却不知道KVO是基于isa-swizzling实现的也不知道为什么对属性调setter方法才会触发对成员变量直接操作不会触发这种底层的缺失在二面问项目时很容易暴露。这里有个小技巧我工作后经常用如果需要在对象释放前移除KVO观察者可以写一个中间代理对象来持有观察者这样即使忘记移除也不会崩溃实测下来比在dealloc里手动removeSafe要稳很多。2.2 内存管理与循环引用最容易翻车的点内存管理这块笔试不只考ARC还喜欢考autoreleasepool在什么时候有用什么时候是多余的。比如在for循环里创建大量临时对象这种场景用autoreleasepool就可以显著降低峰值内存但很多人会忽略这个点。还有NSTimer、CADisplayLink这种基于runloop的重复回调如果不主动invalidate就会因为target持有导致控制器无法释放这属于线上特别常见的内存泄漏来源。我在实际项目里遇到过类似问题页面反复push和pop内存一路涨最后拿Instruments的Leaks模板一查全是timer没释放。循环引用的典型场景除了delegate没有用weak、block捕获了self、NSTimer强持有target之外还有一个容易被忽略的父子控制器互相持有。很多人只背了“delegate要用weak”但没想过为什么。delegate通常是单例或者父对象持有子对象如果子对象再强引用delegate那父对象就永远释放不掉。用weak打破这个环其实是让持有方向变成单向依赖。这里我建议大家做题时画一张持有关系图把每个对象之间的引用方向标出来有没有环一眼就能看出来比死记硬背有效得多。另外2018年的笔试题里还有一个高频考点是“在block里使用self要不要weak”。这个问题的标准答案不是“一定要”而是“看是否形成环”。如果block被self持有block里又用了self那就形成了self - block - self的环此时必须weak如果block是系统方法临时调用的比如UIView的动画block执行完就释放那直接用self也没问题。我见过太多人把“weakSelf”当万能药处处weak反而把代码写得很难读。搞懂引用计数的本质比背结论重要一百倍。2.3 UIKit与布局界面题的价值涉及界面的题目主要考两方面一个是UIView和CALayer的关系另一个是Auto Layout和Frame的取舍。UIView是对CALayer的封装负责事件响应和内容布局CALayer负责实际的绘制和动画渲染。能讲清楚这个关系说明你对渲染链路有基本认知这在处理复杂动画和性能优化时特别重要。我工作中有一次优化列表滚动卡顿就是通过把图片圆角从view.layer.cornerRadius改为预绘制圆角图来解决的不提前理解CALayer的绘制机制根本想不到这层。Auto Layout的题目通常会给你一组约束问你哪个约束冲突、哪个约束会导致不确定布局。这类题不是纯考语法而是在考你对约束方程的理解。我自己带新人的时候发现能快速定位约束冲突的人往往是对不等式约束、优先级priority、content hugging和compression resistance有过真实调优经验的人而不是只会用IB拖线的人。2018年的iOS面试还不太考SwiftUI所以Auto Layout是绝对的主流。关于UIStackView这也算2018年前后的热点考点了。其实它就是封装了Auto Layout的容器视图能在不手写约束的情况下自动处理等间距、换行、对齐等布局需求。在一些页面结构固定的场景下用UIStackView能省掉大量约束代码也让布局更不容易出冲突。笔试题里如果问“UIStackView和直接用Auto Layout约束有什么区别”本质是在考察你知不知道UIStackView是对约束的一种更高层封装以及它对动态增删子视图的优势。2.4 多线程与网络并发问题的考察逻辑多线程的题目核心永远是线程安全和死锁。GCD必考的问题是dispatch_sync在主线程执行会怎样dispatch_apply、信号量、栅栏函数的区别。组题的时候货拉拉的笔试题跟大多数公司一样非常喜欢考“多个网络请求并行全部完成后统一刷新UI”这个需求——这几乎是所有App开发都会遇到的实际场景标准解法是dispatch_group或OperationQueue的依赖关系。我建议你把两种方案都写一遍体会一下它们的差别dispatch_group更轻量OperationQueue则更适合做依赖管理和取消操作。网络层除了HTTP状态码、GET和POST的区别这种送分题还会考到HTTPS的握手过程和SSL Pinning这个跟货拉拉这类交易平台对数据安全的要求有关。AFNetworking的原理也是高频考点比如AFNetworking为什么用NSURLSession而不是NSURLConnection因为前者支持后台下载、断点续传、更好的性能表现。2018年那个时间点NSURLSession已经是主流如果候选人还在答NSURLConnection基本会被判断为知识库没有及时更新。还有一类容易被忽略的网络题是关于Charles抓包的。比如“App里的HTTPS请求为什么会在Charles里出现证书错误”“如何配置SSL Proxying才能抓到包”这些不只是测试工程师需要懂开发也需要知道。很多线上问题需要抓包定位特别是排查接口参数对不对、响应数据结构有没有变不会抓包就等于少了一双眼睛。我在带实习生的时候第一周就让他们学会配Charles抓包后面很多低级问题他们自己就能定位了。3. 实操过程我当年是怎么“做题”的光知道考点分布还不够我想把“做题”这个过程本身也拆一拆。很多人笔试挂了不是知识点不会而是题目没读透、答案没组织好、时间分配不合理。这部分完全可以训练而且训练之后提升非常明显。3.1 拿到题目先看什么读题技巧我自己的习惯是拿到卷子先花两分钟通读一遍把题目按“送分题、记忆题、推导题、设计题”四类快速归类。送分题先做保证基础分拿稳记忆题快速作答不会就跳推导题重点做因为这类题最能体现思考深度设计题放到最后因为这类题需要完整的时间和思路来展开。以这种卷子为例前面的选择题和填空题通常是考点密集型的每一道都可以在1分钟内解决遇到卡壳的不要恋战直接跳过等把所有基础分拿到后再回头补。代码题和设计题才是拉开差距的地方一定要留足时间。很多同学在选择题上死磕一道不确定的题结果代码题只写了两分钟就交卷这是最亏的。我见过太多笔试翻车的案例根源不是不会而是时间分配不合理前面纠结太久后面不得不草草收场。3.2 代码题的解答范式从思路到代码笔试代码题不需要你写出能编译运行的完整工程但需要你展示出完整思路。我一般按四步来写先写思路注释再画关键数据结构再写核心逻辑最后补边界处理。比如考“实现一个线程安全的可变字典”第一步我会写清楚考虑用怎么加锁第二步说明数据结构用NSMutableDictionary但访问需要统一加锁第三步写出加锁访问的伪代码第四步补充读写并发下的性能问题比如要不要用pthread_rwlock来优化读多写少的场景。即使最后代码不完整面试官看到步骤齐全也会给你一个不错的印象分。这里我写一个当时比较标准的作答样式你们可以参考// 思路用串行队列保证所有读写操作都串行执行天然线程安全 - (instancetype)init { self [super init]; if (self) { _dictionary [NSMutableDictionary dictionary]; _queue dispatch_queue_create(com.example.safeDict, DISPATCH_QUEUE_SERIAL); } return self; } - (void)setObject:(id)obj forKey:(NSString *)key { dispatch_sync(_queue, ^{ [self-_dictionary setObject:obj forKey:key]; }); } - (id)objectForKey:(NSString *)key { __block id result; dispatch_sync(_queue, ^{ result self-_dictionary[key]; }); return result; }然后我在注释里补充如果读多写少可以换成pthread_rwlock读并发、写独占性能会更好还要补充key为nil、obj为nil时要不要抛异常或静默返回。这样一道题答案的完整度和思考深度都会提升一个档次。3.3 简答题的作答策略怎么写出有经验的答案简答题是笔试中最容易拉开差距的题型它考的不只是你记没记住知识点而是你能不能把这个知识点讲得有条理、说得有场景。我的建议是每道简答题都用“结论原理场景”三段式来写。比如考“为什么delegate要用weak”先写结论——“避免循环引用”再写原理——“子对象持有父对象父对象再持有子对象任一无法释放”最后补一个场景——“列表页push到详情页详情页的delegate指向列表页如果不加weak返回时列表页无法释放内存持续增加”。这种答案结构的好处是阅卷人一眼就能看到你的逻辑链而不是在一堆零散句子里找答案。而“场景”这个部分是最能体现你有没有真实开发经验的地方。哪怕你只是大三学生也要尝试用你做过的小项目去佐证每个知识点比如你在课程设计里遇到过内存上涨的问题然后主动去看了一遍KVO的触发机制这些都可以写上去。面试官很清楚校招生不会有太多商业项目经验他要看的是你有没有自我驱动的学习能力和问题敏感度。3.4 时间分配与答题顺序的实战模板我推荐一个时间分配模板适用于大部分90分钟的iOS笔试题。前5分钟通读全卷标记题目类型接下来25分钟解决所有选择题和填空题控制在30分钟以内不会的先用标记圈出来中间40分钟做代码题和推导题这部分是拿分大头值得投入最后15分钟处理设计题和返工之前跳过的题目即使设计题做不完也要把思路写出来哪怕只是要点也能拿到步骤分。最后的5分钟用来检查是否有漏题、姓名考号是否填好。这个模板不一定要严格照搬但它能帮你建立一种“先把能拿的分全部装进口袋再去碰难题”的应试意识。很多技术能力强的人笔试反而挂往往就是栽在时间管理上在难题上耗了太久前面基础题反而没时间仔细检查。4. 真题复盘那些容易被忽略的细节这部分我聚焦几个在复盘中反复出现的细节问题。它们通常不是独立的大题而是藏在小选项、小填空里的“陷阱”一旦踩中就丢分。我在帮人判卷的时候发现这类细节的丢分率特别高。4.1 生命周期与通知为什么总是被混淆UIViewController的生命周期方法调用顺序是每次必考的选择题。viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear、viewDidDisappear以及它们之间的调用次数差异。很多人能背出顺序但一问“在viewDidLoad里获取frame是什么值”就答不上来因为viewDidLoad执行时视图还没完成布局frame往往还是xib或代码里设置的初始值。正确的做法是在viewDidLayoutSubviews或viewWillAppear之后再取真实尺寸。通知相关的题重点在通知的发送和接收线程。通知的post和接收默认在同一线程也就是说在主线程发送通知接收方法也在主线程执行在子线程发送接收方法也在子线程。如果你想切回主线程做UI刷新需要自己在接收方法里调dispatch_async。这个点经常以“以下关于NSNotificationCenter的说法正确的是”形式出现答案往往就是“接收和发送在同一线程”这一项。4.2 数据持久化偏好设置、归档和数据库的选型逻辑数据存储也是iOS笔试题里的常客。相关题目通常不会让你手写SQLite而是考你在不同场景下怎么选型。NSUserDefaults适合存用户偏好、开关状态、小体积配置不适合存大量列表数据归档NSKeyedArchiver可以存自定义对象但整个文件读写适合小体量模型数据SQLite适合结构化、检索频繁、数据量大的场景Core Data则在对象图管理上有优势但学习曲线和学习成本都相对高。我当时复盘这类题时总结出一个判断逻辑数据量小且是键值对用NSUserDefaults数据量中等且是自定义对象模型用归档或CodableSwift数据量大且需要复杂查询用SQLite或Core Data。这个判断逻辑在笔试答题时非常实用因为你可以在答案里体现出“选型跟随场景”的工程思维而不是只罗列优缺点。在地图相关的App里历史搜索记录、常用地址这类数据量不大但频繁读写用NSUserDefaults或轻量归档就够了没必要上数据库这也是一个能体现经验的加分表述。4.3 响应链与事件传递hitTest到底怎么工作事件传递是UIKit里比较抽象的一部分但笔试题很喜欢考。hitTest:withEvent:的调用顺序是从前到后遍历子视图返回最后一个包含触摸点的视图。这个机制决定了为什么一个视图被另一个视图遮挡时触摸事件不会传给被遮挡的视图也决定了你可以在父视图的hitTest里做事件拦截把点击从某一块区域强制转发给另一个子控件。我在实际项目里用过一次hitTest是为了实现一个“地图上的透明浮层不阻挡地图拖拽”的需求。默认情况下浮层在主视图层级上会挡住地图的手势但通过在浮层的hitTest里判断触摸点是落在透明区域还是按钮区域就能让透明区域的点击事件穿透到下面的地图视图。这种偏门应用笔试时如果能写出来会让面试官眼前一亮因为说明你不只背了概念还真的用概念解决过问题。4.4 响应链、RunLoop和性能优化牵一发动全身除了事件传递RunLoop也是笔试的高频点。它和响应链、定时器、UI刷新、卡顿检测都有关系。核心考点包括RunLoop的mode有哪几种、NSTimer默认添加到哪个mode、为什么滑动UIScrollView时定时器会暂停、怎么通过监听RunLoop的activity来检测卡顿。这些知识点如果串联起来理解就不需要死记硬背了——RunLoop本质上是让线程在“没有事件时休眠、有事件时处理”的循环mode则是事件源的分组UITrackingRunLoopMode在滚动时会优先处理滚动事件所以默认mode里的timer就被暂停了。我当年的一个体会是把RunLoop和响应链放在一起复习效率特别高。因为它们都是“事件驱动”的典型模型理解了事件是怎么产生的、怎么被分发的、怎么被处理的很多看似零散的知识点就串成了一条线。笔试考单一知识点你背了就会但面试官如果顺着一个概念往深里问比如“为什么列表滚动会卡顿”你就得从CPU占用、RunLoop循环耗时、Autorelease释放时机、图层混合等多个角度来回答这时候模型化理解就派上用场了。5. 从2018到现在的iOS考察内容变化虽然这套题是2018年的但把视角拉回到现在再看你会发现整体考察逻辑没变变的只是具体技术栈和侧重点。这一节我想聊聊这些年iOS面试内容的变化也算给今天还在准备iOS岗位的读者一个参考。5.1 Swift主导下笔试内容的迁移2018年那会儿国内iOS岗位笔试题的主流还是OCSwift被认为是“未来方向”但大部分公司在实际业务里还在用OC维护老项目。今天再去看纯Swift项目已经非常普遍SwiftUI也逐渐成为新业务的首选。所以现在的笔试题里OC的比重明显下降Swift语法、Swift并发async/await、SwiftUI的响应式编程被提上了台面。但有一个点一直没变底层原理的考察越来越深。不管是OC还是Swiftruntime、runloop、内存模型、引用计数这些知识仍然是高频考点。因为这些东西不像某个UI框架那样会过时而是理解iOS一切行为的基础。我在面试候选人的时候如果一个人能清楚说明Swift的struct和class在内存分配上的区别能说明值类型和引用类型对性能的影响我就会觉得他的基础是扎实的因为这类知识只能靠系统学习得来很难靠突击背题混过去。5.2 架构能力与跨端方案成为新考点2018年的笔试题还集中在“能不能写”上这几年越来越多公司开始考“怎么写更好”。组件化、模块化、MVVM、VIPER、路由设计、依赖注入这些架构层面的问题逐渐进入了笔试题和系统设计题的范围。毕竟App业务越来越复杂光会写页面的人已经不够用了。跨端方案也是近几年绕不开的话题。React Native、Flutter、uniapp这些跨端框架在笔试题里也经常被拿来讨论比如跨端和原生通信的原理、性能差异、什么场景适合用跨端。这一块的知识面很重要尤其是像货拉拉这种业务类型复杂的App很可能在部分业务线使用跨端方案来提升迭代效率。如果你对uniapp打包iOS的流程、iOS原生和H5互调的方案都有所了解那么在回答“跨端和原生如何共存”这类问题时会非常有优势。5.3 给当下求职者的建议如果你现在正在准备iOS岗位的秋招我的建议是不要因为技术栈更新就忽略基础也不要因为基础扎实就看不上新框架。最好的状态是你能讲清楚一个iOS功能从业务需求到技术实现的完整链路包括底层原理、架构设计、性能优化和线上问题排查。以定位为例不能只会上代码集成还要能说得清楚定位权限的申请时机、前后台定位的策略、省电优化、定位漂移的处理、地图SDK的内存占用。这种“业务技术”融合的深度才是现在面试官最想看到的。说到底笔试只是门槛真正决定你offer的是你对技术体系的理解边界。6. 一点个人意见复盘完这套卷子我最大的感受是2018年的题放到今天依然有价值。因为那些考点——内存管理、多线程、网络、界面生命周期——是iOS开发里真正“不随时间贬值”的东西。即使你用的语言从OC换到了Swift布局从Auto Layout换到了SwiftUI底层逻辑依然是同一套。如果你还在为这类笔试题发愁我的建议只有一条别刷题海去写代码验证。每遇到一个知识点就写一个小demo去验证它的行为亲手观察一遍内存变化、线程执行顺序、界面布局效果比背一百道题都管用。我自己带新人时也一直坚持这个原则——理解原理比记住结论重要得多。希望这份复盘能帮你在准备iOS岗位笔试的路上少走一点弯路。如果对某个具体考点有疑问欢迎在评论里交流我看到都会回。