尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

欢聚时代2018iOS笔试题复盘:高频考点与工程思维解析

欢聚时代2018iOS笔试题复盘:高频考点与工程思维解析 去年年底整理网盘翻出了当年校招季的很多资料其中有一份正好是《欢聚时代2018校招笔试题-iOS A卷【成都场】》。说实话看到这个文件名的一瞬间当时考场上那种“题目量大、时间紧、有几道题一眼看不到底”的感觉又回来了。欢聚时代当时主打YY直播、游戏和语音社交技术栈以Objective-C为主Swift刚开始在部分团队落地所以那份A卷考察的底子和今天iOS面试其实有不少重叠但有些考点已经变了味道。这两天我重新把这套卷子的考察范围过了一遍结合当时成都场同学的回忆和我自己后来的复盘把高频考点、答题思路和容易踩的坑整理成文。这不是押题也不打算逐题给出“标准答案”而是想借这份旧卷子聊清楚一件事2018年前后的移动互联网公司在校招笔试里到底在筛选什么样的iOS新人以及这套筛选逻辑放到现在还有没有参考价值。1. 欢聚时代这套卷子在考什么能力模型的底层逻辑很多同学拿到笔试题的第一反应是“这题我没见过”“这知识点我背过但忘了”其实在一份设计合理的笔试卷里每一道题背后都站着一个筛选维度。搞明白公司想通过笔试筛出什么样的人比盲目刷题重要得多。1.1 一套笔试题背后的筛选漏斗2018年欢聚时代正处于直播业务高速扩张期YY直播、游戏直播、语音房每一个业务都对iOS客户端的稳定性、流畅度、IM消息链路有很高要求。笔试放在简历筛选之后、面试之前它的核心目标不是“看你会多少偏门考点”而是快速筛掉三类人基础不扎实的、没有工程常识的、代码习惯很差的。所以那份A卷的考察面其实很规整典型分布大致如下考点大类常见出题形式考察意图OC语言特性选择题、简答题语言功底是否扎实内存管理代码阅读题、找错题是否理解引用计数、循环引用Runtime与消息机制简答、原理说明对iOS核心机制的理解深度多线程与RunLoop代码题、死锁分析工程并发场景的处理能力网络协议简答、流程说明是否真正做过网络请求UI布局与渲染概念辨析、优化题是否做过真实业务界面系统设计开放题整体架构思维和表达能力从这张表能看出来笔试考察的不是“你背过多少API”而是“你有没有形成一套关于iOS系统如何工作的心智模型”。我在考场上最直观的感受是纯记忆类题目其实不多更多题目是给你一段代码、一个场景让你推演结果。这类题一旦理解机制就很难丢分不理解机制靠猜基本没戏。1.2 为什么同一套题要分A卷、B卷还要分城市标题里写的是“IOS A卷【成都场】”说明同样的笔试在不同城市、不同场次会有不同卷子。这个操作在校招里很常见一是防泄题二是公司可以根据不同站点的简历情况微调题目难度。但别被“A卷”“B卷”这个形式吓到经历过的人都知道同一批考点会被反复以不同形式打散重组。比如广州场可能把“Block循环引用”出成选择题成都场的A卷就可能让你分析一段存在循环引用的代码输出结果。考点是稳定的变的是出题角度。这给了我们一个非常具体的备考启示刷原题的意义不大把每个核心考点的底层原理吃透能以不变应万变才是笔试准备的正路。我自己当年也犯过“背题”的错后来发现一旦题目从“原理是什么”变成“代码会输出什么”死记硬背立刻失效。2. 高频考点逐个拆解从OC语言基础到Runtime底层既然笔试是一面“照妖镜”那它最常照的就是iOS工程师的基本功。OC语言特性和Runtime机制是2018年校招笔试的绝对主力而且直到今天许多公司面试还在沿用同一套问法。2.1 Category为什么不能添加成员变量这是当年A卷里最典型的“送命题”看起来不难但能答完整的人比例并不高。先说结论分类(Category)在运行时可以把方法、协议合并进宿主类但它不能直接添加成员变量。原因是成员变量会参与实例对象的内存布局这个布局在编译期就由实例变量列表决定了而Category是运行时才合并进去的如果允许它改变实例大小内存布局就彻底乱套了。但这里有个细节很多同学会混淆分类里用property声明的属性并不会自动生成成员变量和getter/setter因为property本质上是“声明自动合成”的语法糖自动合成的_ivar需要编译器在类结构里预留空间而Category做不到这件事。笔试题如果问到“那我想让分类有属性怎么办”标准答案是使用关联对象Associated Object。用objc_setAssociatedObject和objc_getAssociatedObject可以给对象动态挂上属性值原理是运行时维护了一个全局的AssociationMap。这个点我会在后面的经验部分重点讲因为这里有一个很隐蔽的坑。2.2 消息发送、动态解析与消息转发OC的方法调用本质是消息发送这件事基本是简答题必考。完整的调用链路是调用objc_msgSend沿着对象的isa指针找到类在类的方法缓存和方法列表中查找SEL对应的IMP找不到时进入动态方法解析流程resolveInstanceMethod:允许你动态给类添加方法实现如果动态解析没有处理走快速转发流程forwardingTargetForSelector:让其他对象代为处理最后走完整转发methodSignatureForSelector:和forwardInvocation:把消息封装成NSInvocation转发出去都没有处理最终触发doesNotRecognizeSelector:崩溃笔试考这个点通常不是让你背诵链路而是给一段方法交换代码或调用不存在方法的场景让你分析结果。比如“怎样防止因方法不存在导致的崩溃”答案就是在forwardInvocation里做统一兜底把不存在的SEL记日志或者丢弃。我还要多说一个实际答题时容易踩的坑消息转发流程里动态方法解析阶段(第2步)是给类方法添加实现的最佳时机但有人会把动态解析和快速转发混为一谈。动态解析是“我自己补上实现”快速转发是“我把消息扔给别人处理”两者处理方式完全不同答题时最好分开表述把实现细节写清楚。2.3 Block的三种形态和循环引用Block是OC笔试里出镜率极高的点因为它把语法特性、内存管理和工程实践串在了一起。2018年那会关于Block最基本的一道题是说出它的三种形态_NSConcreteStackBlock栈上Block捕获自动变量作用域结束后失效_NSConcreteGlobalBlock全局Block不捕获任何外部变量存储在全局区域_NSConcreteMallocBlock堆上Block栈上Block被拷贝到堆上后形成很多同学只背住了三种名称但笔试题一定会追问“__block修饰符是干什么的”。要答全__block修饰的变量可以被Block内修改而且在Block从栈拷贝到堆时__block变量本身也会被移入堆中保证Block内外访问的是同一个变量。循环引用的典型场景也必须会分析self持有BlockBlock内部又使用self或成员变量两者互相持有导致dealloc不执行。标准的解决写法是__weak typeof(self) weakSelf self; self.block ^{ __strong typeof(weakSelf) strongSelf weakSelf; [strongSelf doSomething]; };笔试阅卷时阅卷人特别看重“Block内部是否先__strong再持有”这个小细节。只写__weak不加__strong虽然也能解决循环引用但在Block执行过程中如果weakSelf提前释放后续调用全部失效加了__strong能保证整个Block调用周期内对象存活。这个细节在代码题里非常加分也是过来人常说的“面试官一眼就能看出你有没有真实处理过循环引用”。2.4 内存管理从strong/weak到自动释放池关于内存管理的选择题2018年的笔试卷子特别喜欢结合ARC出题。核心考察点是strong/weak/assign/copy的区别weak指针为什么能自动置nil自动释放池的释放时机weak自动置nil的底层机制笔试常考运行时维护了一张全局的弱引用表对象被释放时会自动遍历该对象的弱引用记录把所有__weak指针安全地置为nil避免悬垂指针。这个机制依赖objc_loadWeak和objc_storeWeak里面还有加锁操作。自动释放池这个问题如果只答“出了作用域就释放”是不及格的。更完整的理解是主线程的RunLoop在每个循环周期会自动创建和销毁自动释放池autorelease对象在RunLoop休眠前或池子销毁时统一释放。这也解释了为什么大量循环内创建临时对象会导致内存峰值上涨因为临时对象会被积压到当前池子销毁时才释放。答题技巧方面我建议画时间轴描述一个RunLoop周期内自动释放池什么时候创建、什么时候销毁、autorelease对象在哪个节点真正释放。这样比干巴巴背概念更容易拿分。3. 多线程与RunLoop这份卷子里最容易被扣分的地方多线程和RunLoop是iOS笔试的“重灾区”因为这部分内容需要真正的并发实践才能理解透彻纯背书很难应付。成都场这份A卷里相关题目占比不低而且区分度很高。3.1 GCD和NSOperationQueue的边界一道高频简答题是“GCD和NSOperationQueue有什么区别”。很多同学能说出“GCD是纯C接口NSOperationQueue是OC封装”但这只是第一层。要拿高分还需要补上NSOperationQueue支持取消任务、设置任务依赖、控制最大并发数量而GCD较难优雅地实现任务取消GCD可以用dispatch_once实现单例用dispatch_barrier_async实现读写锁用dispatch_group实现多任务编排NSOperationQueue原生不提供这些GCD是系统级线程管理的底层能力NSOperationQueue内部也依赖于GCD的队列机制笔试答题的层次应该是先说二者底层关系再说各自擅长场景最后结合实际举一个“我这个功能为什么选GCD而不选NSOperationQueue”的例子。这种答题结构容易让阅卷人觉得你确实做过并发开发。3.2 dispatch_sync死锁一个必考代码题dispatch_sync死锁几乎是每次笔试都会出现的代码题。最经典的场景是在主线程执行dispatch_sync(dispatch_get_main_queue(), ^{ // 任务内容 });这段代码必死。原因是主线程此刻正在执行dispatch_sync而dispatch_sync会阻塞当前线程等主队列里的Block任务完成。但主队列是串行队列这个Block任务排在队列里必须等当前主线程把dispatch_sync调用整完才能执行。当前线程在等BlockBlock在等当前线程形成互相等待。另一个容易忽略的死锁场景是在一个串行队列里调用dispatch_sync到同一个队列同样死锁。原因是串行队列同一时刻只允许一个任务执行当前任务还没结束队列不可能取出下一个任务。笔试答案只写“会死锁”是不够的最好把“为什么死锁”“哪个线程等谁”写清楚。阅卷人看的是你有没有真正理解串行队列和阻塞的含义而不是背诵结论。3.3 RunLoop的Mode切换和定时器RunLoop相关题目在2018年校招里也经常出现。最经典的例子是在UIScrollView滑动时NSTimer失效。原因就是RunLoop切换到了UITrackingRunLoopMode默认模式下注册的Timer没有加入commonModes所以滑动时不被唤醒。标准解法是把Timer加入NSRunLoopCommonModes或者改用CADisplayLink/DispatchSourceTimer。笔试如果考到这里答题时最好补充一句commonModes并不是一个真实存在的Mode而是一个包含多种Mode的集合它会同步到所有声明了它的Mode。另外RunLoop有多个观察点kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting这两个状态和自动释放池的释放时机直接相关。如果能把RunLoop状态机、自动释放池、界面滑动卡顿三者串起来讲这道题基本就是满分水平。3.4 容易忽略的“并发队列与线程爆炸”现在回头复盘当时的卷子里有一类题目看起来简单但特别能拉开差距它不给代码而是给描述性场景“一个直播App的消息服务同时有多条并发任务在拉取数据你如何控制并发”这个场景看似考察网络请求实际是考察并发控制。2018年前后很多App遇到线程爆炸原因就是大量任务被提交到全局并发队列而全局并发队列会根据系统负载动态创建线程任务多了线程数就失控。合理的方案是用NSOperationQueue设置maxConcurrentOperationCount或用dispatch_semaphore限制同时执行的任务数或者通过自定义串行队列做任务拆分答题方向如果只写“用并发队列”就是踩了坑因为面试官想听的恰恰是对“过度并发”的警惕。结合直播场景来说消息服务应该用串行队列保证消息顺序同时用并发队列处理图片下载等无顺序要求的任务。这种“场景化回答”非常能体现工程素养。4. 网络、存储与UI布局工程化能力的考察重点除了语言底层和并发机制笔试还有一个板块专门考察工程化基础网络协议、数据持久化、UI布局和渲染优化。这些内容看起来零散但其实每一项都和“能不能独立负责一个真实业务需求”挂钩。4.1 从三次握手问到HTTPS网络题怎么答才算完整网络题在校招笔试里几乎是必出的最常见的问法是“简述TCP三次握手过程和四次挥手过程”。这道题听着简单但很多人写不清为什么需要三次而不是两次。三次握手的核心是双方都需要确认自己和对方的收发能力正常。第一次握手客户端告诉服务器“我能发”第二次握手服务器告诉客户端“我能收也能发”第三次握手客户端告诉服务器“我也能收”。只有三次握手才能让双方都确认收发通道正常。如果只有两次服务器无法确认客户端的接收能力。往前一步问到HTTPS握手时要能说清楚TLS的流程客户端发起请求生成随机数、服务器返回证书和随机数、客户端验证证书并生成预主密钥、双方基于预主密钥计算出会话密钥、之后用对称加密通信。证书验证的核心是数字签名和CA信任链。移动端网络题的加分项是HTTPDNS和连接复用。可以提“网络请求默认使用HttpURLConnection/NSURLSession的keep-alive机制长连接场景下用HTTP/2多路复用”这会让阅卷人觉得你做过App网络层的真实调优。4.2 SQLite与FMDB为什么要手写增删改查2018年笔试对数据持久化的考察频率很高因为聊天、直播这类场景对本地缓存和消息记录要求很高。考试通常不是让写完整项目而是让你建一张表、写几条SQL。比如出一道“设计消息表结构”基本答复是CREATE TABLE IF NOT EXISTS message ( id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT NOT NULL, content TEXT, timestamp REAL, is_read INTEGER DEFAULT 0 );更高级的回答会主动加上索引CREATE INDEX IF NOT EXISTS idx_timestamp ON message(timestamp);为什么加索引因为IM消息列表最核心的查询是按照时间倒序拉取没有索引的话全表扫描会随着消息量增长迅速变慢。这个“主动优化”的细节在笔试中很容易被忽略但在真实业务中极其重要。另外FMDB的线程安全性也是常考内容FMDatabase不是线程安全的不能在多个线程同时使用同一个实例正确做法是使用FMDatabaseQueue它在内部通过串行队列保证所有数据库操作顺序执行。这里的实操经验是SQLite题千万别只答增删改查能主动写出事务操作或索引你的答案层次会高出一截。因为事务能保证多条消息批量写入时的原子性索引则说明你考虑过数据量增长后的查询性能。4.3 frame和bounds的区别一道看似简单却筛掉不少人的概念题是“frame和bounds有什么区别”。这道题考察的是对视图坐标体系的理解。简单说frame是相对于父视图坐标系的位置和大小bounds是相对于自身坐标系的位置和大小并且修改bounds的origin会影响子视图的布局。具体例子一个View的frame是(100, 50, 200, 100)它的bounds默认是(0, 0, 200, 100)。如果把这个View的bounds改成(0, 0, 100, 50)虽然它的frame不变但子视图会以这个新的原点来布局结果就是所有子视图整体向左上角移动。笔试考这道题很多同学能答出定义但答不出“修改bounds的origin后子视图会移动”这个应用场景。如果能把UIScrollView的滚动原理通过修改bounds实现内容偏移挂上这道题就有了工程意义阅卷人也能看出你是“用过”而不是“背过”。4.4 离屏渲染与卡顿优化性能优化题在2018年的笔试里不算最多但一旦出现很可能就是决定是否进入面试的区分题。最常考的是离屏渲染为什么给UIView设置圆角加masksToBounds会导致离屏渲染原因在于圆角加裁剪意味着图层需要先被渲染到一个离屏的缓冲区再进行圆角裁剪和合成。离屏渲染本身不是洪水猛兽但如果Cell里大量视图同时开启圆角阴影GPU的离屏缓冲区会被耗尽触发性能瓶颈。优化方案通常写三个方向使用UIBezierPath画出圆角路径并设置layer.cornerRadius配合shouldRasterize但要小心shouldRasterize的滚动卡顿直接让美术切图用带圆角的图片替代动态圆角裁剪或者使用CAShapeLayer做遮罩避免触发整棵视图树的离屏渲染为什么笔试爱考这个因为公司希望招到的人上线后不会因为小小的圆角写法就让列表掉帧尤其是欢聚时代这种以音视频和聊天室为核心业务的App消息列表和直播间的流畅度就是生命线。这个考点背后是公司对“线上质量”的焦虑。5. 如果卷子里有一道“设计题”你应该给出怎样的答卷除了客观题和代码题校招笔试的压轴位置通常会有开放设计题。很多人一看到开放题就懵觉得没有标准答案无从下手。实际上开放题最有套路也最能拉开差距。5.1 设计一个图片加载库图片加载库是iOS笔试开放题里的经典题目因为它把异步、缓存、并发、内存管理、UI更新全部串在一起特别适合考察综合能力。答题时不要上来写代码先铺框架。第一步需求分析图片加载要快不能阻塞主线程要支持内存和磁盘缓存要处理重复请求要防止图片错乱。第二步接口定义核心是loadImageWithURL:completion:支持占位图、进度回调、取消操作。这一步要体现面向协议的设计思想而不是写死一个类。第三步时序设计请求图片时先从内存缓存查没有再查磁盘缓存都没有才发起网络请求。网络返回后先解码、压缩到适当尺寸再写入缓存最后回到主线程更新UI。第四步边界处理内存警告时清空内存缓存同一个URL的并发请求要合并避免发N次请求TableView滑动时Cell复用后上一次的图片请求要支持取消防止图片错乱。开放题答题的隐藏加分项是主动提到“避免在主线程解大图”和“在子线程进行图片解码”。很多校招新人不知道UIImage创建时不会真正解码直到绘制时才解码而解码默认发生在主线程会造成掉帧。能写出这一点说明有真实调优经验。5.2 设计一个IM消息列表欢聚时代是做直播和语音社交的IM场景贯穿整个产品。笔试里的设计题如果问“设计一个聊天室消息列表”不要只把它当普通列表做要抓住IM系统的核心问题消息有序、消息去重、分页加载、增量同步、本地缓存。比较完整的回答是数据模型包含消息ID、发送者、内容、时间戳、消息状态发送中、失败、已读)本地存储使用SQLite以消息ID建表并加时间索引列表数据源保持一个有序数组收到新消息时先根据消息ID去重再插入到正确位置下拉加载历史消息时从数据库分页读取并和当前数组做合并去重。增量同步部分最好提一下进入聊天室后先拉最近50条建立本地分页游标后续通过长连接接收增量消息。如果网络断开重连后需要做消息补偿拉取拉取范围是本地最大消息ID之后的所有消息。答题时如果能画出“数据在内存、数据库、网络三端如何流动”的示意这件事会非常加分。面试官真正想看到的不是你会写UITableView而是你能不能设计出一个经得住消息量大、弱网环境考验的方案。5.3 设计题通用的作答框架开放题其实是有答题套路的我在复盘了多份笔试评语之后总结了五步需求分析列出该功能的用户场景和非目标明确边界接口设计定义外部调用方式尽量面向协议模块拆分把系统拆成网络层、存储层、业务层、UI层时序流程用文字描述一次完整操作从开始到结束的流程容错与优化补齐异常处理、性能优化、埋点监控方案这个框架的好处是哪怕你对某个技术细节不够熟悉也能从总体结构上让阅卷人看出“这个候选人设计过系统”而不是“只知道堆API”。笔试时间紧的时候框架能帮你在有限时间里输出一份逻辑完整的答案避免想到哪写到哪。6. 现在回头看这套卷子给我的三个实战启示把2018年成都场的卷子完整复盘完我最深的感受是技术会过时但考察的能力模型不会过时。那套题里涉及的消息转发、Block循环引用、RunLoop、SQLite索引、离屏渲染放在今天的面试里依然是高频问题只是换了一种问法套了一层新壳。第一个启示是知识点不能孤立记忆要串成系统。比如Block循环引用不是背一句“用__weak解决”就行它连着内存管理、dealloc时序、局部变量作用域甚至RunLoop释放时机。我在后面带新人时会让他们把每一个“考点”做成一张关联图由一个点往外扩散直到能讲清楚它和自己做过的哪个线上Bug相关。这种复习方式比刷一百道题有用。第二个启示是手写代码题存在“隐形成分”。同一道代码题A同学只写答案B同学写答案的同时带注释、处理边界、注意变量命名阅卷观感完全不同。我在模拟面试中反复提醒笔试时先写思路再写实现对可能出现nil的地方做防护命名使用系统认可的规范写完回头复查是否有循环引用。这些“隐形成分”不影响代码本身对不对但会影响阅卷人判断你是不是一个靠谱的工程师。第三个启示是这套题里我当年最头疼的设计题反而是现在工作里最常用的能力。设计图片加载库、设计IM消息列表这些场景几乎每天都会变个样子出现在需求里。能答好开放题的人往往不是懂得最多的而是思路最清晰的。思路清晰来自真实项目的反复捶打没有捷径。如果你正在准备下一场iOS笔试我的建议很直接翻出你手上最近的笔试回忆题别看考点清单先试着从“这个知识点扣在哪个环节它到底解决什么问题”的角度重新梳理一遍。把基础考点吃透比囤一堆“面经”管用得多。欢聚时代这份旧卷子本身已经成了历史但它背后那套“考察基础、考察工程观、考察设计思维”的筛选逻辑到今天仍然值得每个iOS开发者认真对待。
返回列表