
2018年秋天我参加了商汤科技校招iOS开发工程师的第一场笔试。当时商汤在AI视觉领域风头正盛投递这个岗位的人非常多笔试作为第一道筛选关卡题量不小、覆盖面也很杂。考完之后我第一时间把题目和考点记录了下来后来也陆续把这些内容分享给了学弟学妹。今天整理成一篇完整的复盘笔记希望能给准备iOS校招、尤其是准备投AI公司iOS岗位的朋友一些参考。这篇文章不是官方真题答案而是我从考生视角做的考点还原和思路拆解。内容包括笔试整体结构、iOS基础考点逐一分析、编程题解题复盘、AI公司特有的考察方向以及一些非常实用的避坑经验。适合正在准备iOS校招笔试的人、想了解AI公司技术岗位考察风格的人以及想系统梳理iOS知识框架的开发者参考。1. 笔试整体结构与出题逻辑1.1 试卷总体布局商汤这场笔试是线上笔试考试时间一共90分钟题量大概在40道左右。题型分布大致为单选题约20道、多选题约10道、编程题2道、问答题1道。选择题覆盖了iOS开发基础、计算机基础、算法与数据结构、机器学习常识四个方向编程题以算法为主问答题则偏向系统设计。从题型配比能看出一个明显特征这不是一场纯粹的iOS语法考试而是一场“基本功方向匹配度”的综合筛选。商汤作为AI公司对iOS工程师的定位不是简单的界面开发而是能做算法集成、性能调优、底层优化的客户端工程师所以笔试里计算机基础的比例明显高于一般互联网公司的同类岗位。1.2 考察侧重点分析我复盘后发现这套笔试的出题逻辑其实分三层第一层是硬性门槛也就是iOS基础知识主要筛选“能不能干活”。这一部分题不算难但覆盖面很广从内存管理到多线程、从Runtime到网络请求基本把iOS日常开发的高频知识点都扫了一遍没有实际写过项目的人很容易栽在这里。第二层是算法与数据结构主要考察“能不能解决复杂问题”。商汤的算法笔试难度在2018年校招里属于中上水平两道编程题都不算简单一道偏字符串处理一道偏二叉树的遍历变形全都是LeetCode中等偏上的难度。第三层是AI方向匹配度这算是商汤的特色了。试卷里出现了少量机器学习基础题比如卷积核的作用、梯度下降的基本思想等。这类题并不深但如果你完全没接触过AI概念会明显感觉吃力。1.3 为什么这套题值得反复琢磨很多同学有个误区觉得笔试刷题就是拼手速、拼题海。但商汤这套题给我的最大感受是它考察的不是突击记忆能力而是你平时写代码时有没有真正理解底层机制。比如有一道关于GCD死锁的选择题原理不复杂但如果只是背过“串行队列同步任务会死锁”这个结论而没理解队列与线程的关系换个问法就答不对了。这篇文章的后续部分我会把每一类考点的具体内容、我当时的选择和思考过程、以及错题复盘全部列出来还会补充一些我在准备这场笔试时用到的方法论。2. iOS基础考点逐项拆解2.1 内存管理与循环引用最容易被绕进去的送分题商汤笔试的iOS基础部分内存管理是绝对重点。单选题和多选题里至少出现了3道相关内容。其中最经典的一道是这样的下列代码在ARC环境下是否有循环引用如果有如何解决 implementation Person property (nonatomic, copy) NSString *name; property (nonatomic, strong) void (^block)(void); end self.block ^{ NSLog(%, self.name); };这道题考了两个关键点一是block会对self做强引用二是self又通过strong属性持有block两者相互持有就形成了循环引用。正确答案是“有循环引用需要用__weak修饰self后再在block内部使用”。但这里有个容易忽略的细节ARC环境下使用__weak修饰的变量如果block执行前self已经被释放weakSelf会变成nil所以更稳妥的做法是在block内部先用__strong对weakSelf做一次强引用再在block执行期间安全使用。我当时选择了只写__weak的答案虽然也得分但现在复盘觉得应该主动补充__strong才是完整方案。另一个让我印象深刻的题目是关于copy和strong的选择。题目问NSString属性应该用哪种修饰符。很多人会凭经验回答copy但背后的原理是如果赋值的是一个NSMutableStringstrong修饰会让属性指向同一个可变对象后续外部修改这个可变字符串时属性值也会跟着变这在很多场景下是隐患。用copy则会在赋值时做一次不可变拷贝保证属性值稳定。这种题表面考修饰符实际考的是对对象所有权和可变性的理解。2.2 GCD多线程与死锁陷阱多线程考点里最经典的一道题是这样的在iOS中在主队列上执行以下代码会发生什么 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(hello); });答案是死锁。原因在于主队列是串行队列而当前代码本身就运行在主队列上dispatch_sync会让当前线程阻塞等待新任务执行但新任务又被排在当前任务之后形成互相等待。这里我想多说一句理解GCD死锁的关键不是背结论而是理解“队列负责任务调度线程负责任务执行”这个分工。串行队列一次只执行一个任务同步提交的任务必须等待前一个任务执行完才能开始。如果你把这段代码放到全局并发队列里执行就不会发生死锁因为并发队列不会阻塞当前任务。商汤笔试里还考察了关于dispatch_barrier和dispatch_group的知识点。这道题问的是多个异步任务执行完成后统一回调用哪个API。正确答案是dispatch_group_notify。但题目有个小陷阱如果这些任务都是同步任务dispatch_group_notify也会正常执行但如果是并行队列上加同步任务需要小心dispatch_group的enter和leave次数必须严格匹配否则回调永远不会触发。这个细节在我后来写实际项目时确实踩过坑。2.3 Runtime与消息转发机制Runtime相关题目在2018年的笔试中属于拉开差距的题型。商汤考了一道消息转发的流程排序题大致是把以下步骤按顺序排好resolveInstanceMethod:动态方法解析forwardingTargetForSelector:快速转发给其他对象methodSignatureForSelector:获取方法签名forwardInvocation:完整转发正确答案是1→2→3→4。这道题看起来是送分题但实际上很多人会把2和3的顺序记反。我当时能答对是因为之前遇到过一个场景某个对象收到了performSelector:调用但当时并没有实现对应方法我为了临时修复bug通过forwardingTargetForSelector:把消息转发给了另一个对象。这句错误信息里显示的调用栈帮我梳理了消息转发的完整顺序。现在看来这种“靠实战踩坑记住的知识点”远比背文档来得牢固。商汤还问了一个关于isa指针的问题Objective-C对象的isa指向什么。答案是“指向类对象Class对象”。这在iOS开发中非常基础但它的引申考点是元类MetaClass的概念如果你能把类对象、元类、根元类之间的关系画清楚面试阶段会加不少分。2.4 UIKit与界面布局UI相关题目在笔试题中占比不小但难度普遍不高。有一道典型的UITableView优化题问“以下哪些方式可以提升UITableView的滚动流畅度”。选项包括复用Cell、提前计算并缓存Cell高度、使用不透明的subview、避免在cellForRowAtIndexPath:中做大量同步操作。这四个选项在理论上都是正确答案。但这里面有一个容易忽略的细节prefetchDataSource在iOS 10以上能显著提升列表流畅度但商汤这套题是2018年的iOS 11已经发布所以我在回答时特意提到了额外的方法也判断出题目设的“坑”是想让大家区分静态优化与动态加载两类手段。实际项目中我们团队做列表优化时一般分三层第一层是缓存高度和复用Cell第二层是图片异步加载并做尺寸裁剪第三层才是用Instrument工具做性能分析和卡顿监控。笔试虽然只考了理论但如果你能把这个思路讲清楚面试官会眼前一亮。另外一道UI题是关于Auto Layout的。题目问“在Auto Layout中设置约束时缺少必要约束会发生什么”。答案是“布局可能不确定出现frame为zero或约束警告”。这道题本身不难但它引出的一个重点是UIStackView在iOS 11以后对自动布局的简化。商汤这类AI公司做界面时经常要动态排列一些展示项用UIStackView能大幅简化约束代码。如果备考时间充裕我建议把UIStackView的使用也纳入复习范围。3. 编程题与算法实战复盘3.1 编程题一字符串压缩算法商汤笔试的两道编程题里第一道是字符串压缩。题目描述大概是这样的实现一个字符串压缩函数将连续出现的相同字符压缩成“字符出现次数”的形式。 例如aaabcccccaa 压缩后为 a3b1c5a2。 如果压缩后的字符串长度不小于原字符串则返回原字符串。这道题的核心思路是遍历字符串记录当前字符及连续出现次数遇到不同字符时把上一段的统计结果追加到输出中。我写的是Swift版本代码如下func compressString(_ s: String) - String { if s.isEmpty { return s } var result var prevChar s.first! var count 0 for ch in s { if ch prevChar { count 1 } else { result \(prevChar)\(count) prevChar ch count 1 } } result \(prevChar)\(count) return result.count s.count ? result : s }这道题的难点不在遍历逻辑而在边界条件的判断字符串为空、循环结束后的最后一次拼接、以及压缩后长度不小于原字符串时返回原串。我当时的做法是先把compressString写完整再针对“空字符串”“单字符”“压缩后长度更长”这几种情况做自测。考场上编译器可能给不了太细致的提示所以自己先在草稿纸上跑一遍用例很重要。尤其是“压缩后长度不小于原字符串”这一条如果不加这个判断就会把字符串压缩成更长或等长的结果直接掉进出题人设置的陷阱。3.2 编程题二二叉树最近公共祖先这道题是经典题目“二叉树中两个节点的最近公共祖先”LeetCode原题编号236。题目描述是给定一棵二叉树和两个节点p、q找到这两个节点的最近公共祖先。我用的思路是递归解法。从根节点开始如果当前节点是p或q则直接返回当前节点否则分别在左子树和右子树中搜索如果两边都找到了非空结果说明当前节点就是最近公共祖先如果只有一边有结果那最近公共祖先就在那一边# 当时我用了Python来写这道题 class TreeNode: def __init__(self, x): self.val x self.left None self.right None class Solution: def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) - TreeNode: if not root or root p or root q: return root left self.lowestCommonAncestor(root.left, p, q) right self.lowestCommonAncestor(root.right, p, q) if left and right: return root return left if left else right这道题对iOS开发岗的考生来说难度不算特别大但考察了递归思想和二叉树遍历的掌握程度。我当时比较担心的是递归深度问题如果树是一条链递归会导致栈溢出不过笔试的测试用例本身不会刻意构造极限情况所以用递归是安全的。商汤的笔试环境支持多种编程语言我当时想的是用Python写更稳妥因为Python处理树这种结构用指针引用比较顺手。如果你备考商汤或其他AI公司的笔试建议至少掌握C、Java、Python中一种并且要熟悉链表、树、图这些基础数据结构在语言里的写法不要只会在纸上画结构却写不出代码。3.3 一道没做完的系统设计题问答题是一道系统设计题目要求设计一个iOS端的图像缓存组件。题目给了一个场景需要从网络加载大量图片要求支持异步加载、内存缓存、磁盘缓存并说明缓存淘汰策略。这道题放到今天是老生常谈但2018年让我当场写一套完整方案还是有些手忙脚乱。我当时回答的思路是这样的内存缓存使用NSCache因为它会在内存紧张时自动清理对象并且支持设置countLimit和totalCostLimit不会像NSMutableDictionary那样有OOM风险。磁盘缓存使用文件系统以图片URL的MD5值作为文件名控制总缓存大小和过期时间。图片加载使用异步方式然后在主线程回调更新UI。缓存淘汰策略采用LRU每次访问缓存时把对应项移到最近使用位置超过容量时淘汰最久未使用的缓存项。这套思路其实很接近SDWebImage的实现方式。如果让我现在重新答这道题我会补充两个关键点一是图片解码操作应该放到后台线程避免在主线程解压图片导致卡顿二是内存缓存失效时要同时考虑磁盘缓存和图片请求去重避免同一URL在短时间内被重复请求加载。这个思路后来在我处理大列表图片性能问题时真的直接转化成了一套成熟的优化方案。4. AI相关题目与商汤特色考察4.1 卷积神经网络基础题商汤的笔试里出现了几道机器学习基础题虽然占比不大但如果你完全没有接触过AI知识很容易在这些“额外题”上丢分。我记得有一道题是问“卷积层的主要作用是什么”选项包括图像降维、特征提取、分类、归一化。正确答案是“特征提取”。这道题对计算机视觉方向的同学来说简单得不能再简单但对纯iOS方向的考生来说可能有点陌生。我当时准备笔试时特意翻了一下AI基础概念因为商汤作为AI视觉公司就算招iOS开发工程师也希望你对公司主营方向有基本认知。类似公司比如旷视、依图笔试也多有这种出题风格。4.2 梯度下降与损失函数还有一道选择题问“梯度下降算法中学习率过大会导致什么问题”。正确答案是“损失值震荡甚至无法收敛”。这个考点其实很通用任何一个做算法工程的岗位都绕不开。不过iOS岗位考到这里更像是在筛选“有学习能力、愿意跨界”的人。我的建议是准备商汤这类AI公司笔试时不用把机器学习当作大头来复习但至少要理解卷积、池化、全连接、梯度下降、过拟合这些最基础的概念。不需要会推公式但选择题如果遇到要做到能选对。如果你本身对AI方向感兴趣笔试后不妨深入看一下YOLO、ResNet这些经典模型对后续面试和实际项目会有很大帮助。4.3 为什么AI公司要考iOS开发者的AI知识这个问题我后来和商汤的一名工程师聊过一次。他说这背后的逻辑其实很现实AI公司的客户端研发工作不只是写界面很多时候要做模型在移动端的集成、推理性能优化、图像采集与预处理等工作。如果你不懂AI的基础概念遇到类似问题会完全不知道从何下手。所以这套笔试里加入AI题目更像是一种“方向信号测试”考察候选人对公司技术主线的敏感度。不是要求你已经会训练模型而是希望你有基本认知、愿意学习。这一点对我后来的职业选择也有启发——准备任何公司的笔试都要提前去了解它的业务和技术方向这是性价比最高的备考策略。5. 备考经验与避坑指南5.1 笔试现场的时间分配策略我这次笔试是一场线上笔试总时长90分钟。我当时的策略是先快速把选择题做完遇到完全没把握的先标记不恋战然后优先做两道编程题因为编程题分值高、区分度大最后留15到20分钟回去处理标记过的选择题和问答题。这个策略帮了我大忙。编程题的第一道字符串压缩我用大概10分钟写完并自测第二道最近公共祖先我花的时间多一些大约25分钟因为递归思路虽然清晰但边界条件还是需要多验证几组用例。如果我在选择题上纠结太久两道编程题肯定会写不完。给准备校招笔试的同学一个建议笔试前先看题目分值分布编程题往往每题15到25分一定要留足时间。选择题哪怕全部空着只扣少数分也比编程题没写完直接丢20分划算得多。5.2 最容易踩的坑和连错题复盘我这场笔试里最近踩坑的题目主要集中在两块一个是前面提到的GCD死锁分析另一个是消息转发流程的排序。死锁题第一时间容易直接选择“正常执行子线程打印”如果对GCD的同步/异步语义不够敏感就会掉坑消息转发排序题则容易把动态方法解析和快速转发顺序搞混。这里列一个踩坑速查表方便你们备考时自检考点常见错误正确理解block循环引用认为__weak后就绝对安全需要配合__strong防止弱引用nilcopy vs strong所有对象都用strong可变/不可变类要注意赋值时的引用行为dispatch_sync主队列死锁以为只是子线程阻塞当前所在队列的任务被阻塞形成互相等待消息转发顺序快速转发和完整转发顺序颠倒动态方法解析在前快速转发其次完整转发最后缓存淘汰策略只考虑FIFO实际场景中LRU更贴合图片浏览需求5.3 笔试之后如何转化为综合能力笔试结束不代表学习结束。我考完的当天晚上就把所有记得的题目和答案整理成笔记给自己设置了一个复盘周期三天内把每一道错题对应的知识点在官方文档或权威博客里找到原文再写一遍代码或完整推导。这个习惯帮助我在后续多家公司的面试中很有底气。我个人觉得笔试真正的价值不是那道题的分数而是它逼你把iOS核心知识体系重新梳理了一遍。准备商汤笔试的过程让我系统地复习了内存管理、多线程、Runtime、网络请求和UI布局这些内容在后续的真实项目中反复用到属于性价比极高的投入。如果你打算投商汤或其他AI公司的iOS岗位我的建议是别只刷iOS岗的题把计算机基础尤其是数据结构和算法补扎实再把机器学习最基础的概念扫一遍就已经比大多数候选人有竞争力了。6. 时间过去几年再回顾这套笔试题现在回头看商汤2018年这场笔试很多知识点在今天仍然是iOS面试的核心。内存管理、GCD、消息转发、UITableView优化这些技术在最新版本的iOS开发中依然是基本功。变化在于现在SwiftUI逐渐成为主流很多底层实现方式发生了改变比如Swift中ARC的自动管理更完善但循环引用的坑还是没有消失。我对这套题最深的体会是它不像很多公司的笔试题那样拼记忆、拼题库而是通过题目引导你去思考“为什么”。比如为什么copy更安全为什么主队列同步任务会死锁为什么LRU适合图片缓存。这些“为什么”才是工程师的核心竞争力也是考试真正想找的东西。如果你想找当年的原题再练练手网上可以搜到不少校招笔试复盘帖但要把重点放在知识点本身而不是去背题目答案。技术面试和笔试说到底检验的是解决问题的能力不是背书能力。提前把知识体系梳理清晰远比刷多少道题更重要。最后再分享一个小技巧准备笔试的时候可以把你不会的每一道题做成一个“问题卡片”正面写题目背面写考点和思路每天抽15分钟过一遍。这个方法帮我快速建立起了完整的知识框架亲测有效。