尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

爱奇艺iOS校招笔试复盘:底层原理考点与答题思路

爱奇艺iOS校招笔试复盘:底层原理考点与答题思路 去年九月底我投了爱奇艺的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持有blockblock内部又使用了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指针找到类对象再在方法缓存和方法列表中查找方法实现。找不到时会依次触发resolveInstanceMethod、forwardingTargetForSelector、methodSignatureForSelector和forwardInvocation。动态方法解析阶段可以调用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内部会调用willChangeValueForKey和didChangeValueForKey从而触发监听回调。笔试常挖的坑有两个。第一个坑KVO触发的条件是属性值发生变化如果每次setter设置的值都一样默认不会触发回调因为系统会做一次oldValue newValue判断。第二个坑直接修改对象的成员变量而不走setter也不会触发KVO。比如在init方法里直接赋值_age 18监听方法不会被调用因为初始化期间本来也不该触发。这个考点能区分你是只会用KVO监听数据还是能理解它依赖setter这一底层前提。4. 并发与RunLoopGCD、线程安全与死锁题4.1 队列与线程的辨析并发相关的题目在多线程部分占据很大比重。首先要分清“队列”和“线程”不是一回事。队列是任务调度的结构线程是真正执行任务的载体。串行队列同一时刻只有一个任务在执行并发队列同一时刻可以有多个任务在不同线程上并行执行。同步和异步则决定了当前线程是否要等待。笔试里特别爱考的一个点是在主队列上调用dispatch_sync会发生什么答案是一定会死锁。因为主队列是串行队列当前任务正在主线程执行你再用同步提交一个任务到同一个串行队列新任务要等执行完才能返回而前一个任务又要等同步返回才能继续两者互相等待形成死锁。类似的在任何串行队列内部再向这个队列同步提交任务都会死锁。这个结论我在刷题时反复记过考场上很快就选出来了。4.2 死锁题的快速判断方法死锁的判断方法其实很简单我复盘时总结成了一套流程先看任务是不是提交到同一个串行队列再看是不是同步提交。如果两个条件同时满足必然死锁如果不满足则不会死锁。还有一种变体是两把锁互相等待比如线程A持有锁1等待锁2线程B持有锁2等待锁1笔试简答题中会出现这种经典的哲学家就餐问题。答题时最好画一下资源等待图把线程、锁、队列关系写出来一目了然。我在简答题里就是这么做的虽然没有要求画图但把等待关系说清楚面试官会认为你思路很清晰。4.3 RunLoop模式、Source与自动释放池RunLoop也是笔试中不会缺席的知识点。一个RunLoop可以运行在多种模式下比如kCFRunLoopDefaultMode、UITrackingRunLoopMode、kCFRunLoopCommonModes。常见面试题是用NSTimer开启一个不断执行的任务为什么在滑动TableView的时候Timer不走了因为滑动时主线程RunLoop切换到了UITrackingRunLoopMode而Timer默认只注册在DefaultMode下所以被暂停了。解决办法是把Timer加入NSRunLoopCommonModes让它在默认模式和跟踪模式下都能运行。RunLoop和自动释放池的关系也会考。主线程的RunLoop在每次事件循环结束前会自动释放自动释放池因此在RunLoop循环中创建的大量对象会被及时释放。如果在循环里创建大量临时对象且没有主动加autoreleasepool内存峰值会很高。笔试给一段在循环里创建UIImage对象的代码问如何优化答案就是在循环体内加自动释放池。5. UI与性能优化事件响应、离屏渲染、Auto Layout5.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 priority和compression resistance priority。前者决定视图在约束允许范围内是倾向于保持内容大小还是被拉伸后者决定视图会不会被压缩。笔试喜欢出那种“两个Label并排放其中一个没有设置width约束为什么另一个被挤掉了”的题。答案通常是后者需要调整compression resistance priority让它的内容不被压缩。我在实际项目中遇到类似问题通常直接检查线框和约束优先级但在笔试里需要在纸上推演。这个题没有捷径只能把优先级关系背熟约束优先级高的先满足抗压缩优先级高于抗拉伸内容尺寸和约束尺寸冲突时谁优先级低谁让步。6. 网络层与架构设计从HTTPS到MVVM6.1 HTTPS握手过程与证书校验网络部分爱奇艺笔试出了一道HTTPS握手流程的简答题。相比TCP三次握手HTTPS多了一层TLS握手。简单说就是客户端发起请求服务端返回证书客户端用系统根证书验证服务端证书的合法性验证通过后双方协商出对称密钥之后通信都用对称加密。这个过程里非对称加密用来传输密钥对称加密用来加密业务数据各取所长。爱奇艺这种视频App在处理网络请求时还会关注本地证书校验、中间人攻击防护。笔试问“如何防止抓包”这类问题时可以从证书固定、双向认证、混淆业务协议这些方向答。但要注意不要跑偏考官更想看到的是分清传输层安全和应用层加密的区别。6.2 网络层封装与架构选型架构题在iOS方向的笔试中越来越常见。爱奇艺这类体量的App单是一个播放器模块就可能涉及网络层、缓存层、UI层、数据上报模块所以笔试会问“如何设计一个网络层”。标准回答会把网络层抽象成接口层、请求管理层、数据解析层、缓存层。接口层用协议封装具体API业务层不直接依赖AFNetworking或NSURLSession而是面向一个抽象协议方便做单元测试和以后替换底层实现。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卡顿”时我会列成“定位-离屏渲染-图片解码-重用机制-预加载”几条比写一大段文字更清晰。第三注意浏览器兼容有的笔试平台只支持特定浏览器如果键位冲突或弹出层异常会影响代码输入。我这里说的小细节都是真实参加过笔试之后才会注意到的。很多同学把精力全放在知识点上反而忽略了答题这个动作本身的技巧。笔试不只是考你会什么也考你能不能在你规定的时间内把这些东西清楚地表达出来。平时练习时最好用固定时间模拟整套卷子而不是零散刷题。题不在多关键是每一道做错的题都能复述出完整的原理链条考场上遇到任何变形题你也能从容应对。
返回列表