ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2018欢聚时代iOS笔试题B卷复盘:底层原理与实战考点解析

2018欢聚时代iOS笔试题B卷复盘:底层原理与实战考点解析 那几年直播行业正是风头最劲的时候欢聚时代手握YY直播和虎牙两块牌在校园招聘里对iOS方向的要求一直偏向“真能上手干活”的人。当时我拿到2018校招笔试题-IOS B卷第一感受是这卷子不像在考刷题量更像在考你平时写代码时有没有想过“为什么”。整个卷子覆盖的语言底层、内存机制、并发模型和UI渲染这些点哪怕放到今天拿出来逐题复盘依然能对应上日常开发里最容易踩坑的地方。这篇内容就围绕这份B卷重新过一遍把每类题背后的考点、解题思路以及展开出去值得深入的知识全部捋清楚。1. B卷整体考察逻辑不是背题而是看技术体系是否完整1.1 题型分布与分值风格先还原一下B卷的大致结构整套题以选择题、填空题、简答题和编程题混合构成。选择题和填空题基本覆盖OC语言细节、内存管理、Runtime、多线程、UIKit机制这些基础方向简答题里有一两道明确结合直播场景的设计类问题编程题则是常规的算法加手写代码。这里有一个值得应届生注意的信息B卷和A卷最大的区别并不在难度梯度而在简答题的场景偏重。A卷会更偏向通用App方向B卷里则出现了直播间的消息通道设计、弱网下的表现优化这类与欢聚时代业务强绑定的题目。也就是说出题人并不是随机抽考点而是拿着自己业务里真实遇到的难题来筛人。搞清楚这一点备考的方向就会从“海量刷题”转为“围绕业务场景做技术延伸”。1.2 拿到卷子以后先做什么我当时的策略是先把所有题通读一遍标出自己确定能拿分的、需要犹豫的、完全没思路的。这一招在时间紧张的笔试里非常关键因为选择题里经常出现“以下说法正确的是”这类需要逐项对比的题如果一开始就死磕某一道往往会挤占后面简答题的展开时间。另外要注意这类笔试题里选择题的选项经常故意设计成“两两相反”比如一个选项说“Block会默认强引用外部变量”另一个说“Block会默认弱引用外部变量”。这种设计在iOS方向的笔试题里出现频率极高核心目的就是考察你对机制细节记忆的准确度而不是靠模糊印象猜答案。通读试卷后我给自己定的做题顺序是先做填空和选择再做编程题最后处理简答题。原因很简单选择题的正确答案能帮你在短时间内回顾知识点为后面的简答题组织语言提供铺垫编程题需要清晰的思路趁头脑最清醒时解决最合适简答题虽然分值高但更看重答题逻辑放最后反而是优势。2. 语言基础与Runtime选择题题目千变万化底层原理不变2.1 OC对象本质与isa指针的考法B卷选择题里有一道关于[obj class]返回结果和obj-isa指向关系的题。这种题表面在考消息机制实际是在考你对OC对象内存布局的理解。一个OC对象在内存里本质是一个结构体指针结构体的第一个成员是isa。在早期的OC Runtime里isa直接指向类对象后来为了优化引入了Non-pointer isa用位域的方式把引用计数、weak标记等状态压缩到了isa的额外位段中。理解到这一层遇到“对象是否已经释放”这类偏门选项时你就知道它想考察的是weak表的工作原理而不是简简单单的判空。在这个知识点上我的建议是不要死记结论而是要在Xcode里实际写一段代码打断点用lldb看object-isa在对象创建、释放、关联对象前后的变化。笔试前不看这类底层细节光靠背题很容易在“以下哪种方式能获取到当前对象的类”这种变体题上翻车。2.2 消息发送、消息转发与动态方法解析B卷简答题中有一道“说说OC中方法调用的完整流程”这基本是iOS笔试题里的常青树。完整的答案应该有三个阶段第一阶段是消息发送即通过isa找到类对象再沿着继承链查找方法列表找到就绑定IMP第二阶段是动态方法解析允许你在运行时用resolveInstanceMethod:给类动态添加方法第三阶段是消息转发通过forwardingTargetForSelector:把消息转给其他对象或者走完整的methodSignatureForSelector:加forwardInvocation:流程。这道题想拿高分不能只写“方法列表查找”这种一句话答案。你需要把三个阶段串联起来并说明每个阶段触发的时机和实际用途。比如动态方法解析常用于实现dynamic属性消息转发常用于代理模式、多继承模拟等场景。我记得自己在答题时把三个阶段画成了一行流程objc_msgSend→getMethod→resolveInstanceMethod→forwardingTargetForSelector→forwardInvocation→doesNotRecognizeSelector。这个流程如果能写清楚面试官一般会认为你是真正使用过Runtime的。2.3 Category能否添加属性与关联对象这道题属于“标准陷阱题”因为很多人会把“能添加方法”和“能添加属性”混为一谈。正确答案是Category可以声明属性但不会自动生成成员变量和getter/setter真正运行时的属性关联要靠objc_setAssociatedObject。关联对象这题在B卷里的变体是“Category里定义的属性能不能用点语法访问”。如果你直接答“不能”也不完全对因为你可以手动实现getter和setter用关联对象存储数据。我当初回答时做了两个层面的区分编译期Category不会把属性放进类的成员变量列表运行期通过关联对象可以模拟出属性的存储效果。这种分层表述会让阅卷人觉得你有分析问题的框架感。2.4 KVO与KVC的实现机制KVO在这份卷子里也是高频考点而且常常结合“手动触发”来出题。很多人只知道注册观察者、实现回调却不知道KVO底层是通过动态生成子类并重写setter实现的。具体过程是addObserver时Runtime会创建一个名为NSKVONotifying_xxx的子类重写被观察属性的setter方法在setter里插入willChangeValueForKey和didChangeValueForKey的调用。正因为有这个动态子类机制KVO才有了“自动触发”和“手动触发”的区别。手动触发需要你显式调用那对will/did方法而且需要配合automaticallyNotifiesObserversForKey返回NO来关闭自动触发。这道题的坑在于很多人不知道类对象会在KVO过程中被替换所以遇到“KVO之后调用class方法返回什么”这类题就容易答错。3. 内存管理与循环引用笔试中最容易拉开分差的部分3.1 ARC与MRC的题目套路的底层逻辑B卷里关于内存管理的题目常见出题方式是这样的给你一段MRC时代风格的代码问你是否存在内存泄漏。很多应届生看到retain和release就蒙了因为平时开发基本全是ARC。但这类题目考察的不是你是否用过MRC而是你是否理解引用计数这种内存管理模型。理解引用计数的关键是抓住两条规则第一谁持有谁负责释放第二从alloc/new/copy/mutableCopy开头的命名方法取得的对象归调用者所有其他方法取得的对象是自动释放的。将这两条规则套到ARC时代其实就是编译器自动帮你插入了retain/release/autorelease而weak则是不增加引用计数的持有关系。在答题时我建议不要直接写结论而是把对象的引用计数变化列出来。比如“将对象加入数组引用计数从1变为2数组释放后变为1外部再置nil后变为0对象释放”这种分步表述会让答案更有说服力。3.2 Block循环引用的三个高频场景B卷中循环引用相关题目出现的频率非常高因为它是实际项目里最容易引发内存泄漏、同时笔试又方便出成场景题的知识点。三个高频场景是Block被当前控制器持有、Block内部引用了self、Block内部引用了成员变量。要真正理解循环引用不要只看“Block会捕获self”这个结论。Block在MRC时代是可能放在栈上的栈上的Block不会循环引用因为在栈上时它不持有捕获的对象但ARC下Block会默认被拷贝到堆上堆上的Block会对捕获的OC对象做强引用于是循环引用形成。答题时如果能说明“在MRC中Block对捕获变量的引用不同而在ARC下需要__weak来打破循环”会让阅卷人觉得你是有兼容思维的。到今天的Swift环境下这个考点对应的就是捕获列表里的[weak self]原理是一致的。3.3__weak与__strong的配合用意有一类拔高题会问到“在Block内部使用__weak之后还需要__strong吗”。这是一个特别容易被忽略又经常在笔试简答题中考查的点。答案是需要但要看场景。当Block内部需要多次访问weakSelf时如果直接用__weak变量对象的释放时机会在多次访问之间变得不可控。更常见的做法是在Block开头先声明一个__strong局部变量将weakSelf赋值给它后续统一用这个强引用。这样可以确保在Block生命周期内对象不会被提前释放同时不造成循环引用因为它是局部变量Block执行完毕就会释放。这种写法在实际项目中几乎随处可见笔试出这题的目的就是看候选人有没有处理过真实场景下的内存问题而不是只会背weakSelf。3.4 copy修饰符与深浅拷贝B卷的选择题里还有一道关于NSString和NSMutableString用copy与strong修饰的题。标准答案是不可变字符串建议使用copy可变字符串使用strong。原因在于如果你用strong修饰一个NSString但实际赋值来源是NSMutableString后续这个可变对象被修改时你的属性也会跟着变导致数据异常。深浅拷贝的区分也要分集合类和非集合类来记。对非集合类copy得到不可变副本mutableCopy得到可变副本对集合类copy默认是浅拷贝只拷贝容器本身元素对象的引用计数不会变化。笔试里经常给一个数组套数组的例子问copyItems:YES时内层数组是否被深拷贝答案是只对一层生效深层依然是浅拷贝需要嵌套实现深拷贝。4. 多线程与网络直播场景下的硬核考点4.1 GCD队列任务组合与死锁分析B卷的并发题很喜欢出一个经典死锁在主队列调用dispatch_sync提交一个任务到主队列。结果是程序死锁因为dispatch_sync会阻塞当前线程并等待目标任务执行而目标任务又被排在主队列里需要等主线程处理完当前任务两个任务互相等待。回答这类题关键是把队列的层级关系理清楚。队列分串行队列和并发队列同步提交dispatch_sync会阻塞当前线程异步提交dispatch_async不会。是否死锁取决于提交方式和目标队列的组合主队列同步提交任务到主队列死锁。串行队列同步提交任务到同一个串行队列死锁。并发队列同步提交不会死锁但可能会阻塞当前线程。任何队列异步提交不会因为队列等待产生死锁。记住这些组合还不够你要能说出死锁的本质是“队列等待”还是“线程等待”。系统底层是用线程池来调度GCD的同步提交的本质是当前线程在等待队列完成工作而队列又在等待当前线程让出这样互相等待才会死锁。4.2 NSOperationQueue与GCD的差异和选型B卷有一道简答是“NSOperationQueue相比GCD有哪些优势如何选择”。这题如果在拼多多式的答案里只写“GCD更快NSOperation能做依赖关系”大概率会被认为是记忆模板而不是理解。两者真正的本质区别是NSOperationQueue是面向任务的抽象层dispatch_queue是面向底层调度的C接口。GCD更轻量、更底层适合简单的异步块执行NSOperationQueue则支持取消、设置依赖关系、控制最大并发数、KVO监听任务状态、指定任务优先级这些高级操作。在直播场景里一个常见的设计是用NSOperationQueue来管理图片上传队列因为上传任务需要依赖图片压缩前置任务完成而且当用户退出直播间时需要可以整体取消上传。这种场景用GCD裸写会非常复杂但用NSOperationQueue只需要设置依赖和调用cancelAllOperations。答题时带上这类具体业务场景比单纯罗列API更能体现你的架构判断力。4.3 从三次握手到HTTPS与弱网优化这一块属于网络基础但B卷把它和直播连线场景绑定要求说明如何优化弱网下的连接质量。这里有个容易忽略的点TCP的三次握手和四次挥手不只是面试八股它直接决定了直播App在弱网下的连接耗时。如果主播和观众之间跨地域完全依赖TCP的握手和慢启动首帧延迟会非常明显。方案的常见组合是接入层使用QUIC或HTTP/3替代纯TCP连接应用层采用长连接加心跳保活业务层在首帧下发时只推送必要信息、减小TCP报文窗口压力。同时在弱网丢包率高的场景下需要区分“拥塞丢包”和“链路错包”通过丢包重传和冗余编码两种方式结合来保障音视频流的连续性。我建议在答这类题时把优化手段分成连接层、传输层、应用层三个维度去讲这样每个优化点都有明确的归位条理清晰也不容易漏点。5. UI与渲染机制从Auto Layout到TableView流畅度5.1 Auto Layout是面试高频也是性能瓶颈点B卷简答题里有一道“Auto Layout的布局过程及性能优化”看起来很常规但想拿高分需要回答到Cassowary算法和视图渲染的两阶段流程。Auto Layout本质是把约束转成线性方程用Cassowary算法求最优解布局结果是frame的Set。对性能的理解不能停留在“约束多就慢”这种直觉层面笔试要能写出Auto Layout和frame计算相比多了一步求解方程的过程因此在滚动视图中频繁更新约束会带来额外的CPU成本。优化方案的关键是对静态视图直接使用frame或者用约束占位避免重复计算对动态变化的约束尽量使用translatesAutoresizingMaskIntoConstraintsNO之后一次性更新避免多步约束变化导致多次布局。5.2 离屏渲染、圆角、阴影的底层原理选择题里有一段关于离屏渲染的选项问“设置cornerRadius后为什么tableView滚动会卡顿”。这道题的机制是圆角裁剪如果只设置layer.cornerRadius和layer.masksToBounds会触发离屏渲染因为系统需要先把内容渲染到一块额外的缓冲区再按照圆角路径进行裁剪最后才呈现到屏幕上。避免离屏渲染的方案有几种使用UIBezierPath加CAShapeLayer的mask路径方式来裁剪、直接让美术切一张圆角图片、使用UIGraphicsImageRenderer预先绘制圆角图。每种方案都有适用场景但笔试里只要说明“离屏渲染发生在GPU额外缓冲区需要上下文切换因此消耗性能”就已经抓住了核心。5.3 TableView复用机制的底层与优化这道题只要做过iOS开发基本都会但笔试考的是“复用池是怎么实现的”。当cell滑出屏幕时它会被放入复用池新的cell将要出现时tableview从池中取出一个cell调用prepareForReuse重置状态然后绑定新数据。这一步关键点是prepareForReuse不会清空所有状态只清理你手动重设的部分所以如果自定义cell里忘了重置某个Label的高度就会出现数据残留的bug。接下来还会问“如何保证cell高度计算不影响滚动性能”。方案核心是避免在heightForRowAtIndexPath中动态创建临时对象和计算约束预先把高度缓存在数据模型或字典中或者采用约束驱动自适应的UITableViewAutomaticDimension并配合estimatedRowHeight让系统先给出估算值、再在布局完成后精确更新。5.4 启动优化方向启动优化这类题在近几年笔试题里出现概率越来越高因为业务体量变大后App冷启动的秒开率直接影响用户留存。B卷中它的呈现方式是场景题如果直播App冷启动比较慢你会怎么排查和优化。答案要从main函数之前和之后两个阶段分别展开。main之前主要是系统加载动态库、加载可执行文件、执行ObjC的load方法、进行rebase和bind等操作这个阶段能做的优化是减少动态库依赖、合并/删除多余的类与分类、尽量少写load逻辑。main之后主要是初始化window、rootViewController以及首屏业务数据的加载这个阶段可以通过将必要的首屏数据改为异步预加载并把图片解码、数据库打开等任务延后到滚动空闲或子线程执行来优化。6. 直播场景简答题欢聚时代出题风格中最具辨识度的一环6.1 如何设计直播间的IM消息通道B卷简答题中出现了一道结合业务的题目让你设计直播间里的聊天消息收发架构要求消息不丢、不乱序、低延迟。答题框架我建议这样拆客户端通过长连接WebSocket或自研TCP长连接与IM网关通信点赞、弹幕这类高频消息可以走独立的轻量消息通道降低与核心聊天信息的相互影响消息乱序的解决方案是引入客户端序列号机制由服务器下发统一的递增序号客户端收到后按序号插入缓存消息不丢则依赖客户端本地可靠重传和ACK确认机制发送方发出消息后如果超时未收到ACK会重新发送。在扩展到直播场景时还要考虑弹幕的“高频聚合”特性几万条弹幕在短时间内涌入如果逐条渲染会导致UI卡顿通常做法是对弹幕做分级合并或按用户策略丢弃低优先级消息。这样既能保证用户体验也不会把客户端CPU打满。6.2 礼物动画和连麦的音画同步设计礼物动画是直播场景中业务占比较高的部分同时也是iOS性能挑战极大的场景。B卷里有一道关于“礼物特效怎么保证流畅度”的题表面考动画实际考的是渲染性能。不同礼物动画的渲染层级应尽量合并使用CALayer的shouldRasterize或drawRect的离屏缓存来减轻GPU负担高并发场景下要避免在UI线程频繁创建大量视图对象而应采用对象池复用礼物视图。连麦场景下的音画同步是另一个重头。音频和视频数据来自不同的采集通道如果网络抖动接收端的音频和视频会产生越来越大的延迟偏差。设计时需要在接收端为音视频包分别维护缓冲区并根据播放时间戳计算当前RTP传输的延迟增量动态调整缓冲深度和播放速度来补偿抖动。这个思路放在今天的WebRTC里也同样适用涉及JitterBuffer与音画同步模块的联动。6.3 直播间秒开优化“秒开”在欢聚时代笔试里也是有明确要求的。观看端从点击直播间到看到第一帧画面的耗时是会影响用户停留时长的关键指标。B卷围绕这个方向出的简答题问法通常是“从哪些环节来优化直播首屏秒开”。答题应按数据链路拆解首帧链路包括拉流地址获取、CDN调度、建立连接、音视频解码、首帧渲染。每个环节的优化对应着不同的技术获取拉流地址可以提前在进入直播间前预请求CDN调度需要根据IP和地理位置选择就近节点建立连接可以升级到QUIC来减少握手RTT首帧渲染优先输出I帧并在播放器侧用首帧快速渲染代替先缓冲一定时长才能实现显著缩短用户首屏等待时间。7. 编程题实战从算法思路到iOS手写代码7.1 链表反转与环检测B卷的编程题以基础算法为主其中链表反转是出现概率很高的题。三种实现方式都要能随口说出迭代法、递归法、头插法而且必须注意处理空链表和单节点边界。这里有个笔试坑如果是在画图答题纸或在线编辑器里递归法经常被忽略但实际考的是你写代码是否干净。链表反转的迭代法核心是三个指针pre、cur、next每轮先保存next再把cur-next指向前一个节点最后整体后移。环检测则是快慢指针的经典应用快指针每次走两步慢指针每次走一步如果存在环则必然相遇。这类题放在iOS笔试题里并不难但它是后续手写代码题的铺垫如果连链表反转都写不利索会直接影响阅卷人对你的整体评价。我的建议是把链表、二叉树、栈和队列的经典题全部手写熟练现场才不会慌张。7.2 字符串操作题去重、翻转、最长不重复子串B卷里还有一道字符串题从考察点看是“最长不重复子串的长度”。这种题要用滑动窗口来做用左右两个指针维护当前不含重复字符的窗口右指针向右移动遇到重复字符时把左指针跳转到重复字符索引1的位置同时用哈希表记录每个字符最近一次出现的位置。时间复杂度从暴力解法的O(n²)降到O(n)。这道题出现在iOS笔试里的用意是考察基本的数据结构和算法思维。需要注意的是不同语言中字符串的字符访问方式差别很大OC和Swift对Unicode的支持也不同。如果使用OC一般用unichar数组或NSRange逐字符处理要特别小心emoji等双字符Unicode这是实际开发中容易出bug的点。7.3 手写单例与手写KVO封装除了纯算法题B卷还会有一道“根据需求实现指定iOS功能”的手写代码题。常见的就是手写单例或者封装一个简易的KVO监听工具。单例的标准写法在OC中要考虑线程安全通常用dispatch_once保证sharedInstance在整个生命周期内只执行一次。Swift 5之后可以用static let shared ClassName()因为全局let本身就自带线程安全和懒加载属性所以不需要额外加锁。手写KVO封装则是进阶版的考察要求实现一个不依赖系统KVO的监听工具核心是在监听时为对象动态添加关联的观察者信息在被监听的setter里手动对比新旧值并回调。这道题考察的其实不止KVO还包括关联对象、Block存储、多线程回调这些综合能力能完整写出来的人确实不多。8. 这套B卷放到2024年还值得复盘吗8.1 技术栈演进后哪些考点依然成立很多人觉得2018年的笔试题放到现在Objective-C都已经不再是新项目的主流选择了Swift和SwiftUI的夹击下当年那些Runtime知识是不是过时了。我的判断是底层原理没有过时反而因为现代框架越来越黑盒而变得更加重要。Swift的运行时在纯Swift类型上不依赖OC Runtime但一旦和OC混编消息转发、关联对象、KVO这些概念依然会出现。SwiftUI的声明式UI虽然改变了界面构建方式但背后的布局系统依然要处理约束求解和渲染性能问题。内存管理中[weak self]捕获列表和循环引用的场景更是从OC原封不动迁移了过来。所以如果你现在为了这场考试翻出2018的B卷完全可以把它当成面试八股的源题库来复习。只是需要额外补充Swift并发、SwiftUI渲染机制、Masonry到SnapKit迁移这些技术演进相关的内容。8.2 备考建议从笔试到面试的衔接笔试题里的简答题大概率会在后续面试里被追问。比如B卷里问“GCD与NSOperationQueue区别”面试官就接着问“你实际项目里用NSOperationQueue实现过什么”如果你没有真实使用经验回答就会露怯。我的建议是笔试复习阶段不要只背书要针对每道笔试题准备一个真实的项目侧案例。你可以在GitHub上找一个开源直播项目研究它的IM消息模块设计或者自己写一个简单的播放器Demo把秒开优化、弱网适配这些场景亲手做一遍。这样笔试时你能写出理论答案面试时又能拿实战经验来佐证整个链路才算完整。8.3 最后一条务实经验做题时不要在不会的选题上浪费超过两分钟。B卷的平衡点在于基础题的分数要尽量拿满中等题的展开要体现逻辑困难题即使是写个解题思路也能得到过程分。我当时在选择题里遇到一道没见过的Runtime黑科技题硬耗了五分钟导致后面简答题时间紧张这个教训希望后来人不要再踩。如果准备时间有限我建议优先摸透内存管理、多线程、UI渲染这三块。它们既是笔试题里的重头戏也是日后iOS日常开发中真正会反复使用的核心能力。把这三块的底层道理想清楚比追求刷一千道题更有用。
返回列表