拓冰建站拓冰建站
首页 / 资讯中心 / 正文

欢聚时代iOS笔试题深度解析:从OC底层到并发编程全考点

2018年秋招欢聚时代那时候大家还习惯叫它YY在成都场放出的这套iOS笔试题在当年校招圈子里算得上是风格比较硬核的一套。它不像某些大厂那样纯靠选择题刷人而是把语言基础、运行时机制、并发编程、手写代码全部揉在一起题量不小时间压力相当大。几年之后回头看这份试卷背后的考点依然很值得拿出来深挖一遍尤其对于正在准备iOS校招、或者刚转iOS开发不久的同学来说这些知识点到现在依然是面试高频区。这篇东西不是简单给一份“真题答案”而是结合我多年做iOS开发和面试别人的经验把试卷背后真正想考察的东西拆开讲透。你如果能理解每个考点背后的“为什么”比背十道答案都值钱。1. 试卷全景速览欢聚时代校招笔试题的定位与结构1.1 欢聚时代2018校招背景与考察倾向欢聚时代当时的核心业务是直播、语音聊天室和游戏相关服务像YY、虎牙直播都在它的体系内。这类业务有一个共同特点强实时、高并发、长时间在线。音视频传输、消息推送、IM 长连接、流量优化这些都是日常要面对的问题。所以它招iOS开发不会只满足于“你会写界面”而是希望候选人对底层机制有真正的理解。放到2018年那个时间点Swift 刚进入 4.0 时代不久Objective-C 依然是很多成熟项目的主力语言。欢聚时代这种体量的公司线上业务代码存量很大新人也很难一上来就脱离 OC 用 Swift 重写。因此这套笔试题在语言层面明显偏向 OCSwift 可能只作为加分项出现甚至整卷都不涉及。从当年的氛围看校招笔试更看重“基础是否牢固、思维是否清晰、代码能不能落笔”。这和社招不太一样社招可以问“你做过什么项目、怎么解决线上问题”但校招生项目经验普遍薄弱所以笔试题目必须落在通用的计算机基础和 iOS 机制上否则没有区分度。1.2 试卷结构推测与时间分配结合当年参加过的同学反馈和行业常见的考察范围这套A卷大概率由三部分组成题型数量考察重点预估分值占比选择题10道左右语言基础、内存管理、并发、网络30%简答题4-6道机制原理、方案设计40%编程题1-2道算法与数据结构、OC代码功底30%整体考试时间一般在 90 到 120 分钟。如果掐表算的话选择题留给 20 到 25 分钟简答题 40 到 50 分钟最后编程题至少留出 25 到 30 分钟。很多同学栽在时间分配上前面选择题犹豫太久导致后面编程题没时间写。这里有个经验如果一道选择题超过两分钟还没思路先标记跳过。笔试的容错率比你想象的低但也不是每道题都值得死磕把后面稳拿的分保住才是关键。1.3 难度评估与区分度这套题的难度放在当年校招里属于中等偏上。它没有太多“偏怪难”的冷门知识点但每一道题都在经典知识点上挖了一个小坑比如 Block 循环引用不止问你“会不会导致泄漏”还会问你“为什么 pop 之后 dealloc 没有被调用”或者“加了 __weak 为什么还不够”。这类题目的区分度在于背过八股文的同学能答出表面答案但只有真正写过、真正调试过循环引用问题的同学才能答出深层原因。面试官想看到的不是标准答案而是你能不能把链路讲完整。另外欢聚时代对 IM 和音视频通信场景格外关注所以网络相关的题目出现频率会偏高。Socket 长连接和 HTTP 短连接的对比、TCP 和 UDP 的选择、弱网下的消息送达方案这些都是可能出现的考点。2. iOS语言基础从OC特性到内存管理2.1 Category与Extension高频送分题也是失分题Category分类和 Extension类扩展几乎是 iOS 笔试的“必考题”原因很简单它最能检验一个候选人到底有没有正经写过 OC。很多人能说“分类可以给已有类添加方法扩展可以添加私有属性和方法”但一到深层区别就露馅了。首先Category 是在运行时把方法注册到类的方法列表中它可以扩展类的行为但不能直接添加实例变量。如果你尝试在分类里声明一个属性编译器会说“property implementation not found”因为分类不会自动生成 _ivar 和对应的 getter/setter你只能通过 associated object 曲线救国。其次Category 的方法会“覆盖”宿主类原有的同名方法但这不是真正的覆盖而是因为运行时查找方法时分类方法在方法列表中排在更靠前的位置。所以你在分类里写了 -viewDidLoad调用时走的是分类版本但原方法并没有消失它只是被“挤到后面”了。这一点很多同学答不上来或者答成“原来的方法不能用了”都不够准确。Extension 则完全不同。它在编译期就能确定所以可以添加实例变量、属性、私有方法并且你必须在 implementation 里实现这些方法否则编译器直接报错。这个“编译期 vs 运行时”的区别往往是简答题的采分点。我当年面试别人的时候如果候选人能主动提到“Category 加载顺序受到的链接顺序影响”或者“多个分类都有同名方法时最终调用哪个取决于编译顺序”那一票基本就给过了。这是测试“是否真的写过复杂项目”的好问题。2.2 Block与循环引用笔试绝对不会缺席Block 的循环引用问题在2018年的校招笔试里基本是必考。典型题目是有一个 ViewController 持有属性 myBlockmyBlock 内部用了 self问会不会循环引用为什么会怎么解决。标准回答是self 持有 myBlockmyBlock 又捕获并持有self形成引用环导致 self 的引用计数永远无法归零dealloc、viewDidDisappear 都不会走。解决办法是使用 __weak typeof(self) weakSelf self; 在 Block 内部用 weakSelf。但这里有个升级考点如果 Block 内部是异步执行的只用 weakSelf 可能遇到执行前 self 已经被释放的问题。所以更严谨的写法是加一个 __strong 的局部变量__weak typeof(self) weakSelf self; self.myBlock ^{ __strong typeof(weakSelf) strongSelf weakSelf; if (strongSelf) { [strongSelf doSomething]; } };这样既避免了循环引用又保证了 Block 在异步执行期间 self 不会被提前释放。这个“weak strong”组合是 iOS 开发中非常成熟也足够经典的写法。还有一个点涉及延时执行时dispatch_after 和 NSTimer 的 Block 版本也会隐式持有 self如果 Timer 没有被及时 invalidate一样会造成泄漏。笔试里出题人会把这些变体融合在一起问你哪一段代码有泄漏风险。平时多写、多调试这种题很难答错。说句题外话2018 年之后 Xcode 对 Block 的循环引用提示已经比较友好了再后来 Swift 里的闭包捕获列表[weak self]也成了标配。但在 OC 时代这个考点真的是区分“背没背过”和“会不会写”的分水岭。2.3 KVC/KVO原理与潜在坑KVCKey-Value Coding和 KVOKey-Value Observing也是简答题常客。KVC 的核心就是 setValue:forKey: 和 valueForKey:它的查找机制很多同学背不全从 getter 方法开始找找不到再看实例变量再找不到才走 valueForUndefinedKey:。KVO 的原理则更值得展开。它是基于 KVC 和运行时机制实现的系统会动态生成一个子类 NSKVONotifying_XXX重写被观察属性的 setter 方法在 setter 里插入 willChangeValueForKey: 和 didChangeValueForKey: 的调用从而通知观察者。这也是为什么 KVO 只能监听 KVC 兼容的键路径也是为什么你需要在 dealloc 里 removeObserver。笔试里常见的坑有两种一是没有在正确时机移除观察者。iOS 11 之前如果观察者已经释放但没移除会直接崩溃。很多同学知道要 removeObserver但放在 dealloc 里也可能出问题因为 dealloc 执行时对象已经处于销毁阶段某些系统属性在此时还会触发 KVO导致崩溃。所以更稳妥的方式是在 viewWillDisappear 或 viewDidDisappear 里移除或者用 block 形式的 KVO 框架来规避。二是 KVO 触发时机。手动触发和自动触发的区别KVC 设置值和直接调用 setter 的触发差异这些都值得整理成笔记。比如连续对同一个属性赋值两次相同的值系统默认不会触发一次因为它会先对比新旧值不相同才发送通知。这些细节笔试范围虽然不一定全覆盖但在面试官追问的时候非常加分。2.4 ARC与内存管理细节ARC 是编译器在编译期自动插入 retain/release/autorelease 代码但它不是万能的。笔试里经常考在 ARC 下什么时候会导致内存泄漏除了循环引用还有 C 语言对象没有手动释放、Core Foundation 对象没有 CFRelease、NSTimer 没有失效、Block 被系统单例持有等。还有经典的 autoreleasepool 考点。for 循环里创建大量临时对象如果不包一层 autoreleasepool峰值内存会非常高。这个问题在 2018 年面试中常问“为什么 cellForRowAtIndexPath 需要在循环里加 autoreleasepool”或者“子线程有没有 RunLoop 和自动释放池”。实际上主线程的 RunLoop 会在每次循环结束时自动释放池而子线程默认没有 autoreleasepool所以子线程里你手动创建的大量临时对象会累积到线程退出才释放这是很多内存异常问题的根源。我记得当时有个同学回答 ARC 时说“ARC 之后我就不用管内存了”这种话一出来面试基本就结束了。ARC 管的是编译器能分析到的对象管不了你持有的系统资源、C 对象和底层指针。3. 并发与网络笔试的重头戏3.1 GCD核心概念与死锁GCDGrand Central Dispatch在 iOS 并发编程中的地位无需多言。笔试常考的点包括队列的种类串行、并行、全局队列、主队列、同步/异步的差别、栅栏函数、信号量、死锁条件。最容易出简答或选择题陷阱的是“在主队列上同步执行会怎样”。标准回答是死锁原因也很清晰主队列是串行队列主线程正在执行当前任务你又往主队列里同步提交了一个新任务这个新任务要等待前面任务执行完才启动而前面的任务是当前正在执行的代码块它又在等待新任务返回互相等待直接卡死。dispatch_sync(dispatch_get_main_queue(), ^{ // 永远不执行崩溃 });这个点哪怕到了现在的面试依然是高频中的高频。你可以直接背结论但如果能画出“队列 线程”的模型图来解释为什么会有循环等待就会比其他人高一个层级。另一个常考的是并行队列 同步操作。并行队列的“并行”指的是任务可以由多个线程同时执行但 dispatch_sync 会把并行队列也变成串行等待。比如你在主线程里对并行队列做同步提交提交的任务会在后台线程执行主线程会等待它完成但不会死锁。这个区别很微妙很多人容易搞混。信号量 dispatch_semaphore_t 也是个高频点它常用于控制并发数量比如同时最多允许 3 个网络请求。典型的写法是用 dispatch_semaphore_wait 在创建任务前减一任务完成后 dispatch_semaphore_signal 加一。2018 年的笔试可能会有“多个请求并发完成后再统一刷新 UI”的题目或者“限制同时下载的文件数量”的题目用信号量可以优雅解决。3.2 进程与线程、线程与RunLoop的关系进程和线程的区别属于计算机基础但 iOS 笔试经常结合场景来出为什么在主线程更新 UI因为 UIKit 不是线程安全的多个线程同时操作 UI 会导致不可预知的绘制问题而且渲染服务器渲染层本身也希望所有 UI 操作都在同一个线程串行执行主线程就是那个唯一合法线程。RunLoop 是 iOS 里一个很容易被忽视但极其重要的机制。笔试里会考RunLoop 有哪些模式Timer 在滑动时为什么可能失效如何解决。这个问题真实场景里很常见你在 ScrollView 滚动时定时器经常不执行因为 RunLoop 切到了 UITrackingRunLoopMode默认的 Timer 在 DefaultMode 下被暂停了。解决方案是把 Timer 添加到 NSRunLoopCommonModes 里或者用 dispatch_source_t 实现定时器或者干脆用 CADisplayLink 按帧回调。2018 年的时候很多人还没有养成用 dispatch source 的习惯但这道题只要答出“RunLoop 模式切换”这个核心原因就足以拿大部分分。线程和 RunLoop 的关系也要理清楚RunLoop 是线程绑定的机制每个线程都有一个关联的 RunLoop 对象但默认情况子线程的 RunLoop 并不会自动运行你需要调用 run 方法才会启动。子线程的 RunLoop 一般用于处理定时器、监听 socket、保持线程存活等场景。3.3 HTTP/TCP/Socket结合欢聚时代的实际业务网络题目在欢聚时代这套卷子里大概率占比不低因为它的业务真的太依赖网络了。直播聊天室需要长连接、礼物消息需要低延迟送达、弱网环境需要重传策略这些都可以包装成笔试简答题。基础题是 HTTP 和 TCP 的关系。HTTP 是应用层协议TCP 是传输层协议HTTP 基于 TCP 承载。再往下拆TCP 的三次握手、四次挥手、为什么挥手要四次这些经典计算机网络题通常会给一两道选择题。拔高题是 Socket 长连接和 HTTP 短连接的差异。HTTP 请求每次都要建立连接、发送请求、接收响应、断开连接头部开销大适合请求-响应模式Socket 长连接是客户端和服务端保持一条持久连接适合服务端主动推送消息、客户端频繁上报的场景比如直播聊天室。如果再深一层还有心跳机制。长连接如果长时间没有数据交互链路可能被中间设备断开所以客户端要定时发送心跳包。笔试可能会问“心跳间隔设置多少合适”“收到心跳回执还继续发吗”这类问题在真实项目里都是踩坑踩出来的。TCP 和 UDP 的对比也是常客。直播场景为什么用 UDP 比较多因为 TCP 有拥塞控制和重传机制在网络差的时候会延迟增加、吞吐量下降而直播追求低延迟能接受少量的丢包所以很多实时音视频传输会用 UDP 上层协议来弥补可靠性。4. UI与架构设计的考察4.1 Auto Layout与UIStackView适配与布局的实现思路UI 题目在 iOS 笔试卷里不会缺席尤其是 Auto Layout。2018 年那会儿iPhone X 刚发布不久刘海屏适配是热点笔试里可能出现“如何让视图在 iPhone X 和普通机型上都表现一致”的问题。这背后其实是在考 safeAreaLayoutGuide 和约束链路的完整性。Auto Layout 的本质是把布局问题转换成线性方程组求解所以容易出现“约束冲突”和“约束缺失”。笔试如果给出一个界面描述让你写出约束或者判断约束冲突原因你必须先理清每条约束的可满足性。比如“一个 UILabel 同时设置 leading、trailing、centerX 三个约束”必冲突因为 leading trailing 已经能确定宽度centerX 又要求中心对齐怎么可能同时满足。UIStackView 虽然是 iOS 9 才有的东西但到了 2018 年已经广泛使用。它解决的问题是避免为每个子视图写大量约束通过 stack 的 axis、distribution、alignment 来控制子视图排布。笔试如果问“UIStackView 的四个 distribution 模式区别”那就是在考你对动态布局的理解。fill、fillEqually、fillProportionally、equalSpacing、equalCentering 每个都对应不同场景面试官问到的时候最好直接答出“我用 fillEqually 做等宽按钮用 equalSpacing 做标签间隔”这类实际案例。4.2 事件传递与响应链UIKit的底层逻辑事件传递是 iOS 笔试里区分度很高的一题。触摸事件发生时系统先通过 hitTest:withEvent: 找到最合适的视图也就是“命中测试”然后再沿着响应链逐级返回处理。这个过程分为两个阶段第一阶段从 UIWindow 开始往下递归调用 hitTest找到被点击的视图第二阶段从被点击视图开始沿着 superview 链向上传递直到有对象能处理。笔试常见考法是有一个父视图 A里面有一个子视图 BB 超出 A 的 bounds 的部分点击不到为什么答案很简单因为 hitTest 默认会先判断触摸点是否在视图的 bounds 内B 超出的部分已经超出 A 的 bounds 范围父视图的 hitTest 认为这个点不属于 A 的可点击范围自然不会继续往下找 B。另一个考点是“如果想让某个视图不响应事件但又不影响手势识别”方案是重写 pointInside:withEvent: 返回 NO或者设置 userInteractionEnabled NO。再深一点如果 A 和 B 都实现了 touchesBegan点击 B 时谁会响应响应链机制会让 B 先处理处理不了或处理完再向上冒泡。这个机制和事件代理、手势识别的优先级也能综合起来出题。4.3 MVC/MVVM架构从代码理解到工程思维笔试简答题里如果出现“谈谈你对 MVC 和 MVVM 的理解”不要只答“模型-视图-控制器”三个词。面试官想听的是你踩过的坑MVC 容易让 Controller 变成“上帝类”所有业务逻辑全堆在 Controller 里导致代码膨胀、难以测试。MVVM 引入 ViewModel把原本属于 Controller 的业务逻辑抽走结合数据绑定机制让 Controller 更薄。2018 年的时候ReactiveCocoa 和 RAC 已经流行过一轮但很多人只是听说过。笔试题如果问到 MVVM你不一定非要写出 RAC 的具体代码但至少要能说出“数据绑定”的思想——视图变更通过 ViewModel 更新模型模型变更反向驱动视图刷新。架构题的评分点往往在于你有没有“自己的判断”。比如你直接批判 MVVM 这套框架学习成本高、调试困难、过度设计并且说明在小型项目里 MVC 反而更简洁这种有立场的答案比无脑吹要好得多。面试官不爱听标准答案爱听真实工程中碰撞出来的权衡结果。5. 手写代码题从思路到实现的完整还原5.1 常见的编程题类型手写代码是整套笔试试卷里最能拉开差距的部分。2018 年校招 iOS 编程题不太会出特别变态的算法题更常见的是能够考察“语言基本功 逻辑能力”的题目。我比较确定会覆盖的几种类型字符串处理比如反转字符串、判断回文串、去重。数组操作如数组去重、找众数、排序。链表基本操作反转链表、删除节点。二叉树遍历前序、中序、后序的递归和迭代写法。这些题目在 LeetCode 上都是 easy 或 medium 难度难点在于你必须在纸上或者纯文本编辑器里写出没有语法错误、边界处理完整的代码还不能跑起来调试。5.2 一个典型题目的完整解法假设题目是“把一个字符串按单词反转比如 hello world 变成 world hello”看起来简单但很多同学连空格处理都能写错。边界条件输入是空字符串、多个连续空格、首尾有空格或者只有一个单词。真正的实现要考虑- (NSString *)reverseWords:(NSString *)input { if (input.length 0) { return ; } NSArray *components [input componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSMutableArray *result [NSMutableArray array]; for (NSString *word in components) { if (word.length 0) { [result insertObject:word atIndex:0]; } } return [result componentsJoinedByString: ]; }这段代码的关键在于用 insertObject:atIndex:0 实现反转同时过滤掉空字符串避免多个连续空格导致结果出现多余空格。如果你再提一句“可以用 NSStringEnumerationByWords 按词遍历”面试官会眼前一亮说明你熟悉 OC 字符串的底层枚举能力。另一个高频题是“链表反转”很多同学用 Swift 写得很顺但一落到 OC 的 next 指针操作就容易把节点搞丢。核心就是三个指针 pre、cur、nextLNode *reverseList(LNode *head) { LNode *pre nil; LNode *cur head; while (cur) { LNode *next cur-next; cur-next pre; pre cur; cur next; } return pre; }每一步都要先保存 next 再改 cur-next否则原链表断裂后就找不到剩下的节点了。这个点写错的人非常多几乎每场笔试都有人挂在指针顺序上。5.3 手写代码的评分要点作为阅卷人我看手写代码主要看三点。第一是边界条件。你有没有判空、是否处理了只有一个元素的情况、循环结束后节点是否正确。这些体现了你的工程习惯而不只是算法能力。第二是变量命名。用 count 就别写 cnt用 index 就别写 i 乱飞。命名混乱的代码读起来太痛苦校招阶段还能忍但我会在心里扣分。第三是复杂度说明。很多人只写代码不写复杂度笔试有时间的话最好在代码末尾加一行注释“时间复杂度 O(n)空间复杂度 O(1)”。这说明你真的理解了自己的代码而不是背下来的。6. 备考建议与踩坑实录6.1 时间分配和答题顺序90 分钟卷子我建议拿到手先花两分钟把所有题目扫一遍确定哪些题完全会、哪些题需要思考、哪些题完全不会。先做会做的把确定能拿的分数拿稳再回来啃难题。编程题如果不会也一定要写思路。哪怕是伪代码、关键步骤说明都比较白卷强。很多阅卷人判卷时更看重“思路是否对路”残缺的代码加上清晰的注释可能比一个完全错误的“完整代码”分数更高。6.2 阅卷视角面试官想看到什么我从面试官角度说点实话校招笔试不是真的指望你答满分而是想通过卷面判断你的学习能力、表达能力、基础扎实程度。所以遇到不会的题不要直接空白。哪怕你只能写出“这个问题我理解是……”也比一字不写强。卷面上那种“这个知识点我还没学到但根据我对 iOS 内存管理的理解它可能和 X 有关”的表达也能传递出你是有逻辑的。另一个同学们经常忽略的是字迹和排版。笔试卷子是人工看同一道题排版清晰的答案和乱写一气的答案印象分差别很大。6.3 我踩过的坑我自己当年也参加过不少校招笔试回头整理一下最典型的坑有三个。第一个是过度纠结选择题的细节比如“NSInteger 在 64 位下占几个字节”这种题就算答对了也就一两分但卡了五分钟后面编程题时间就紧张了。第二个是手写代码时只顾着写主逻辑忘了考虑内存管理和 nil 空串。笔试不要求你把每一行都写成生产级代码但基本防御性编程还是要有的。第三个是简答题里没有层次。一问“如何避免循环引用”先别急着写答案在心里列一个层次什么是循环引用、什么场景触发、怎么解决、为什么需要 weakstrong 双保险。回答时按链路展开阅卷人看着舒服你也拿分。这份卷子的价值不在于它本身有多难而在于它把 iOS 开发者应该掌握的核心知识做了一个非常合理的抽样。如果你能把这篇文章里提到的每个知识点都理清楚、并且能独立写一遍代码那无论是这套题还是类似的校招笔试题你都有把握应对。我自己带过的实习生里凡是能认真把这些基础点过一遍的人入职后成长速度都明显比临时抱佛脚的人快。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门