爱奇艺iOS校招笔试复盘:底层原理考点与答题思路
2026/8/31 6:03:11 网站建设 项目流程

去年九月底我投了爱奇艺的iOS校招岗位,收到笔试邮件后点开链接的那一瞬间,说实话有点紧张。整场笔试时长120分钟,题量不算小,选择题、填空题、简答题、编程题都有,而且考察的内容非常集中在iOS开发底层:OC语言特性、Runtime、内存管理、多线程、网络、UI机制。我身边几所学校的同学考完之后交流了一下,大家共同的感受是:爱奇艺的笔试题不走偏门怪招,但很吃基础功底,尤其是源码层面的理解。

这篇文章就结合我参加爱奇艺2020校招iOS方向第一场笔试的回忆,把那些重复出现的考点、容易踩的坑、以及我复盘时整理出的答题思路全部梳理出来。不是官方题解,也不是标准答案,而是一个过来人对校招iOS笔试真实侧重点的还原。无论你是正在备战校招,还是想通过笔试题来查漏补缺,这篇内容应该都能帮你在iOS面试题这条路上少走一点弯路。

1. 第一场笔试的题型分布与考察逻辑

1.1 选择题、填空题与简答题的真实比例体验

整场笔试给我的第一印象是:选择和填空占了接近一半,简答题大概五到六道,最后还有一道算法题和一道iOS相关的编程设计题。选择题不是单纯考概念背诵,而是经常给一段小代码,问你输出是什么、会不会崩溃、引用计数变化是几次。这种题比直接问“copy和strong有什么区别”要难一个层次,因为它要求你能在脑子里执行代码,对内存管理的行为有足够精确的预期。

填空题则更偏重关键术语和源码细节。比如runloop的几种模式名称、autoreleasepool的底层数据结构、GCD中dispatch_barrier_async的作用等等。这些知识点如果只是看过没手写过,很容易卡壳。简答题部分比较典型的有两类:一类是“讲讲你对iOS消息发送机制的理解”,另一类是“如何优化列表滚动的卡顿”。这类题没有标准答案,但能看出你到底是有真实项目经验还是只刷过题库。

1.2 从爱奇艺的业务形态反推笔试考点

爱奇艺的App是典型的视频流场景,播放器、缓存、弹幕、推荐列表、搜索、会员支付这些功能决定了它对iOS工程师有一些特定要求。笔试中反复出现的网络请求、多线程下载、缓存策略、图片加载优化相关题目,背后对应的其实就是播放器缓冲、视频缓存、首页Feed流加载这些真实业务场景。

所以备考的时候不能只凭感觉刷题,一定要结合目标公司的产品形态去猜考点。视频类公司会重点考察网络层如何设计、大文件下载如何做断点续传、视频列表如何保证滑动流畅、弱网环境下如何做重试和超时控制。这些都是爱奇艺这种体量的App在开发中一定会遇到的问题,自然也就容易变成笔试和面试的关注点。我当时就是提前把视频类App常见的性能瓶颈过了一遍,结果简答题里遇到“如何降低离屏渲染对列表性能的影响”时,心里就有谱多了。

2. OC语言底层:属性修饰符、Block与内存管理是必争之地

2.1 copy与strong:为什么NSMutableArray属性用copy会翻车

选择题里有一道印象很深的题:把@property声明为copy,但实际传入一个NSMutableArray,然后往里addObject,问会发生什么。答案是编译能过,运行时会因为“unrecognized selector sent to instance”崩溃。原因在于copy修饰符会调用对象的copy方法,而NSMutableArray的copy返回的是不可变的NSArray。你用NSMutableArray指针接住这个不可变对象,再调用addObject时,方法查找沿着继承链走,最终没法找到对应实现,只好走消息转发,转发也没有处理,于是崩溃。

这个考点考察的不只是“copy和strong哪个更安全”,而是你对深浅拷贝本质的理解。对于一个不可变类型的属性,用copy可以起到保护作用,防止外部传入可变对象后在不知情的情况下被修改。但对于本身就要求可变的属性,用copy会把可变性直接抹掉,反而埋雷。正确的属性修饰符应该是strong。笔试里这种题特别爱出,因为它既能考基础概念,又能考对运行时行为的预判。

我在复盘时把这类题总结成了一个判断方法:先看属性本身是否需要可变,再看外部传入的对象是否可能是可变类型,最后再看copy返回类型和属性声明的类型是否一致。三步走下来,基本不会错。

2.2 Block捕获变量的三种情况与__block的作用

Block相关的题目在笔试里几乎每次都会出现。最常见的是问下面这段代码输出什么:

int a = 10; void (^block)(void) = ^{ NSLog(@"%d", a); }; a = 20; block();

输出是10。因为对于auto局部变量,Block在创建时是值捕获,把a当时的值拷贝进了Block内部。如果你希望Block内部能修改这个外部变量,或者想看到修改后的值,就需要在变量声明前加上__block修饰。加了__block之后,编译器会把变量包装成一个结构体,Block和外部代码通过指针引用同一个结构体,所以能同步修改,输出才是20。

这个机制背后其实涉及栈和堆的区别:不捕获的Block可能是全局Block,捕获了auto变量的Block可能从栈上拷贝到堆上。笔试中偶尔会直接问Block存放在哪种区域,以及copy操作对Block的影响。这类题需要你理解Block的三种类型:_NSConcreteGlobalBlock_NSConcreteStackBlock_NSConcreteMallocBlock。全局Block没有捕获外部状态,存储在全局区,其余情况会先出现在栈上,只有在ARC下被赋值给强引用时才会自动拷贝到堆上。

2.3 循环引用的识别与修复

内存管理这块,除了引用计数和autorelease,最常考的就是循环引用。经典场景是self持有block,block内部又使用了self,导致两者都无法释放。笔试简答题会给一段代码,让你指出问题并提出修复方案。正确的处理方式是在block外部声明一个弱引用:

__weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; [strongSelf doSomething]; };

这里有一个很容易忽略的细节:如果在Block内部只使用weakSelf,当Block执行期间self被提前释放,weakSelf就变成了nil,后续所有调用都不会生效。所以更稳妥的做法是在Block第一行用__strong修饰符把weakSelf重新持有一下,保证这一轮Block执行期间对象不会中途消失。笔试中如果能写出这层考虑,是会加分的,因为这证明你思考过生命周期,而不只是背过“防止循环引用要weak”。

3. Runtime底层:消息机制、isa与KVO的实现原理

3.1 消息发送、动态方法解析与消息转发

Runtime是爱奇艺笔试的另一个重头戏。选择题里经常出现“调用一个不存在的方法时,运行时会发生什么”的变体。完整的流程是这样的:调用方法本质上是向对象发送消息,编译器会把[obj foo]转换成objc_msgSend(obj, @selector(foo))。运行时先通过isa指针找到类对象,再在方法缓存和方法列表中查找方法实现。找不到时,会依次触发resolveInstanceMethodforwardingTargetForSelectormethodSignatureForSelectorforwardInvocation

动态方法解析阶段可以调用class_addMethod给类添加方法实现,这一步常被用来实现较为隐蔽的“动态添加方法”功能。如果解析失败,运行时会给对象一个机会把消息转发给其他对象,也就是forwardingTargetForSelector。如果再失败,就需要完整的方法签名和转发调用,否则直接抛出unrecognized selector异常。

笔试中有一道题很经典:声明了一个@dynamic属性,但没有手动实现setter和getter,调用时会怎样。答案是会在运行时触发动态方法解析,如果你在resolveInstanceMethod里用class_addMethod动态添加了实现,就能正常调用;否则进入消息转发流程。大厂笔试喜欢考这个,因为它能判断你是否真正理解Objective-C的动态性,而不只是停留在语法层面。

3.2 isa指针、类对象与元类

对象、类、元类这三层结构关系也是笔试填空题的高频考点。实例对象的isa指向类对象,类对象的isa指向元类,元类的isa指向根元类,根元类的isa指向自己。类对象里面存储的是实例方法、属性信息,而元类里面存储的是类方法。所以调用一个类方法时,运行时实际是在元类的方法列表中查找。

很多考生容易在这里搞混。有一个简单记忆方法:对象能调用实例方法,类能调用类方法;那么“类”本身作为对象,它的方法列表就应该存在于“类的类”里面,也就是元类。笔试如果给出一个类的继承体系图,让你标出某个isa指向哪一层,用这条逻辑可以快速推出来。另外,class_isMetaClass这个函数偶尔会作为填空考点,用来判断一个Class是不是元类。

3.3 KVO底层实现与常见坑

KVO的底层原理是isa-swizzling。当你对某个属性添加KVO监听时,运行时会在运行时创建一个原类的子类,比如NSKVONotifying_ClassA,然后把对象的isa指针指向这个子类。子类重写了被监听属性的setter方法,在setter内部会调用willChangeValueForKeydidChangeValueForKey,从而触发监听回调。

笔试常挖的坑有两个。第一个坑:KVO触发的条件是属性值发生变化,如果每次setter设置的值都一样,默认不会触发回调,因为系统会做一次oldValue == newValue判断。第二个坑:直接修改对象的成员变量而不走setter,也不会触发KVO。比如在init方法里直接赋值_age = 18,监听方法不会被调用,因为初始化期间本来也不该触发。这个考点能区分你是只会用KVO监听数据,还是能理解它依赖setter这一底层前提。

4. 并发与RunLoop:GCD、线程安全与死锁题

4.1 队列与线程的辨析

并发相关的题目在多线程部分占据很大比重。首先要分清“队列”和“线程”不是一回事。队列是任务调度的结构,线程是真正执行任务的载体。串行队列同一时刻只有一个任务在执行,并发队列同一时刻可以有多个任务在不同线程上并行执行。同步和异步则决定了当前线程是否要等待。

笔试里特别爱考的一个点是:在主队列上调用dispatch_sync会发生什么?答案是一定会死锁。因为主队列是串行队列,当前任务正在主线程执行,你再用同步提交一个任务到同一个串行队列,新任务要等执行完才能返回,而前一个任务又要等同步返回才能继续,两者互相等待,形成死锁。类似的,在任何串行队列内部再向这个队列同步提交任务,都会死锁。这个结论我在刷题时反复记过,考场上很快就选出来了。

4.2 死锁题的快速判断方法

死锁的判断方法其实很简单,我复盘时总结成了一套流程:先看任务是不是提交到同一个串行队列,再看是不是同步提交。如果两个条件同时满足,必然死锁;如果不满足,则不会死锁。还有一种变体是两把锁互相等待,比如线程A持有锁1等待锁2,线程B持有锁2等待锁1,笔试简答题中会出现这种经典的哲学家就餐问题。

答题时最好画一下资源等待图,把线程、锁、队列关系写出来,一目了然。我在简答题里就是这么做的,虽然没有要求画图,但把等待关系说清楚,面试官会认为你思路很清晰。

4.3 RunLoop模式、Source与自动释放池

RunLoop也是笔试中不会缺席的知识点。一个RunLoop可以运行在多种模式下,比如kCFRunLoopDefaultModeUITrackingRunLoopModekCFRunLoopCommonModes。常见面试题是:用NSTimer开启一个不断执行的任务,为什么在滑动TableView的时候Timer不走了?因为滑动时主线程RunLoop切换到了UITrackingRunLoopMode,而Timer默认只注册在DefaultMode下,所以被暂停了。解决办法是把Timer加入NSRunLoopCommonModes,让它在默认模式和跟踪模式下都能运行。

RunLoop和自动释放池的关系也会考。主线程的RunLoop在每次事件循环结束前会自动释放自动释放池,因此在RunLoop循环中创建的大量对象会被及时释放。如果在循环里创建大量临时对象且没有主动加@autoreleasepool,内存峰值会很高。笔试给一段在循环里创建UIImage对象的代码,问如何优化,答案就是在循环体内加自动释放池。

5. UI与性能优化:事件响应、离屏渲染、Auto Layout

5.1 hitTest与事件响应链

UI部分的第一类常考题是事件传递。点击屏幕后,系统通过hitTest:withEvent:找到最合适的视图,然后沿响应链依次处理。笔试会问:子视图超出父视图bounds的部分,点击后还能响应吗?答案是不能。因为hitTest默认会检查触摸点是否在视图的bounds范围内,子视图超出父视图容器的区域,父视图在遍历子视图时就直接排除了,根本不会把点击事件传给超出范围的子视图。不过如果你重写了hitTest,手动判断子视图的frame,是可以让超出部分响应事件的。

响应链顺着视图层级往上走,如果当前视图没有处理事件,事件会传递给nextResponder,一级一级传到视图控制器、父视图,最后到UIApplication。这个知识点很容易和手势识别混在一起。笔试中只要出现点击某个view事件会由谁响应,先画一遍响应链,再考虑手势识别器的优先级,基本不会错。

5.2 离屏渲染的成因与优化

“离屏渲染”几乎是爱奇艺笔试简答题的压轴常客。要理解离屏渲染,得先知道GPU正常情况下会把渲染结果直接绘制到当前屏幕缓冲区,但如果某些效果需要额外的处理,系统会先在另一个缓冲区里完成绘制,再切换回来,这一去一回就是额外的开销。常见触发离屏渲染的操作包括:设置圆角且同时masksToBounds = YES、添加阴影、使用图层蒙版、绘制文字模糊等。

列表滚动卡顿的优化题里,解法通常是:把圆角图片用带圆角的背景图替换,或者用UIBezierPath主动绘制圆角;阴影用shadowPath指定路径,避免系统自动计算;避免在Cell里频繁使用CAShapeLayer做遮罩。我考试时就按这条思路答,先解释离屏渲染是什么,再列优化手段,看起来很完整。

5.3 Auto Layout约束冲突排查

Auto Layout相关的选择题经常给一个模糊的约束场景,让考生判断会不会出现约束冲突,以及如何解决。核心概念是content hugging prioritycompression resistance priority。前者决定视图在约束允许范围内,是倾向于保持内容大小还是被拉伸;后者决定视图会不会被压缩。笔试喜欢出那种“两个Label并排放,其中一个没有设置width约束,为什么另一个被挤掉了”的题。答案通常是后者需要调整compression resistance priority,让它的内容不被压缩。

我在实际项目中遇到类似问题通常直接检查线框和约束优先级,但在笔试里需要在纸上推演。这个题没有捷径,只能把优先级关系背熟:约束优先级高的先满足,抗压缩优先级高于抗拉伸,内容尺寸和约束尺寸冲突时谁优先级低谁让步。

6. 网络层与架构设计:从HTTPS到MVVM

6.1 HTTPS握手过程与证书校验

网络部分爱奇艺笔试出了一道HTTPS握手流程的简答题。相比TCP三次握手,HTTPS多了一层TLS握手。简单说就是客户端发起请求,服务端返回证书,客户端用系统根证书验证服务端证书的合法性,验证通过后双方协商出对称密钥,之后通信都用对称加密。这个过程里,非对称加密用来传输密钥,对称加密用来加密业务数据,各取所长。

爱奇艺这种视频App在处理网络请求时还会关注本地证书校验、中间人攻击防护。笔试问“如何防止抓包”这类问题时,可以从证书固定、双向认证、混淆业务协议这些方向答。但要注意不要跑偏,考官更想看到的是分清传输层安全和应用层加密的区别。

6.2 网络层封装与架构选型

架构题在iOS方向的笔试中越来越常见。爱奇艺这类体量的App,单是一个播放器模块就可能涉及网络层、缓存层、UI层、数据上报模块,所以笔试会问“如何设计一个网络层”。标准回答会把网络层抽象成接口层、请求管理层、数据解析层、缓存层。接口层用协议封装具体API,业务层不直接依赖AFNetworkingNSURLSession,而是面向一个抽象协议,方便做单元测试和以后替换底层实现。

MVVM也是高频词,很多同学会把MVVM和MVC对比来答。MVVM的重点是ViewModel层把ViewController里的业务逻辑抽走,通过数据绑定驱动UI更新,让ViewController变得更薄。笔试中如果问架构选型,最好结合项目说清楚为什么选MVVM而不是MVC。我在答案里列举了播放器页面的例子:播放状态、进度、缓存状态这些数据变化频繁,用MVVM的绑定机制可以避免在Controller里写一堆update方法。

iOS混合开发方案偶尔也会出现在笔试中,比如对Flutter、React Native、原生开发的对比。爱奇艺这类公司有部分页面是跨端方案,所以问你“混合开发如何跟原生通信”也很正常。这个方向不用深入源码,但至少要了解常见的桥接方式和性能差异。

7. 考后复盘:答题节奏、时间分配与我的避坑清单

7.1 时间分配建议

第一场笔试给我的直接教训是:不能在前面选择题上恋战。整套题选择加填空大概40道,如果每题都想得很细,很容易消耗掉一个小时,后面简答题和编程题就会非常紧张。比较合理的时间分配是:选择和填空控制在40分钟以内,不要纠结超过两分钟的题,先标记跳过;简答题每道控制在10分钟左右,重点是写出关键步骤和结论;最后的编程题至少留40分钟,因为算法题要调试,iOS相关的设计题还要画代码结构。

我身边有个朋友就是因为选择题里一道Block捕获题想太久,最后编程题只写了一半。考场上一定要有舍得的勇气,一道选择题再多也就两分,编程题挂了可能整场就悬了。

7.2 错题本的设计:按考点归类的复盘方式

笔试结束后的错题复盘比刷题本身更重要。我没有按题目顺序记错题,而是按考点分类整理成几个大块:内存管理、Runtime、多线程、UI、网络、架构。每道错题都写清楚三件事:我当时的错误答案是什么、正确答案是什么、背后涉及哪几个源码知识点。这样第二轮复习时可以直接翻考点,不用再从头过一千道题。

比如我在Block捕获变量上栽过一次,错题本里就记录了编译器对__block变量包装成结构体的过程,还补充了栈上Block、堆上Block、全局Block的对比表格。笔试题的考点是有边界的,认认真真整理两三套真题,基本就能把iOS面试题的高频范围摸清。

7.3 实际笔试时容易被忽略的隐藏要求

整场笔试里有一些容易踩的细节,在模拟题里很少被强调。第一,编程题不要只写核心逻辑,要补全输入输出和异常处理。爱奇艺这种大厂的线上OJ环境对代码完整性要求很高,函数签名写错或者没处理边界输入,会直接扣大分。第二,简答题尽量分条作答,哪怕时间不够,也要把关键步骤写全。回答“如何优化TableView卡顿”时,我会列成“定位-离屏渲染-图片解码-重用机制-预加载”几条,比写一大段文字更清晰。第三,注意浏览器兼容,有的笔试平台只支持特定浏览器,如果键位冲突或弹出层异常,会影响代码输入。

我这里说的小细节,都是真实参加过笔试之后才会注意到的。很多同学把精力全放在知识点上,反而忽略了答题这个动作本身的技巧。笔试不只是考你会什么,也考你能不能在你规定的时间内把这些东西清楚地表达出来。平时练习时最好用固定时间模拟整套卷子,而不是零散刷题。题不在多,关键是每一道做错的题都能复述出完整的原理链条,考场上遇到任何变形题,你也能从容应对。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询