
爱奇艺2019秋招iOS方向笔试题A这套卷子我到现在还记得打开试题页面时的第一感觉没有一道“背一背就能答”的送分题每一题都在逼你把OC底层机制和实际业务场景串起来。当年一起参加笔试的几个同学考完交流普遍觉得选择题比预想中“刁”简答题又特别贴近视频App的真实问题场景。后来我拿到面试后复盘才发现这套笔试题的核心价值并不在于题目本身而在于它暴露了我基础理解里的很多盲区。如果你正在准备大厂iOS笔试题或者想系统检验一遍自己对Objective-C、内存管理、网络和播放器相关知识的掌握程度这份复盘应该能用上。1. 爱奇艺的笔试风格视频业务如何影响考点分布1.1 为什么网络和性能相关题目占比高先说一个容易被忽略的判断大厂笔试不是随便从题库里抽题题目背后多半有业务逻辑。爱奇艺做长视频iOS端最核心的场景就是播放器、视频列表、弹幕、评论和会员支付这些场景对网络稳定性、弱网表现、列表流畅度、内存占用都有极高要求。所以你在A卷里会看到很多与网络缓存、图片加载、列表优化、播放进度相关的题目这并不奇怪。我那年备考时一开始还在死磕各种偏门的Swift语法后来发现方向偏了。爱奇艺笔试A卷虽然顶着“iOS方向”的名头但考查重点恰恰是那些平时写业务代码时最常用、也最容易想当然的模块Objective-C内存管理、Runtime消息机制、多线程任务管理、HTTP缓存策略。它不需要你背诵某个API的完整签名而是给你一个具体业务场景让你判断这段代码哪里有问题、内存会不会泄漏、界面为什么卡顿。1.2 A卷的常见模块结构从当时整理到的考后反馈来看这份A卷大致分为三个模块。客观题以选择题为主偶尔夹杂判断题内容集中在OC语言基础、属性修饰符、KVC/KVO、容器类的坑以及少量RunLoop概念。简答题则更直接问你某个内存问题的原因、两个多线程方案的对比、block循环引用的解决思路。编程题一般两到三题算法题占大头偶尔会有一道用OC实现的小功能题。我整理了一张做题时可以参考的时间分布表方便你估算每部分投入的比重模块常见题型建议用时主要考点客观题选择、判断15分钟OC语法、内存管理、容器、KVO简答题概念问答25分钟Runtime、RunLoop、多线程、网络缓存编程题手写代码30分钟链表、字符串、哈希表、边界处理整卷总时间一般控制在60到90分钟。这套节奏意味着你几乎没有时间在单个选择题上纠结超过两分钟简答题也不需要长篇大论点到根因即可代码题反而要留够时间。1.3 笔试很少考什么很多人在备考时有个误区以为大厂笔试会考特别前沿的东西。实际上2019年这个时间节点这套A卷几乎不涉及SwiftUI也没有太多纯Swift语法题更不会让你现场设计一套完整架构。它考的是你作为iOS开发者的基本功对象生命周期、内存引用关系、线程任务组织方式、网络请求的缓存策略。我当时有个挺深的体会如果只背面试题而不是真正理解原理遇到稍微变通的选择题就会卡住。比如“weak属性修饰的对象释放后为什么自动变nil”这种题看似简单很多人却答不到Runtime的weak表上。笔试之后我发现越是看似基础的问题越能拉开差距。2. 语言基础题OC对象模型与内存管理的答法2.1 引用计数、copy和weak这几组概念怎么考A卷选择题里属性修饰符是雷打不动的考点。最常见的出题方式是把几种修饰符混在一起问你会不会循环引用、会不会崩溃、会不会改变对象类型。第一个高频陷阱是copy修饰NSMutableArray。很多人想当然地觉得copy就是为了“复制一份防止外部修改”但忽略了copy对可变对象默认返回的是不可变副本。也就是说用copy修饰NSMutableArray属性后真正存进去的是NSArray。如果后面再向这个属性addObject运行时会直接崩溃报unrecognized selector。这个考点考的是“深浅拷贝”和“属性修饰符语义”不是靠记忆能混过去的。第二个高频陷阱是weak与assign的区别。assign修饰对象在释放后指针不会自动置nil继续访问就是悬垂指针而weak会自动置nil。为什么weak能自动置nil因为Runtime内部维护了一张全局的weak表当对象进入dealloc流程时它会根据对象的地址去weak表中找到所有注册过的weak指针把它们的值统一置为nil再把这个对象的weak条目移除。还有一个一定会出现的考点是循环引用。典型问法以下哪种写法一定会造成循环引用A. block内直接调用self方法B. delegate用strong修饰C. NSTimer用block方式并且timer被self强持有时在block里引用selfD. 通知观察者没有移除。这题的关键在于理解内存图只要self和另一个对象互相强引用就会形成retain cycle。B选项里delegate被strong修饰后持有delegate的对象被self持有delegate又反过来持有self必然循环。C选项里NSTimer的target如果被block强持有那timer - block - selfself又持有timer同样是循环。2.2 字符串与容器的隐藏陷阱容器类是笔试中很容易丢分的地方因为很多题看起来“能跑”实际写出来就是崩溃。NSString是一个典型例子。通过直接创建的字符串常量属于__NSCFConstantString存放在常量区不参与常规引用计数管理而通过stringWithFormat创建出来的字符串在堆上内存管理规则和其他对象一样。题目可能会问“为什么用copy修饰NSString属性”却不会问“为什么copy修饰NSMutableString”因为NSString用copy可以避免赋值对象在外部被修改这属于防御性编程。NSArray遇到nil会崩溃因为它把nil当作数组结束标志。所以[obj1, nil, obj2]这种写法运行时是收不到的数组只会在obj1位置停止。同样NSDictionary的key会被copy所以用可变对象做key之后如果改变内容会导致hash值变化取不到原来的值。这个考点在选择题里经常以“哪段代码会出错”的形式出现答的时候要特别小心。2.3 简答题dealloc里到底要注意什么爱奇艺简答题里出现过一类题说说对象析构时需要注意哪些问题。这题看起来开放实际上有明确的踩分点。dealloc里首先要避免访问或调用weak修饰的其他属性。因为对象已经进入析构阶段weak属性背后的对象可能已经提前释放访问它可能拿到脏数据或直接崩溃。其次不要在dealloc里使用setter方法给self的属性赋值因为你不知道这个属性是否被某个KVO观察者监听setter可能触发额外逻辑。第三如果对象持有IO资源、网络连接、大块临时文件句柄应该在dealloc里释放并清理。最后异步操作要小心如果在dealloc里发起一个异步block并捕获self反而会把self保活导致dealloc不一定按预期执行。我建议准备这道题时不要只背答案而是拿一个实际项目里的ViewModel或者播放器对象去模拟一遍析构场景看看哪些资源真正需要手动释放。笔试和面试都会追问“你实际碰到过什么析构问题”能用项目经历佐证就会很有说服力。3. 深度机制题Runtime、RunLoop和多线程的必考问答3.1 Runtime核心消息发送和消息转发Runtime是OC动态性的基础也是A卷简答题的常客。最经典的题是当向一个对象发送它没有实现的方法时系统会发生什么。完整流程是先走正常消息发送调用objc_msgSend在类和父类的方法列表中查找方法实现。找不到时先触发resolveInstanceMethod允许你动态添加方法如果返回NO再走forwardingTargetForSelector允许你把消息转发给另一个对象如果返回nil继续走methodSignatureForSelector和forwardInvocation生成方法签名并转发调用如果最后也没处理就抛出unrecognized selector并崩溃。这个机制笔试时一般不会让你写全部流程但你必须能说清“动态方法解析”和“消息转发”的区别。动态方法解析阶段还能用class_addMethod把方法补上一旦进入forwardInvocation阶段意味着MethodResolution已经失败只能靠转发解决。理解这条链路再去理解Method Swizzling就顺理成章了。Method Swizzling也是常见考点。比如交换viewDidLoad和自定义方法底层是用method_exchangeImplementations交换两个方法的IMP。这里有个小坑交换时要注意两个方法的实现是否都来自同一个类否则可能把父类方法一起交换导致全局行为异常。笔试虽然不要求你手写Swizzling但可能会问“为什么交换方法后调用顺序会变化”答案就是IMP指针交换之后调用原方法名实际执行的是新实现。3.2 RunLoop与界面流畅度视频类App对列表流畅度极其敏感所以RunLoop相关题目在笔试中经常出现。最典型的问法是为什么滑动tableView的时候如果有大量图片加载和处理界面会卡顿。要答好这个问题得理解主线程RunLoop的运行机制。滑动时主线程RunLoop会优先处理UI事件和CADisplayLink回调接着才处理定时器和网络回调。如果你在cellForRowAtIndexPath里做了大量同步计算比如图片解码、动态创建视图、复杂Autolayout计算每滚动一帧都会让RunLoop消耗大量时间在这些任务上下一帧绘制就会被推迟最后表现为掉帧和卡顿。解决方案无非是几条图片解码放到子线程、使用异步绘制、提前计算cell高度并缓存、尽量复用视图、避免在cell内部动态创建大量子view。笔试时你不需要展开太多但最好能点出RunLoop的“beforeWaiting”阶段说明界面绘制是被RunLoop某个阶段阻塞的。能讲到这一层通常就会比只会背“不要把耗时操作放在主线程”的答案高一个档次。3.3 多线程方案对比与合并请求多线程方案的对比题几乎是必考。GCD、NSOperationQueue、NSThread、pthread这四者各有什么优劣势要能说得清楚。GCD基于C语言API轻量高效适合一次性并发任务和简单的依赖关系但取消任务比较麻烦。NSOperationQueue封装了GCD支持取消、设置最大并发数、添加依赖还支持KVO监听任务状态。NSThread更底层需要手动管理生命周期现在业务代码里用得不多。pthread是POSIX线程接口面试时一般只提名字不会深问。A卷里更偏实际应用的是如何同时发起多个网络请求等全部返回后统一刷新UI。常规答法是用dispatch_groupdispatch_group_t group dispatch_group_create(); dispatch_group_enter(group); [self requestA:^{ dispatch_group_leave(group); }]; dispatch_group_enter(group); [self requestB:^{ dispatch_group_leave(group); }]; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 所有请求完成统一刷新UI });这里有个细节必须注意enter和leave一定要成对出现如果某个请求失败的回调里没有leavegroup计数器永远不会归零notify永远不会触发。所以更好的做法是在请求的完成回调里统一调用leave包括失败分支也要调用。笔试时如果能写出“失败分支也要leave”这层考虑得分会明显不同。4. 手写代码题高频算法与OC实现细节4.1 链表反转迭代与递归都要熟练笔试编程题里链表反转出现的概率非常高不只是爱奇艺很多大厂都一样。它考察的不是“会不会背解法”而是“能不能控制好指针边界”。先看迭代写法- (ListNode *)reverseList:(ListNode *)head { ListNode *prev nil; ListNode *cur head; while (cur ! nil) { ListNode *next cur.next; cur.next prev; prev cur; cur next; } return prev; }递归写法- (ListNode *)reverseList:(ListNode *)head { if (head nil || head.next nil) { return head; } ListNode *newHead [self reverseList:head.next]; head.next.next head; head.next nil; return newHead; }我建议两种都要练到能默写因为笔试不一定要求哪种但一定会看你的变量名、空指针判断和代码可读性。递归写法里最容易错的是head.next.next head这一步很多人理解不了为什么反转后新链表的末尾要指向head。画图会好很多先把后面一串反转好然后把原第二个节点指向原头节点再让原头节点的next置nil就能避免环。4.2 字符串题第一个只出现一次的字符字符串题也是笔试常客。我印象比较深的一道是给定一个字符串返回第一个只出现一次的字符。最简单可靠的解法是两次遍历加字典统计- (NSString *)firstUniqChar:(NSString *)str { NSMutableDictionaryNSString *, NSNumber * *countMap [NSMutableDictionary dictionary]; for (NSInteger i 0; i str.length; i) { NSString *ch [str substringWithRange:NSMakeRange(i, 1)]; countMap[ch] (countMap[ch].integerValue 1); } for (NSInteger i 0; i str.length; i) { NSString *ch [str substringWithRange:NSMakeRange(i, 1)]; if (countMap[ch].integerValue 1) { return ch; } } return nil; }这道题有两个容易忽略的点。一是空字符串的返回值通常题目会规定返回nil或空串笔试时最好直接问清楚不问就默认返回nil。二是“第一个”这个约束必须按原字符串顺序遍历第二遍而不是遍历字典否则会乱序。如果题目要求保证字符范围有限比如只包含小写字母可以用数组代替字典性能更高也能体现你对空间复杂度的理解。4.3 哈希表题两数之和如果说链表反转是送分题那两数之和就是“看起来简单写不好就掉分”的题。题目一般长这样给定一个整数数组和一个目标值返回两个数的下标使它们相加等于目标值。经典写法是用哈希表存“数组值 - 下标”- (NSArrayNSNumber * *)twoSum:(NSArrayNSNumber * *)nums target:(NSInteger)target { NSMutableDictionaryNSNumber *, NSNumber * *map [NSMutableDictionary dictionary]; for (NSInteger i 0; i nums.count; i) { NSInteger current nums[i].integerValue; NSInteger complement target - current; NSNumber *complementIndex map[(complement)]; if (complementIndex ! nil) { return [(complementIndex.integerValue), (i)]; } map[(current)] (i); } return []; }这题的核心思路是“一边遍历一边存”不需要先把整个字典建好。笔试时要注意的点如果数组里有两个相同的值后一个会覆盖前一个下标但因为我们是先查后存所以不会影响匹配结果。另外返回值顺序题目可能有要求如果要求下标从小到大需要在返回前做一次比较。4.4 边界条件笔试扣分的隐形杀手代码题最冤的扣分点不是算法不会而是边界没处理。我当时复盘的时候发现很多没通过的代码都是没有处理空数组、字符串长度为0、链表只有一个节点等情况。我的建议是每道题写完都顺手加三行检查输入是否为nil、输入是否为空、输入长度是否为1。链表题检查head是否为nil字符串题检查str.length是否为0数组题检查count是否为0。不要嫌代码啰嗦笔试判题时这些边界恰恰是决定能否通过隐藏用例的关键。5. 场景题网络、播放与App上线相关考点5.1 网络层HTTP缓存与弱网优化爱奇艺这种视频类公司对网络的理解要求很高。笔试简答题里出现过与图片加载缓存、HTTP缓存策略相关的题目。常规考点是ETag、Last-Modified、Cache-Control这些HTTP缓存字段的作用以及SDWebImage这类图片库的二级缓存设计。面试官想看到的是你对“内存缓存磁盘缓存网络请求”三层结构的理解而不是背SDWebImage源码。回答时可以提图片加载先查内存缓存再查磁盘缓存找不到再发请求请求成功后先写磁盘再写内存。弱网处理也是一个常考方向。视频App里网络很差时用户会看到转圈实际上客户端需要做超时控制、重试策略和降级方案。重试不能无脑重试一般用指数退避比如第一次失败等1秒、第二次等2秒、第三次等4秒防止服务端被打爆。A卷如果出这类题通常不要求你写代码但要求你能表达出业务上可控的降级顺序。5.2 播放器与列表体验如何把基础题考出区分度笔试里也有非常贴近业务的题比如“如何设计播放器的缓冲进度展示”或者“如何优化列表滑动卡顿”。播放器缓冲这道题核心是区分播放进度和缓冲进度。播放器底层会通过AVPlayer的timeControlStatus、loadedTimeRanges等状态回调当前进度和缓冲长度。你需要在UI层用两个进度条表示这两个值之间的差距同时处理网络状态变化和拖动进度条之后的seek逻辑。笔试时如果能提到“预加载下一集”“提前在Wi-Fi下缓存视频”会更有优势。列表滑动优化题可以答得更系统耗时的图片解码放到子线程图片请求结果回来后只在主线程刷新对应cell的图片异步绘制cell内容提交到RunLoop的空闲时机固定高度缓存避免重复计算尽量用系统cell样式少嵌套view。能把这些点串起来讲比零散说“用异步加载”“减少约束计算”要有条理得多。5.3 与上线相关的iOS开发常识也别忽视虽然A卷主体是基础题但iOS开发者的日常工作注定绕不开证书、签名、上架这些事。我在备考时看到不少人在讨论iOS开发者证书更新、描述文件、加急审核流程、混合开发打包之类的话题。笔试不一定直接出这些题但如果你应聘的是偏业务或偏上层的岗位面试官可能会顺带问一句你了解过App的上架流程吗这类知识我建议作为日常积累来补开发证书和发布证书的区别、描述文件里包含了什么权限、为什么模拟器能跑但真机装不上、iOS上架审核对IPv6和用户隐私的常规要求。这些问题不需要背得特别细但要能把逻辑说清楚不然面试聊到项目上线阶段时容易露怯。另外一个容易混淆的概念是“iOS模拟器和真机的差异”涉及架构和沙盒文件访问位置笔试偶尔会以小判断题出现提前看一眼总是好的。6. 复盘方法论从笔试A卷到面试的口头输出6.1 时间分配先拿稳程序题再回头啃概念题我后来复盘时发现A卷最合理的顺序不是从头做到尾而是先快速扫一遍编程题。因为编程题是最容易在时间紧张时丢分的而选择题即使不会蒙一个也可能蒙对。我的建议是拿到试卷先看一下编程题有几道如果链表反转、两数之和这类题比较常规就先做如果某道编程题一看就没思路先跳过把选择题和简答题做扎实再回来想。写代码时不要一上来就写先在草稿纸上列一下输入输出、边界情况再用注释写下核心逻辑最后补代码。这样即使代码没写完阅卷人也知道你思路是对的往往能拿部分分。笔试期间没有人给你讲道理卷面上的注释和结构就是你的沟通工具。6.2 建立“考点-错因-同类题”的复盘表笔试结束后最重要的是复盘。我建议用一张表把每道题整理成三列考点是什么、我错在哪里、这类题下次怎么举一反三。题目类型本次错因下一次的应对copy修饰可变数组忽略了不可变副本的语义看到“可变”先判断copy后的实际类型RunLoop卡顿题只答了“不要在主线程耗时”补充RunLoop阶段和异步绘制方案链表反转递归边界写错没有判断单节点画图模拟检查递归出口这张表不是为了好看而是为了把每次笔试都变成一次系统性的知识补全。我当时坚持做了三个月明显感觉到后期做题的正确率在提升因为很多考察点是反复出现的。6.3 笔试里的每一题都可能是面试深挖的引子很多人把笔试和面试割裂开看这是不对的。你笔试里怎么写面试官其实能看到或者说面试官问的问题很大概率是从寒暄和做题痕迹里延伸出来的。比如你笔试里写了线程安全的数组面试官可能会追问你这个锁的粒度会不会太小用NSLock和synchronized的区别是什么你在工作里遇到过真正的数据竞争吗所以准备笔试时最好的心态是“每个知识点都当成面试题来准备”。不要只满足于把代码跑通试着问自己如果面试官让我讲设计思路我能讲出几分钟这个点有没有坑有没有更好的方案当你对知识点的理解达到了能“讲给别人听”的程度笔试和面试都会很稳。我个人的经验是准备这类笔试时不要追求“刷题数量”而是追求“一题多解错因分析”。爱奇艺A卷的考点放在今天看依然有参考价值因为iOS基础没有翻天覆地的变化。把OC内存管理、Runtime、RunLoop、多线程、网络缓存和几道基础算法题吃透遇到大部分视频类大厂的笔试题你都会觉得题目换汤不换药。希望这份复盘能帮你少走弯路。