整理资料的时候翻到当年做过的一套爱奇艺2019秋招iOS方向笔试题A虽然已经过去几年但每次给准备秋招的学弟学妹做模拟的时候还是会拿它当试金石。这套题覆盖的知识点非常典型既有Objective-C和Swift的语言底子也有内存管理、并发编程、网络层设计这些实战内容还有一批故意埋坑的选择题。我当时答完后对完答案发现丢分点根本不是不会而是审题不仔细。后面我把这套题反复研究过几遍结合自己后来在项目里踩过的坑整理出了一套拆解思路。今天把这些内容分享出来希望帮正在准备iOS笔试的同学少走弯路。1. 拿到这套题先看它在考察什么1.1 从A卷看考点地图不只是刷题更是在筛人爱奇艺2019秋招iOS方向笔试题A给我的第一感觉是考点非常“正”几乎没有偏题怪题但很多同学照样拿不到高分。原因很简单它把iOS开发里最常见的知识点做成了各种“变体”你如果只是背过答案没有真正理解原理很容易在选项之间犹豫。从我当时的记忆来看这套卷子大致可以分为选择题、简答题、编程题三大块。选择题覆盖面很广包括语言特性、内存管理、多线程、网络、UI渲染、数据结构。简答题则是让你用文字描述某个机制比如Runloop、事件传递。编程题一般会给你一个场景让你实现一个类或方法重点看代码规范、边界条件和算法复杂度。我把这套题涉及的考点整理成了下面的地图方便对照检查自己的盲区考察维度典型考点出题目的语言基础属性关键字、分类与扩展、Swift可选型、protocol看基本功是否扎实能不能写出规范代码内存管理ARC、循环引用、weak/strong、自动释放池排查真实项目中内存问题的能力并发编程GCD队列、死锁、NSOperation依赖处理多线程任务是否熟练系统机制Runloop、事件响应链、通知是否理解App运行时的底层逻辑网络编程HTTP状态码、HTTPS握手、缓存策略对应视频类App高流量场景的思考架构设计MVC、MVVM、单例能否写出易维护、可扩展的代码结构1.2 为什么爱奇艺要这样出题筛选逻辑藏得很深大部分同学看到这套题时第一反应是“考得太杂了”。但站在招聘方的角度想爱奇艺做的是长视频App日活千万级对iOS端的稳定性、流畅度、耗电控制、弱网处理要求非常高。笔试不可能考察你熟悉哪个第三方库而是要通过通用基础知识来判断你的学习能力和工程素养。所以在选择题里经常出现“以下哪种写法不会导致循环引用”“这段代码在什么情况下会崩溃”这类问题。它考察的不是记忆而是你是否在平时开发中真正遇到过这些坑。简答题里的Runloop、响应链也不是让你背概念而是想看你能否结合具体使用场景讲清楚机制。编程题更是直接模拟日常开发中的小需求看你会不会考虑空值、边界、并发安全。换句话说这套题是在筛选“能实际干活的人”而不是“刷过题的人”。如果你的准备方式只是刷LeetCode和背诵面经会发现选择题部分常常错得莫名其妙。2. 高频知识点拆解语言基础与内存管理2.1 属性关键字、分类与扩展基础题里全是细节语言基础的题目是送分题也是“送命题”。因为每个iOS开发者都见过strong、copy、weak但真正问你“为什么NSMutableArray属性要用copy来修饰”的时候很多人就答不上来了。copy修饰可变类属性的坑如果NSMutableArray属性被copy修饰系统会自动把可变的NSMutableArray对象拷贝成不可变的NSArray。这会导致运行时调用addObject:直接崩溃。所以正确写法应该是用strong来修饰NSMutableArray。但反过来对于NSString这类不可变类用copy可以防止外部传入NSMutableString后内容被篡改。注意笔试里经常反着出题问“以下属性声明哪个是安全的”如果看见property (nonatomic, copy) NSMutableArray *dataArray;要第一时间反应出来这是错误的。分类与扩展也是高频考点。分类可以给已有的类添加方法但不能直接添加成员变量因为类的内存布局在编译时就确定了。如果想在分类里存东西可以用关联对象。扩展则是在编译期直接“合并”到主类中所以扩展里声明的方法可以不必在implementation中单独实现它会直接编译进主类。面试官还喜欢追问“分类和扩展的区别”答题时要从编译期/运行期、能加什么、不能加什么、使用场景几个维度分开说。比如扩展一般用于隐藏私有属性或方法分类用于把功能拆分到不同文件、实现协议方法、给系统类添加工具方法。2.2 内存管理ARC不是万能的循环引用必须会查内存管理在iOS笔试中的权重极高爱奇艺这套题里我记得至少有五六道题和它相关。ARC解决的是手动引用计数的问题它会在编译期自动插入retain/release代码但它解决不了循环引用——两个对象互相持有对方导致引用计数永远不为0内存泄漏。最经典的循环引用有三个来源Blockblock内部捕获了self而self又持有这个block。Delegate用strong声明的delegate导致持有循环。NSTimertarget-strong方式持有的定时器。针对block的循环引用修起来很简单__weak typeof(self) weakSelf self; self.block ^{ __strong typeof(weakSelf) strongSelf weakSelf; if (strongSelf) { [strongSelf doSomething]; } };这里在block内部又加了一个__strong是为了防止在block执行过程中对象被提前释放增加安全性。笔试如果考到你要能说出这一行的意义否则会被认为只是背了模板。delegate在声明时要用weakproperty (nonatomic, weak) idMyDelegate delegate;NSTimer的循环引用稍微隐蔽一点。scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:会把target强引用而self又持有timer必须在viewWillDisappear或者dealloc里调用invalid去断开。2.3 自动释放池与对象生命周期常被忽略的考点自动释放池Autoreleasepool在ARC时代被很多人忽略但笔试里常考“以下代码有没有问题”for (NSInteger i 0; i 100000; i) { autoreleasepool { NSString *str [NSString stringWithFormat:%ld, i]; // 使用str } }如果没有autoreleasepool循环里产生的临时对象会往当前线程的autoreleasepool中压栈等到池子释放时才统一释放内存峰值会很高。加了之后能及时释放。这里要理解ARC下返回值一般总是autorelease的所以处理大批量临时对象时手动创建autoreleasepool是优化内存的有效手段。另外还要会区分“栈上的对象指针”和“堆上的对象”。在Objective-C里[[NSObject alloc] init]返回的对象在堆上ARC负责管理。但在Swift里值类型如struct和引用类型class的区别也经常出现在笔试中。Swift的结构体在栈上传递时拷贝类在堆上传递时引用。回答这类题时要从内存位置、赋值语义、线程安全角度分析。3. 多线程与Runloop笔试里的硬骨头3.1 GCD队列类型与死锁选择题最爱的陷阱多线程几乎是大厂iOS笔试的必考模块爱奇艺这套题也不例外。GCDGrand Central Dispatch考察最多的是队列和任务的组合。你需要分清这些概念串行队列任务按顺序执行。并发队列任务可以同时开始但执行顺序不固定。主队列主线程上的串行队列任务需要等主线程空闲才能执行。全局队列系统提供的并发队列。死锁的经典题是dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(不会执行到这里); });这段代码会死锁。因为主队列是串行队列dispatch_sync提交的block要等主队列前面的任务执行完才会执行而现在主队列正在执行当前代码代码又在等block执行完。互相等待死锁形成。提示如果题目问“这段代码会不会崩溃”答案不是崩溃而是死锁。崩溃和死锁是两回事崩溃是程序直接退出死锁是程序卡住在某个点。类似的嵌套场景dispatch_queue_t queue dispatch_queue_create(com.test.serial, DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ dispatch_sync(queue, ^{ NSLog(这里也不会执行); }); });在同一个串行队列里异步任务内部再同步派发到同一队列也会死锁。如果换成并发队列就没有问题。3.2 NSOperation与GCD选型考察工程决策能力简答题里经常出现“NSOperationQueue和GCD有什么区别各自的使用场景是什么”。这类问题没有唯一答案但你要能说到关键点。NSOperationQueue是建立在GCD之上的高级抽象它引入了任务依赖、取消、最大并发数控制、KVO监听状态这些能力。比如要等三个下载任务都完成后再刷新UI用GCD需要自己用dispatch_group或者信号量来协调而用NSOperation很容易NSOperationQueue *queue [[NSOperationQueue alloc] init]; NSOperation *download1 [NSBlockOperation blockOperationWithBlock:^{ // 下载1 }]; NSOperation *download2 [NSBlockOperation blockOperationWithBlock:^{ // 下载2 }]; NSOperation *refresh [NSBlockOperation blockOperationWithBlock:^{ // 刷新UI }]; [refresh addDependency:download1]; [refresh addDependency:download2]; [queue addOperations:[download1, download2, refresh] waitUntilFinished:NO];代码的可读性比用GCD高很多且取消任务很方便。但是纯轻量级异步GCD更简洁。我的经验是简单任务、一次性任务、全局兜底队列用GCD需要复杂编排、任务状态管理、取消、依赖时用NSOperation。笔试里单独问“你什么时候用NSOperationQueue”答到这个层次基本就过关了。3.3 Runloop不只是“保活线程”那么简单Runloop是iOS笔试里的常客但很多同学只会背概念。问“Runloop是什么”能答出“是事件循环保持线程存活处理事件”。再深一层问“Runloop有哪些模式什么作用”就露馅了。实际答题时建议按下面的结构组织Runloop是一个运行循环每个线程都有一个对应的Runloop主线程的Runloop默认启动。Runloop能监听事件源Source、定时器Timer、观察者Observer。它通过do-while循环不停接收消息并处理没有消息时就进入睡眠节省CPU。结合场景来答如何利用Runloop实现线程保活可以给子线程添加一个NSMachPort作为Source让线程的Runloop持续运转避免线程被释放。如何检测主线程卡顿可以使用CFRunLoopObserver观察主线程Runloop的状态在进入kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间计算时间差超过阈值就是卡顿。另外Runloop和自动释放池也有关系主线程Runloop每跑一圈会创建和销毁一个自动释放池。所以UI事件回调、定时器回调里产生的临时对象都会在这个周期结束时被清理。这个点如果笔试考到是非常加分的。4. 网络与UI架构拉开差距的地方4.1 HTTP与HTTPS从三次握手到证书验证视频类App对网络依赖极重所以网络部分占的分值也不低。选择题喜欢考状态码201创建成功301永久重定向302临时重定向304未修改走缓存401未认证403禁止访问404不存在500服务器错误502网关错误。这些要滚瓜烂熟。HTTPS的握手流程是简答题和选择题交叉的考点。你可以简化成以下几步客户端发送请求带上支持的TLS版本、加密套件、随机数。服务端返回数字证书、随机数、选定的加密方式。客户端验证证书的合法性信任链、域名、有效期。客户端生成一个随机数预主密钥用证书中的公钥加密发送给服务端。服务端用私钥解密得到预主密钥。双方用三个随机数生成对称加密密钥之后通信都用对称加密。这里要注意HTTPS的核心不是全程非对称加密而是通过非对称加密安全地协商出对称密钥再使用对称加密传输数据。理解了这一层遇到“公钥加密、私钥签名”之类的选择题才不会懵。4.2 网络缓存与断点续传视频App绕不开的设计爱奇艺的App涉及视频播放、弹幕、评论、搜索等场景笔试里偶尔会结合业务场景出题比如“如何设计一个合适的缓存策略”。这类题不一定在卷子上但准备时应该掌握。常见思路是分层缓存内存缓存NSCache、磁盘缓存文件、网络缓存策略缓存NSURLRequest的cachePolicy。对于视频数据通常会做分片下载和断点续传。分片下载基于HTTP Range请求头客户端向服务器请求“从第n个字节开始的内容”服务器返回206 Partial Content。这样即使下载中断了下次可以接着下载不用重新开始。如果你在简答题里能写出用setValue:forHTTPHeaderField:拼接Range字段、处理206响应面试官会觉得你有真实项目经验因为这是网上教程很少讲到的细节。4.3 从MVC到MVVM架构设计的考点iOS笔试最后的大题或简答题经常出现“谈谈你对MVC和MVVM的理解”。这也是能体现工程素养的地方。MVC是iOS开发最基础的架构Model表示数据View显示界面Controller管交互和业务逻辑。但实际工程中Controller很容易膨胀被戏称为“Massive View Controller”。MVVM则把Controller中的展示逻辑和业务逻辑抽取到ViewModelView和Model不直接通信通过ViewModel的绑定关系互相协作。在iOS里实现绑定常用有三种方式KVO观察数据的keyPath变化。Block/闭包属性变化后回调。第三方框架如ReactiveCocoa/Combine。笔试如果写代码演示MVVM不一定要求你用某个框架用简单block回调即可class ProfileViewModel { var onDataUpdated: (() - Void)? private var user: User? { didSet { onDataUpdated?() } } func loadUser() { /* fetch */ } }View持有viewModel监听onDataUpdated更新UI。这样Controller只需负责搭建中间桥梁逻辑可以单元测试。4.4 响应链与UIStackView平时用得多但原理容易混事件传递和响应链是简答题常客。题目可能是“点击一个按钮事件的传递过程是怎样的”。答题流程触摸事件发生时UIApplication先收到事件然后发送给UIWindow。hitTest:withEvent:从window开始先看window能否处理、子视图是否包含该点沿着视图树从后往前递归寻找最合适的view。找到后事件从first responder开始沿着响应链nextResponder向上传递直到有对象处理。注意hitTest和touchBegan事件传递方向相反。hitTest是从父视图到子视图找目标事件处理是从目标视图往父视图回溯。另一个容易考的点是UIStackView。它并不是新控件而是基于Auto Layout的容器能自动管理子视图的布局。热搜词里也有“iOS oc uistackview”说明很多人在问。使用UIStackView时要注意arrangedSubviews和subviews的区别要设置axis、spacing、distribution。它的intrinsicContentSize在自适应高度时会带来很多便利但也可能因约束冲突导致布局异常。笔试考到UIStackView时通常不会让你写具体代码而是让你描述它相对手写约束的优势或者判断某个属性是否合理。5. 编程题与简答题的答题策略5.1 编程题先想边界再写代码爱奇艺这类大厂笔试的编程题一般是即时编译的方式环境给你一个初始方法签名让你补全实现。我拿到的A卷里面有一道很典型的字符串处理题类似“反转字符串中的每个单词”。这种题LeetCode上有原题但容易栽在输入为空、字符串首尾有空格、多个空格连续出现这些边界上。我会按三步来写明确输入输出格式。方法签名里可能要求返回NSString *还是返回void并修改原数组。写核心逻辑。先反转整个字符串再逐个反转每个单词。统一处理边界。用trimmingCharacters(in:.whitespacesAndNewlines)去除首尾空格用components(separatedBy:)切分然后再拼接。示例代码Swift风格但笔试常考C/OC难度可以用NSArray处理- (NSString *)reverseWordsInString:(NSString *)input { if (input.length 1) return input; NSArray *words [input componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSMutableArray *nonEmptyWords [NSMutableArray array]; for (NSString *word in words) { if (word.length 0) { [nonEmptyWords addObject:[self reverseString:word]]; } } return [nonEmptyWords componentsJoinedByString: ]; }不要觉得这样写“太简答”笔试现场能一次写出无崩溃风险的代码已经很厉害了。怕的是你在那纠结能不能O(1)空间最后时间不够没写完。先实现再优化这是笔试的生存法则。5.2 简答题结构化表达踩得分点简答题最忌讳写一大段哲学论述。阅卷人是在屏幕上快速扫关键点的你要把术语和逻辑分点列出来。比如“简述Runloop机制”我平时会建议学弟学妹按“是什么-有什么-怎么用”的结构回答是什么Runloop是一个事件循环用来调度事件、定时器和系统消息。有什么有Source0、Source1、Timer、Observer等输入源还有kCFRunLoopDefaultMode、UITrackingRunLoopMode等模式。怎么用线程保活、卡顿检测、自动释放池定期释放。再比如“如何处理TableView滑动卡顿”可以从四个方面说减少Cell的层级和离屏渲染把圆角、阴影合并到图层上。使用异步绘制复杂视图预先渲染成图片。高度缓存避免滑动时重复计算。使用reuseIdentifier复用Cell不在cellForRowAtIndexPath里做耗时操作。这样答阅卷人一眼就能看到你的知识结构分数自然高。5.3 选择题陷阱汇总这些坑我当年都踩过选择题错得最多的其实不是不会而是“半懂不懂”。我把这几年收集到的典型陷阱列一份也当为各位备个参考copy修饰NSMutableArray运行时崩溃。正确用法是strong。在block里直接使用self编译器提醒你捕获self如果不处理循环引用。dispatch_sync到主队列会产生死锁。weak和assign的区别weak在对象释放后自动置nilassign不会会导致悬垂指针指向已被释放的内存。atomic只能保证读写线程安全不保证对象内部可变操作的线程安全。UIViewController的viewDidLoad里修改view的frame可能没有生效因为约束在下一次布局时才更新。scheduledTimerWithTimeInterval在列表滚动时会停止吗默认Runloop在UITrackingMode下定时器会暂停需要用NSRunLoopCommonModes添加。nil、Nil、NULL的区别。nil是OC对象的空指针Nil是类对象的空指针NULL是C指针的空值。使用NSArray的objectAtIndex:越界会崩溃而array[10]在普通集合里也一样。Swift中struct是值类型class是引用类型默认情况下修改struct需要声明为var。这些点结合真实项目会很有画面感。比如滚动列表时定时器停了就是UITrackingRunLoopMode的锅。你可以在回答时主动说出来会显得“做过项目”。6. 秋招备战策略刷题之外还能做什么6.1 构建知识树而不是碎片化刷题很多同学在大三暑假就开始刷LeetCode但iOS方向的笔试题里纯算法占比并不如后端高更看重对苹果框架的理解。建议把iOS知识树分成几大枝干语言基础、内存管理、并发、网络、UI、架构设计、性能优化、项目经验。每天早上花一小时过一遍其中一个枝干问自己“这个知识点我在哪个项目里用到过”如果不记得就去找资料补上。我有个习惯把每个知识点在手机备忘录里写一个“一句话解释”。比如“runloop 线程常驻的循环没事件就睡觉”。这样不仅复习快面试时也能用生活化语言让对方快速理解。比背一堆定理有用得多。6.2 项目经历要学会“造点”和笔试内容挂钩笔试结束后通常有面试面试官会深挖项目。如果项目里没有能体现多线程、内存优化、架构设计的事情就必须主动“造点”。比如你在项目里用到了DispatchGroup同时下载多个图片再拼装这就是一个很好的多线程案例。你可以准备故事线背景是什么用了什么方案遇到什么问题怎么排查。我建议每一个项目至少准备三个这样的“技术亮点”分别对应多线程、内存、架构。面试官问起时就能自然地把笔试里的知识点迁移到项目中显得很有实战意识。6.3 时间规划与几个实用资源备战秋招建议给自己留出三个月第一个月把iOS基础过一遍从头写一个demo不用UI重点练Object-C和Swift互转、JSON解析、懒加载、单例。第二个月专项突破内存和多线程去GitHub找一些大型项目源码看别人怎么定义property、怎么设计网络层。第三个月刷真题和模拟题控制时间每套卷子做完后要对错题整理成文档分析错因。资源方面Apple官方文档是首选虽然英文但术语最准其次是一些高质量的博客比如精选论道、iOS摸鱼周报里面有很多人整理面经。不过要注意辨别“营销号文章”那些只讲“你做了就不会挂”的爽文没有参考价值。写在最后我在整理这套爱奇艺2019秋招iOS方向笔试题A的时候最大的感受是大厂笔试其实是在帮你划重点把iOS开发者最需要掌握的能力一次性列了出来。真正难的并不是某个知识点有多深而是你能不能把知识串起来在限定时间内反映出最优解。如果你现在还没开始准备也不用慌按我上面列的知识树一步步来把每个考点都落到代码里而不是只看不写。等你能把Runloop的底层逻辑、ARC的循环引用、GCD的死锁场景都用自己的话讲给身边人听时再进考场基本就稳了。最后再分享一个小技巧笔试前可以专门花半天看一遍系统框架的头文件尤其是Foundation和UIKit的常用类很多选择题的干扰项就是从这些你“以为会但实际记错”的API里出来的。祝各位都能顺利拿到心仪的offer。