ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

爱奇艺iOS笔试真题解析:内存管理、多线程与架构设计

爱奇艺iOS笔试真题解析:内存管理、多线程与架构设计 爱奇艺2018秋季校招iOS工程师第三场这套题我当年完整刷过一遍后来也经常推荐给准备大厂面试的朋友。原因很简单这套笔试题几乎把iOS开发的核心考点都覆盖了——内存管理、多线程、网络、UI优化、架构设计难度梯度也拉得比较开既有送分题也有能让不少人卡半小时的智力型题目。哪怕放到现在回头看iOS技术栈没有根本性变化里面的考点依然是面试高频区值得系统性梳理一遍。这篇文章我会按自己的答题思路把这场校招笔试中最值得关注的几个模块拆开来讲包括每类题目背后的考察意图、标准答题思路、以及我实际作答时的经验。无论你是准备校招的学生还是想查漏补缺的初级工程师这套题都值得认真刷一遍。1. 整体考察思路这套题到底在考什么1.1 题型构成与坑点分布那场笔试的题型大致分四类单选题、多选题、简答题、编程题。前两部分围绕Objective-C语言特性和UIKit常见API展开简答题集中在内存管理、多线程、网络请求这几个方向编程题则是典型的算法题链表、字符串处理出现的概率最高。从题目内容来看出题人想考察的核心能力其实不是“背了多少API”而是三个层面第一基础是否扎实尤其是引用计数、消息传递这类Objective-C底层机制第二有没有生产环境经验比如UITableView卡顿优化、网络层缓存这些只有真正写过项目、踩过坑的人才能答出细节第三算法和逻辑思维是否过关这部分就是编程题的价值。三个层面递进着筛人基础不牢连第二关都摸不到。1.2 时间分配与答题顺序我自己的经验是选择题控制在30分钟内简答题花40到50分钟编程题留足60分钟以上。编程题往往分值最高但很多人上来就死磕编程题结果前面基础题没时间检查丢了稳拿的分。建议的顺序是先快速过一遍选择题遇到拿不准的直接标记跳过不要恋战。简答题优先答自己最有把握的把关键术语写全比如问“weak和assign的区别”就一定要提到“weak修饰的对象在释放后自动置为nil”这个核心点。编程题先审清题目在草稿纸上把思路理顺再动笔后面我会专门讲编程题的答题技巧。2. 内存管理与引用计数iOS面试的第一道门槛2.1 ARC背后的机制不能只知道“自动管理”这场笔试的简答题里果然出现了内存管理相关题目。常见的问法包括“ARC下什么时候需要手动使用autoreleasepool”“__weak和__strong的区别”“循环引用是怎么产生的”。这些问题从表面看是在问语法实际上是在考察你对引用计数的理解深度。ARC的本质是编译器在合适的位置自动插入retain、release、autorelease核心规则其实只有一条每个对象的引用计数加一就必须对应一个减一。你不需要手动写但要能够回答“系统会在哪里插入这些调用”。比如在使用block时block内部如果引用了self编译器会默认对self做一次retain所以要用__weak typeof(self) weakSelf self;来打破这种持有关系。delalloc方法里不要直接调用self的属性访问器。因为delalloc执行时对象已经处于释放阶段如果属性是weak或者unsafe_unretained再通过getter方法访问很可能导致崩溃。我在笔试答案里额外补了这个点面试官追问时还专门问了“为什么delalloc里不能使用self”就是因为属性访问器内部可能会触发KVO或其他消息发送而对象已经不再安全。2.2 循环引用的三类高发场景循环引用是iOS面试必考题这场笔试的简答题里也出现了“请列举循环引用的常见场景及解决办法”。我按照项目中的实际经验归纳了三类场景。第一类是delegate和blockdelegate用weak修饰是基本常识了但要注意有些老代码用assign修饰delegate对象释放后delegate指针不会自动置空访问时就会野指针崩溃这也是问“assign和weak区别”的由来。第二类是NSTimer。把timer强引用给self同时timer的block里又持有self就会形成self - timer - block - self的引用环。解决方式除了__weak外iOS 10之后多了系统级别的block API可以配合[timer invalidate]在合适时机释放。更关键的问题是timer本身会被runloop强持有所以invalidate必须手动调用很多人只在delalloc里写invalidate但delalloc根本不会执行因为循环引用导致self永远释放不了。第三类是block作为属性被self持有同时block内部引用了self的成员变量。编译器对_property这类直接访问是隐式持有self的所以即使写了__weak如果你在block里直接访问下划线变量依然会造成强引用。正确做法是先__weak typeof(self) weakSelf self然后在block里用weakSelf访问。2.3 其他笔试中的高频追问内存部分还常考“autoreleasepool在什么场景下能明显优化内存”。标准答案是在for循环中创建大量临时对象时如果这些对象没被外部持有它们会延迟释放导致峰值内存暴涨。用autoreleasepool包裹循环体可以让临时对象在每次迭代结束时就释放。另外“如何检测内存泄漏”也是常见简答题。除了Xcode自带的Memory Graph和Instruments的Leaks工具外MLeaksFinder这类第三方库的思路也值得提一下在ViewController disappear后延迟检测如果对象没有被释放就弹出提示。答这道题时最好是能带上自己实际使用工具排查过泄漏问题的经历面试官特别吃这一套。3. 多线程与并发从GCD到线程安全3.1 队列与任务死锁是怎么形成的爱奇艺这场笔试的多选题里有一道很经典的题在主队列上执行dispatch_sync会发生什么答案是死锁做这道题时首先要分清楚队列和线程的区别。主队列是串行队列绑定在主线程上dispatch_sync把任务同步提交到主队列意味着当前线程要等待这个任务执行完才继续但主线程正在等这个任务执行形成相互等待最终卡死。类似的还有“dispatch_get_main_queue和dispatch_get_global_queue有什么区别”前者是串行队列后者是并发队列。这道题还可以引申到“如何在子线程中获取主线程的数据”标准做法是用dispatch_async(dispatch_get_main_queue(), ^{})回主线程更新UI。我当时答题时额外补了一个细节就是dispatch_barrier_async的应用场景。多线程同时读一个数据单线程写数据可以用栅栏块保证写入时不会被并发读打断。这个考点在实际项目中特别常见比如读写缓存的线程安全保护。3.2 多线程下的资源竞争问“多线程操作同一个可变数组会有什么问题”时不能只说“崩溃”要解释崩溃的原因数组类不是线程安全的多个线程同时往可变数组里addObject可能触发内部容器的结构不一致导致NSMallocException或访问野指针。解决思路有三个层次。第一种加锁但要注意不能只用synchronized因为它在性能上并不算好更推荐os_unfair_lock或者pthread_mutex。第二种用串行队列做读写隔离结合dispatch_barrier_async实现单写多读。第三种把数据结构改成线程安全的替代品比如atomic属性、dispatch_semaphore控制的信号量设计。笔试里如果出“怎么让多个网络请求都完成后再统一刷新UI”最佳答案是dispatch_group_t。用dispatch_group_enter和dispatch_group_leave配对配合dispatch_group_notify在全部完成后执行回调。注意enter和leave必须一一对应不然group永远等不完。3.3 NSOperation的选择简答题里偶尔会问“什么时候用GCD什么时候用NSOperation”。我的回答思路是GCD更适合轻量级的一次性并发任务API简单、层级低、开销更小NSOperation则适合需要取消、依赖关系、最大并发数控制的场景。比如下载图片时可以用NSOperationQueue设置maxConcurrentOperationCount限制同时下载的数量还可以通过addDependency让某个任务等待前面的任务完成后再执行。从责任划分来看GCD是工具NSOperation是任务管理框架两者不冲突只是面向的使用深度不一样。4. 网络层与数据持久化请求到存储的完整链路4.1 HTTP/HTTPS和三次握手选择题里出现“HTTPS比HTTP多了哪一层”答案是SSL/TLS层。这个考点如果深挖还会问“SSL握手过程”和“对称加密与非对称加密如何配合使用”。我的简化回答是服务器先把证书公钥发给客户端客户端用公钥加密一个随机密钥回传双方后续通信就用这个协商出来的对称密钥加密这样兼顾了非对称加密的安全性交换和对称加密的传输效率。还有一个高频简答题是“什么是DNS解析DNS会被劫持吗怎么防”。DNS解析就是把域名解析成IPHTTP请求之前都要先走这一步。DNS劫持是个很现实的问题尤其是运营商层面可能把域名解析到错误IP。解决方案是直接用IP发起HTTPS请求再用Host字段标记域名或者使用HTTPDNS服务客户端直接通过HTTP接口向DNS服务器查询解析结果绕开系统DNS。4.2 网络层优化与缓存策略笔试中问“移动端网络优化有哪些手段”时尽量往系统化方向答。连接层考虑HTTP/2多路复用、连接复用请求层做请求合并、减小包体缓存层区分不同资源的使用场景图片用磁盘缓存接口数据用内存缓存加本地持久化。缓存这块我也会主动提一下HTTP缓存头。Cache-Control的max-age字段代表缓存有效时间ETag用来做条件请求客户端带上If-None-Match向服务器确认缓存是否过期。服务器返回304时客户端直接读本地缓存这样可以显著减少服务器压力和请求耗时。4.3 本地持久化方案对比笔试里出现“数据持久化有哪几种方式如何选择”也不意外。我一般把选项分成四类NSUserDefaults适合存小规模偏好设置读写频繁但数据量少plist文件适合存结构简单的配置SQLite/FMDB适合存结构化数据并需要条件查询Core Data适合对象图复杂、需要关系映射的场景。对于大型项目我建议以SQLite为主配合WCDB或者FMDB这类封装库性能稳定且可以跨平台。如果业务数据量不大直接用NSUserDefaults和归档也可以但要注意归档NSKeyedArchiver在升级版本时容易出兼容问题需要预留好版本号字段。5. UI与界面性能如何回答“表格卡顿怎么处理”5.1 ViewController生命周期选择题和简答题里都有ViewDidLoad、viewWillAppear、viewDidAppear的调用时机题这类题就是送分题但也最容易答漏。一个VC从创建到展示完整路径是init - loadView - viewDidLoad - viewWillAppear - viewDidAppear。viewDidLoad只调用一次而viewWillAppear每次从其他页面返回时都会调用所以数据刷新逻辑要写在viewWillAppear里而不是viewDidLoad。push和present两个操作的生命周期略有差别present时viewWillDisappear和viewDidDisappear同样会执行但不会调用viewWillAppear。答题时如果能把“从后台进入前台”时的applicationDidBecomeActive也串进来显得你对事件流理解很完整。5.2 UITableView优化清单问“UITableView卡顿怎么优化”算得上大厂面试的必考大题这场笔试的简答题里也出现了。我的回答喜欢用一个清单来展开。cell复用注册classdequeueReusableCellWithIdentifier复用不要在cellForRowAtIndexPath里频繁创建新cell。高度计算不要用heightForRowAtIndexPath里动态算高度尽量缓存高度或者改用Auto Layout的自动估算高度配合estimatedRowHeight减少计算次数。图片异步加载网络图片用SDWebImage或YYKit注意在主线程外解压图片以免在绘制时卡住主线程。避免离屏渲染圆角、阴影这类效果会触发离屏渲染如果有大量cell需要圆角建议直接让设计师出圆角图或者用贝塞尔曲线切图。滑动时减少主线程操作不要在scrollViewDidScroll里做复杂计算和创建对象可以用异步处理或者缓存。5.3 自动布局与离屏渲染笔试中还可能考“Auto Layout为什么会导致性能问题”。原因是系统需要把约束转换成一堆线性方程然后求解frame约束越多求解成本越高。所以即使用了Auto Layout也要注意约束数量不要在cell上堆几十个约束。离屏渲染这个考点我建议结合Instruments的Core Animation调试工具来解释。当设置layer.cornerRadius且masksToBounds YES时系统需要把layer渲染到离屏buffer再合成性能开销很大。替代方案包括使用UIBezierPath绘制圆角、给图片打圆角后再缓存、或者在父视图上叠加覆盖层来实现圆角效果。6. 架构设计与业务分层从MVC到组件化6.1 MVC的职责边界爱奇艺这类大厂非常看重候选人是否有架构意识笔试里出现“MVC架构的优点和缺点”几乎是可以预见的。标准回答是MVC把代码分成Model、View、Controller三层各司其职协同工作。但随着业务变复杂Controller会变得特别臃肿所以出现了MVVM、VIPER等衍生架构。关键是要能说明白MVC在iOS中的具体问题View和ViewController是天然耦合的Controller又承担了网络请求、数据解析、UI更新、事件处理等很多职责导致“Massive View Controller”问题。笔试答案如果只停留在“Model负责数据、View负责显示”这个层面基本拿不到高分。6.2 MVVM引入与双向绑定MVVM的核心思路是把ViewController中的业务逻辑和数据加工流程挪到ViewModel中ViewController瘦身为“绑定器”和“导航器”。ViewModel暴露供View展示的数据对象View通过KVO或RAC监听这些对象的变化自动刷新UI。笔试问“MVVM相比MVC有什么优缺点”时建议从可测试性切入。ViewModel不依赖UIKit所以能直接做单元测试这是MVC很难做到的。但同时也要指出MVVM的坑如果绑定框架使用不当会出现难以排查的隐式调用链调试起来比MVC更痛苦。我在项目中用MVVM最大的体会是需要给ViewModel定清晰的输入输出协议不能把网络回调直接写在View层。6.3 组件化怎么落地如果笔试问“模块化或组件化怎么做”这是一道典型的架构题。我的回答分四步第一按业务拆分模块每个模块一个独立的pod工程第二模块间通过Protocol或中间层通信避免直接import其他模块的头文件第三公共基础组件下沉到底层比如网络层、存储层、工具类第四使用cocoapods管理依赖版本通过私有仓库统一分发。还要补充“组件化带来的问题”比如模块间循环依赖、Podfile维护成本、启动时需要注册路由表等。如果你能说出这些实现后的痛苦反而会让面试官觉得你真正做过组件化落地而不是只会背书。7. 算法与编程题典型的机试环节7.1 链表类题目是高频题型那场笔试的编程题里有一道“反转链表”类的题这类题目基本上是校招算法题的标配。链表题的要点是画图、设指针、边界判断。我以经典的“反转单链表”为例说明答题思路。先把当前节点的next指针指向前一个节点然后依次向后移动。关键是必须用一个临时指针保存下一个节点否则链表就断了。- (ListNode *)reverseList:(ListNode *)head { ListNode *prev nil; ListNode *curr head; while (curr ! nil) { ListNode *nextTemp curr.next; curr.next prev; prev curr; curr nextTemp; } return prev; }笔试时如果思路不清晰先在旁边画一个三节点的链表把指针变化一步步画出来然后再写代码正确率会高很多。7.2 字符串处理与哈希思想字符串处理也是笔试常客比如“判断两个字符串是否是变位词”“找出字符串中第一个只出现一次的字符”。这类题的核心思路是哈希表用空间换时间。一个数组或字典记录每个字符出现的次数然后再次遍历找第一次出现次数为1的字符时间复杂度O(n)。写这类题时要注意字符集问题。如果只包含a-z用一个长度26的数组就行如果包含所有ASCII字符用128位如果是Unicode直接用NSDictionary更稳妥。这种细节在笔试里特容易被测试用例卡住我就是因为没考虑大写字母直接挂过一次。7.3 现场编程的注意事项编程题阅卷时除了看答案对不对还看重代码风格和边界处理。我总结的注意事项有空指针判断函数开头一定要判断入参是否为nil。边界条件链表长度为0或1时要保证代码仍然成立。变量命名不要用a、b、c这类无意义命名用prev、curr、nextTemp这类语义化名称。复杂度说明写完代码后可以顺带写一句时间复杂度、空间复杂度面试官一眼就能看出你有没有算法素养。8. 面试经验与避坑指南8.1 项目经历怎么讲才加分笔试过了之后就是面试环节面试官通常会挑你简历里的一个项目深挖。项目经历的讲法有个经典公式背景 - 难点 - 方案 - 结果。不要只说“我做了个新闻客户端”要说清楚新闻列表页性能优化时遇到了什么问题是卡顿还是崩溃你怎么定位的最后数据提升了多少。推荐准备两个项目一个偏业务功能体现你的产品思维另一个偏技术难点体现你的工程能力。两个项目覆盖的知识面尽量错开比如一个是网络层架构设计一个是UI性能优化。这样面到不同方向的题目时都有实际案例可以拿出来讲。8.2 遇到不会的题怎么办面试官问到你不会的题最忌讳的是瞎编。我建议按这个顺序应对先诚实说“这个知识点我了解不多”然后快速补充“但根据我的理解它可能跟XX有关应该用XX思路去解决”。这样做的好处是既承认了盲区又展示了你分析问题的能力。比如问“为什么block里修改局部变量需要加__block”如果你没复习到这个点可以答“因为block默认会捕获变量的不可变副本所以要加__block让block能够反过来修改外部的变量”。就算表述不精确至少展现了思考过程。8.3 常见审题陷阱笔试中最容易丢分的就是没审清题。比如“下面哪个不是”这类反向选择题很多人一眼看到正确答案就直接选了忽略了否定词。我建议把题干的“不”字圈出来再从后往前看选项。还有“多选”题少选不得分的情况比如“哪些方案能解决循环引用”选项里可能有一个是“将属性声明为copy”这个方案本身没有错但不是解决循环引用的手段选上去就错。遇到这种题先判断每个选项是否直接针对题干问题再判断是否有前提条件导致它不成立宁少勿多。注意爱奇艺的这套笔试题虽然出自2018年但里面的知识点在iOS面试中一直没有过时。建议刷题的模式是先自己独立做一遍再对照解析把每道题关联的知识点整理成自己的笔记。这样比起背十套答案效果要好得多。我个人在实际刷题中的体会是这套题最值得反复琢磨的不是选择题的标准答案而是简答题里那些“为什么”。比如“delegate为什么用weak而不用strong”表面是内存管理背后是对象生命周期和职责边界的设计考量。把这类问题想透面试时才能从一堆候选人里跳出来。最后再分享一个小技巧准备面试时给自己录音把每个技术点用自己的话讲一遍你会发现很多地方你以为懂了实际上根本讲不清楚录下来复盘比对着文档背有用得多。
返回列表