FEATURED · 精选文章

iOS秋招笔试题深度拆解:从内存管理到架构设计考点全解析

发布时间 / 2026/9/1 21:03:44
来源 / 创域科博编辑部
栏目 / 资讯中心
iOS秋招笔试题深度拆解:从内存管理到架构设计考点全解析 “货拉拉2018秋招iOS工程师笔试题卷二A”——光看这个标题很多准备跳槽的iOS开发可能心里会咯噔一下。货运物流场景下的App开发和一般互联网公司的业务系统相比对实时性、地图交互、弱网处理的要求高出一大截。这套卷子虽然是2018年的秋招题但iOS开发的核心知识点演进速度并没有想象中那么快内存管理、多线程、网络层、架构设计这些根上的东西放到今天依然是面试考察的重点。对于正在准备iOS面试的初级工程师或者想从业务开发转向基础架构方向的人来说拆解这套题背后的考点比单纯刷题更有价值。这篇文章不打算一道题一道题地给标准答案那样没意义。我更想做的是从这套笔试卷子映射出的考察逻辑出发把iOS开发面试中那些高频、深入、容易翻车的知识点摊开揉碎讲清楚原理也讲清楚面试官到底在考察什么。1. 物流货运App的iOS端到底在做什么技术活看这张卷子之前建议先理解货拉拉的业务场景。货运匹配平台的核心链路是用户下单、司机接单、货物运输、在线支付、评价售后。这条链路里iOS端承担的角色远不止一个展示页面那么简单。地图与定位全程轨迹追踪、司机位置实时上报、围栏计算这些功能极度依赖地图SDK和系统定位服务的深度结合。IM与消息推送用户和司机的实时沟通、订单状态变更通知需要WebSocket长连接和APNs推送的双通道保障。弱网容错司机在行驶途中经常经过地下停车场、隧道、偏远路段网络状况极差App必须做好请求重试、数据缓存、离线包管理。性能监控货运场景下司机端长期在前台运行耗电、内存、CPU占用都是关键指标HomeGuard式的后台保活策略虽然敏感但合理的前台性能优化是基本功。理解了这层业务背景再看笔试题你会发现它考察的方向其实和业务高度耦合。面试官想找的不是一个只会写UI的工程师而是一个能在这个复杂场景下独立解决问题的人。所以这套卷子的知识点分布基本围绕语言特性、并发处理、网络知识、架构设计和实战经验这几个维度展开。这也是我在带团队面试时最关注的能力模型基础扎不扎实决定了你在生产环境遇到诡异问题时有没有排查方向。2. 语言基础考点OC特性与内存管理的底层逻辑2.1 属性关键字不只是“背答案”那么简单笔试卷子里几乎必考的一类题就是property的修饰词选择。atomic和nonatomic的区别、assign和weak的区别、copy和strong的区别看起来是送分题但深入问下去很多人会露馅。我见过不少简历上写着“精通iOS开发”的候选人问到atomic时只会说“线程安全”再往深问一层“它保证的是什么样的安全”就答不上来了。atomic的原理是在生成getter/setter时加自旋锁保证的是属性的读写操作原子性也就是你读到的值要么是旧值要么是新值不会读到一个中间状态。但它不能保证对象内部属性的线程安全。比如一个NSMutableArray的count和objectAtIndex:操作即使属性声明为atomic多线程同时读写这个数组依然会崩溃。真正的解决方案是使用pthread_rwlock或者dispatch_queue做数据同步。copy和strong的选择也是高频考点。为什么NSString类型的属性要用copy因为外部可能传入一个NSMutableString如果属性是strong这个可变字符串后续被修改属性指向的对象也跟着变了就会出现数据错乱。copy会生成一个不可变副本把属性与外部对象隔离。同样的道理适用于NSArray和NSDictionary。这里有个小坑自定义对象实现了NSCopying协议时copy修饰符要求这个对象必须正确实现copyWithZone:否则运行时会崩溃。2.2 内存管理从Retain Count到AutoRelease苹果的内存管理从MRC时代演进到ARC时代底层逻辑其实是统一的。ARC在编译期帮我们插入了合适的retain、release、autorelease调用运行时还是靠引用计数在维持对象生命周期。面试里经典的考察点是循环引用。常见的出题场景Block里使用self、NSTimer的target强引用、Delegate的持有关系写错方向。很多人知道Block里用__weak打破循环但有一个隐蔽场景容易忽略——Block被对象强持有Block内部又捕获了这个对象的成员变量即使你用了__weak typeof(self) weakSelf self;如果Block内部访问的是_ivar成员变量系统会直接强引用selfweakSelf失效。// 错误写法即使有weakSelf_name访问仍会强引用self __weak typeof(self) weakSelf self; self.block ^{ NSLog(%, _name); // 显式访问成员变量weakSelf形同虚设 }; // 正确写法先通过weakSelf拿到self再访问属性 __weak typeof(self) weakSelf self; self.block ^{ __strong typeof(weakSelf) strongSelf weakSelf; if (strongSelf) { NSLog(%, strongSelf.name); } };这个__strong修饰符的使用也是加分项。它防止在Block执行过程中对象在中间某个时刻被提前释放保证了执行期间对象的存活。2.3 Category和Extension的底层实现差异分类是OC中一个极具特色的机制。Category可以给现有类添加方法但不能直接添加成员变量。原因是成员变量对应的是类的实例变量布局而分类在运行时是合并进类的方法列表里的并不参与class_ivar的布局调整。面试题里常常问“为什么Category不能添加属性”更准确的说法是“Category可以声明property但不会自动生成成员变量和getter/setter实现”。如果用objc_setAssociatedObject和objc_getAssociatedObject手动实现关联对象就能曲线达成“给分类添加属性”的效果。关联对象的使用有几个注意事项objc_setAssociatedObject的key需要是一个静态指针常量不能直接用字符串字面量否则多个地方用相同的key会互相覆盖。关联对象释放时机是被关联对象dealloc时如果关联策略用了OBJC_ASSOCIATION_RETAIN_NONATOMIC系统会负责释放关联对象但要注意避免在对象释放后被其他线程访问。关联对象不能滥用它走的是全局映射表频繁使用会影响性能。// 给UIView添加一个业务标识属性 static char kBusinessKey; implementation UIView (Business) - (NSString *)businessKey { return objc_getAssociatedObject(self, kBusinessKey); } - (void)setBusinessKey:(NSString *)businessKey { objc_setAssociatedObject(self, kBusinessKey, businessKey, OBJC_ASSOCIATION_COPY_NONATOMIC); } end3. 多线程与并发面试必考的GCD深水区3.1 队列与任务的组合逻辑GCD的核心概念是队列和任务。队列分串行和并发任务分同步和异步组合出来就有四种情况。很多面试题喜欢问“输出顺序是什么”考察的就是对这四种组合的理解。最经典的坑是dispatch_sync和dispatch_get_main_queue的组合。在主线程上执行下面的代码dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(Hello); });这行代码会直接死锁。原因很简单主线程正在执行dispatch_sync同步任务需要立即执行但执行这个任务需要主线程而主线程被dispatch_sync占住了任务永远没有机会执行。类似的死锁场景还有递归锁的使用不当。比如在串行队列里用dispatch_sync向同一个队列提交任务一样会死锁。判断一个GCD调用会不会死锁核心思路是当前任务所在的队列是否和提交任务的队列是同一个队列并且当前任务用的是同步API。理解了这一点大部分死锁问题就能规避。3.2 栅栏方法与读写锁的取舍dispatch_barrier_async是处理多读单写场景的经典方案。读操作可以并发写操作必须独占。用栅栏方法实现能保证写入任务提交之前的所有读任务完成完成之后再执行读任务。- (id)readDataForKey:(NSString *)key { __block id result nil; dispatch_sync(self.concurrentQueue, ^{ result [self.dictionary objectForKey:key]; }); return result; } - (void)writeData:(id)data forKey:(NSString *)key { dispatch_barrier_async(self.concurrentQueue, ^{ [self.dictionary setObject:data forKey:key]; }); }这里面有两个细节值得注意并发队列不要用dispatch_get_global_queue因为全局队列会被其他模块共用栅栏方法就失去意义了。dispatch_barrier_sync和dispatch_barrier_async的区别在于是否阻塞当前线程。写操作如果不需要立即获取结果用async是更好的选择不会阻塞UI线程。不过GCD的栅栏实现也有局限。它只适用于你自己创建的并发队列如果写操作的频率很高性能会下降。更灵活的方案是使用pthread_rwlock_t读锁共享、写锁独占在线程竞争激烈的场景下吞吐量更高。3.3 NSOperation的依赖与取消机制NSOperationQueue是GCD的更高层封装。笔试题里常问它和GCD的区别有一种说法是“NSOperationQueue是GCD的封装”这个表述不太准确。NSOperation和NSOperationQueue是基于GCD实现的但额外提供了依赖关系、最大并发数控制、暂停恢复、取消操作等能力。依赖关系是面试的高频考察点。假设有三个网络请求A获取用户信息B获取用户订单列表C需要同时使用A和B的结果做展示那么C应该依赖A和B都完成。实现方式是NSOperation *aOp [NSBlockOperation blockOperationWithBlock:^{ // 请求A }]; NSOperation *bOp [NSBlockOperation blockOperationWithBlock:^{ // 请求B }]; NSOperation *cOp [NSBlockOperation blockOperationWithBlock:^{ // 并发执行完成后处理A和B的结果 }]; [cOp addDependency:aOp]; [cOp addDependency:bOp]; NSOperationQueue *queue [[NSOperationQueue alloc] init]; [queue addOperations:[aOp, bOp, cOp] waitUntilFinished:NO];有一个容易忽略的问题cOp执行时A和B的任务确实完成了但数据如何传递给C如果直接使用共享的可变字典需要保证线程安全。更优雅的方案是使用NSOperation的子类把上一个操作的输出作为当前操作的输入通过自定义数据属性来传递配合completionBlock做最终处理。取消操作也是容易踩坑的地方。调用[operation cancel]并不会停止正在执行的代码它只是在operation.isCancelled返回YES而已。规范的做法是在执行的Block里手动检查取消状态for (int i 0; i 10000; i) { if (op.isCancelled) { break; } // 每执行一段耗时操作检查一次取消标记 }如果忽略这个检查取消操作就是纸上谈兵任务依然会执行完。4. 网络层与数据安全每一层都在考察什么4.1 TCP三次握手和四次挥手不能只背口诀笔试题几乎必考TCP连接管理。但单纯背出“三次握手、四次挥手”顺序最多拿一半分。面试官通常会追问为什么是三次而不是两次为什么挥手是四次而不是三次三次握手的核心目的是同步序列号确保双方都知道对方的初始序列号。如果只有两次握手服务端无法确认客户端是否收到了自己的SYNACK报文可能导致半连接状态。四次挥手多出来的那一次是因为TCP的支持全双工通信每一方的FIN都只能关闭自己这一侧的传输方向所以需要双方各发一次FIN和ACK。还有一个容易混淆的考点是TIME_WAIT状态。主动关闭连接的一方会进入TIME_WAIT状态持续2MSLMaximum Segment Lifetime报文最大生存时间。这个状态存在的理由有二防止最后一个ACK丢失导致被动关闭方重发FIN而主动方已经关闭了收不到重发的FIN。确保本次连接的所有报文在网络中消失避免复用相同端口和IP的新连接收到旧数据。TIME_WAIT数量过多的问题在生产环境很常见尤其是高并发短连接场景。解决思路包括使用长连接、开启SO_REUSEADDR、调整内核参数net.ipv4.tcp_tw_reuse等。但要注意tcp_tw_reuse只对客户端生效服务端不能依赖它。4.2 HTTPS握手过程中客户端到底校验了什么HTTPS的握手过程是面试的高频题。完整的TLS握手涉及证书验证、密钥交换、对称加密协商三个阶段。对于iOS开发来说重点不只是握手的流程还有客户端如何校验证书。证书校验分为几个层级证书链校验从叶子证书开始逐级向上验证到根证书确保证书链完整。有效期校验SecTrustEvaluateWithError返回true代表证书在有效期内。域名校验证书里的CN或Subject Alternative Name需要和请求的域名匹配。OCSP吊销校验可选但推荐做用来确保证书没有被吊销。iOS 13之后NSURLSession默认要求ATSApp Transport Security通过也就是必须使用HTTPS。开发阶段的临时豁免NSAllowsArbitraryLoads可以打开但上架审核时如果被检测到有被拒的风险。我的建议是开发阶段用NSExceptionDomains给特定域名开白名单而不是全局禁用ATS。有一类面试题会问AFNetworking的证书校验是怎么实现的答案是它封装了AFSecurityPolicy提供三种模式无校验、白名单校验、公钥校验。生产环境推荐公钥校验模式这样即使证书过期或者更换了证书只要公钥不变App依然能够正常工作。但公钥校验的缺点是私钥泄露时整个App的通信防护都失效。4.3 DNS解析的坑从HTTPDNS到Happy Eyeballs物流货运App对网络延迟极度敏感。DNS解析是连接的起点但它也经常成为性能瓶颈。传统DNS有两个问题解析慢UDP的53端口如果被运营商劫持或者缓存污染可能导致连接失败。解析结果不准确CDN调度需要判断用户所在的运营商网络但有些DNS服务器返回的IP不是最优节点。所以现在大厂的应用普遍使用HTTPDNS方案。iOS端接入HTTPDNS通常是在NSURLProtocol里拦截请求把URL的域名替换成HTTPDNS返回的IP同时在Host头字段保留原始域名。这样既绕过了本地DNS又让服务端能够识别域名。这个方案在面试中也经常出现关键是理解它的实现原理而不是只会调SDK。HTTPDNS要做到精确替换需要处理HTTPS下的证书校验问题因为服务端的证书是和域名绑定的IP直连时验证证书会失败。解决方案是自定义SecTrust的校验策略用原始的host去校验证书。5. iOS架构设计笔试中的加分题和团队协作分水岭5.1 MVC、MVVM、MVP到底差在哪架构设计题几乎是高级岗位的必考题。笔试卷子里可能会出现一个场景描述让你设计一个订单详情页面的架构考察的就是你对几种架构模式的理解和取舍。MVCModel-View-Controller在iOS中是最经典的架构但实际开发中容易退化成Massive View Controller。原因在于Controller承载了过多的职责网络请求、数据处理、UI更新、事件响应、页面跳转全部堆在一起一个文件动辄上千行。MVVM的核心改进是把业务逻辑和视图状态绑定拆到了ViewModel层。View只负责展示和发送交互事件ViewModel持有模型数据并暴露给View可绑定的属性View和ViewModel之间通过Block、Delegate或响应式框架如RAC、Combine通信。这样做的好处是ViewModel不依赖UIKit可以纯单元测试。但MVVM也有坑。使用RAC或Combine时数据绑定链如果处理不好会产生调试困难的问题。我的经验是不需要追求100%的绑定在关键数据流上做绑定就行复杂的业务逻辑还是用显式的方法调用更清晰。架构选型没有银弹面试官想听的是你能针对具体场景分析优劣而不是背出某个架构的定义。一个合格的回答应该包含说出当前场景的核心痛点比如页面逻辑复杂、多人协作容易冲突、测试覆盖难。说明你选择的架构如何解决这些痛点。承认这个架构的局限性以及你用什么手段弥补。5.2 组件化改造不是为了炫技是为了解决真实问题谈到iOS架构组件化是一个绕不开的话题。货运业务经过多年迭代App体积变大、模块增多业务线之间的代码耦合越来越严重所以组件化改造在笔试和面试中频繁出现。组件化常用方案有CocoaPods私有库和Lotusoot、CTMediator这类中间件方案。中间件方案的核心是通过运行时发现服务、调用服务解除业务模块之间的直接依赖。比如模块A要调用模块B的某个功能A不直接依赖B的头文件而是通过URL Router或者Protocol匹配的方式找到可以处理这个调用的模块。这套机制在解耦业务线的同时也引入了一些问题动态发现机制依赖编译期注册表调试时如果注册表没刷新会出现“服务找不到”的诡异bug。隐式的调用方式让静态分析和代码跳转变得困难。参数类型不安全所有参数以字典形式传递编译期无法发现类型错误。所以我的建议是小团队、小规模项目不需要一上来就组件化先把模块的接口设计清楚控制好依赖方向比引入复杂的中间件更有效。中大型项目做组件化的目的一定是解决真实存在的协作效率问题而不是因为别人都在做。5.3 启动优化和卡顿优化笔试里的实战题性能优化是笔试卷里最具实战色彩的部分。启动时间、FPS流畅度、内存占用每一个指标背后都有一整套优化方法论。启动优化有两个阶段pre-main阶段和post-main阶段。pre-main阶段主要包括dyld加载动态库、图片资源解压、load方法执行、C全局对象构造。启动优化措施包括减少动态库数量将多个Pod库合并成framework或者静态库。清理用不到的load方法特别是组件化后各模块在load里注册表的逻辑可以改为懒加载注册。把didFinishLaunchingWithOptions里的非关键业务延后到首帧渲染完成后执行。图片资源尽量使用[UIImage imageNamed:]之外的加载方式避免主线程解码。卡顿优化的根因通常是主线程执行了耗时操作。排查手段包括使用Time Profiler、Instruments做采样分析或者通过CADisplayLink监控FPS把卡顿的堆栈上报到平台。常见的卡顿源头无非是同步网络请求、大量正则匹配、字符串拼接、图片解码、复杂布局计算layoutSubviews、constraintWithVisualFormat。每一个都有对应的优化手段但归根结底的原则是——能异步就异步能懒加载就懒加载能缓存就缓存。6. 从笔试题到技术终面那些容易被忽略的隐性考察点6.1 考察的是能力模型不是答案本身很多人以为笔试就是做题背熟知识点就够了。但真正的行家透过笔试题看的是你解决问题时的思维路径。比如一道关于TableView卡顿优化的题候选人的回答可能是“减少cell高度计算、异步加载图片、复用cell”这些是标准答案能拿及格分。但高分答案是进一步说清楚“cell高度的计算逻辑在何时执行有没有可能预计算图片解码是否已经放到子线程解码结果有没有缓存快速滑动时是否复用了cell导致图片错乱是否有根据indexPath做校验”。后者体现的是你在真实项目中踩过坑才能真正关心这些细节。笔试卷里的开放题比如“设计一个日志系统”“说一个你印象最深的线上bug”其实都是给候选人展示自己能力模型的机会。我的建议是写这类题目时不要泛泛而谈采用“背景-动作-结果”的结构这个问题的业务背景是什么我做了什么分析最终怎么解决的效果如何。这个结构也适用于面试时的自我介绍和项目介绍。6.2 竞态条件和线程安全考试套壳的深层考点与iOS直接相关的并发线上问题有一个高频场景是竞态条件。多线程同时读写同一个可变字典、同一个计数器、同一个网络请求回调都容易产生难以复现的闪退。笔试卷子里经常用一个看似简单的例子来考察property (nonatomic, strong) NSMutableDictionary *cache; // 线程A self.cache[key] valueA; // 线程B id value self.cache[otherKey];这段代码在nonatomic修饰下两个线程同时操作字典会出现两种情况读取到未完整的值、或者触发EXC_BAD_ACCESS。即使把属性改成atomic这里的问题依然存在因为atomic只保证属性的setter/getter原子性并不保证NSMutableDictionary的底层容器线程安全。正确做法是把字典接在并发队列后面用读写锁保护。这是面试中的送分题但也是生产环境的高频事故值得重点记忆。6.3 手写代码的常见坑和解题套路大部分iOS笔试包含手写代码环节。核心类型是算法题或手工实现API。这里总结几个容易翻车的点边界条件数组越界、空数组、长度为1的数组。变量命名不要用a、b、tmp这样的命名用有业务含义的名字。时间复杂度别写出O(n^2)的解法还不自知。内存管理手写Block或闭包时记得处理循环引用。考虑线程安全如果题目明确说多线程访问补充锁或队列保护。一个常见的解题思路模板先想清楚输入输出再写核心逻辑最后补边界判断。写完之后主动说出时间和空间复杂度以及潜在优化空间。这些细节在人工阅卷时非常加分。7. 实战复盘一份2018真题卷里的iOS高频知识点速查表既然这是一篇围绕笔试题的深度拆解文章最后整理一份高频考点速查表方便备考的朋友按图索骥。下面这些考点是我根据多套iOS笔试题和多年面试经验归纳出来的涵盖范围比较全面。知识点高频考察形式容易翻车点推荐解决思路属性修饰符选错copy/strong/weak可变对象传入后数据被篡改字符串、数组、字典用copy循环引用Block、NSTimer、Delegate访问成员变量导致weakSelf失效__weak __strong组合关联对象分类添加属性key值冲突使用静态指针常量做keyGCD死锁dispatch_sync提交到主队列代码直接卡死判断队列关系读写锁多线程读写数据并发队列用全局队列自建并发队列barrierNSOperation取消调用cancel后任务仍执行未检查isCancelled循环内手动检查取消标记TCP状态TIME_WAIT和CLOSE_WAIT只背四次挥手顺序结合场景理解HTTPS证书校验ATS、证书链、公钥校验忽略域名校验注意自定义SecTrustHTTPDNS域名替换和Host头HTTPS证书验证失败自定义证书校验策略架构模式MVC/MVVM选型Controller膨胀按业务复杂度合理选型启动优化pre-main耗时load方法过多懒加载注册卡顿优化主线程耗时操作图片解码在主线程异步解码缓存这份速查表的核心价值是帮你快速定位自己的薄弱环节。每一个知识点背后都对应着一个真实业务场景。你能把场景讲清楚比单纯记住答案更有说服力。如果你正在准备iOS面试我的建议是不要只刷题把每个知识点放到一个具体的业务场景里想清楚“这个技术到底解决什么问题、不用的后果是什么”。这样做的好处是双重的面试时能言之有物入职后也能更快地上手解决实际问题。我个人带团队的经验是笔试能反映候选人的知识广度但真正决定能不能干活的是看他遇到具体问题时的排查思路。比如一个线上卡顿问题他是直接猜、肉眼盯代码还是用Instruments采样、用火焰图定位这两种思维方式在笔试开放题中会体现得非常明显。写这套卷子的解题反思时不妨多问自己几个“为什么”把每个考点背后的原理吃透这样不管面试官从哪个角度切入你都能接得上话。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻