小红书iOS开发岗笔试复盘:从八股到编程题的完整攻略 小红书的笔试邮件是周五晚上到的标题写着“2023秋招-小红书-iOS开发岗-第二批笔试”点开一看赛码网链接、限时120分钟、单选多选加三道编程题。说实话看到“第二批”三个字的时候心里反而踏实了一点——至少说明第一批已经筛过一轮题库的调性和难度多少能通过面经摸个大概。这篇文章就把我这次笔试的完整复盘写下来从考前怎么收集信息、选择题八股怎么准备到三道编程题的真实思路和考场上的时间分配尽量还原当时的选择和判断。如果你也在准备大厂移动端开发岗的校招笔试或者正在纠结“iOS开发岗笔试到底考什么”这篇应该能帮你少走不少弯路。1. 笔试前的信息收集与整体思路1.1 读懂笔试通知里的三个关键信息收到笔试邮件以后我做的第一件事不是马上刷题而是把邮件里能提取的信息全部列出来。大厂的校招笔试通知一般包含三个关键信息笔试平台、考试时长、题目构成。这三个信息直接决定了你的复习策略和考场策略。我这场邮件里写得很清楚赛码网在线笔试限时120分钟题型包括单选、多选和编程题。这就很关键了。赛码网这个平台和牛客网不完全一样它的代码编辑器不支持你本地调试完再粘贴——严格来说是可以在线编辑和运行但很多公司的赛码笔试会把“跳出页面”行为记录下来哪怕你是切到本地IDE也会被判定为可疑操作。所以考前我特意去赛码网熟悉了一下界面跑了一道快排确认它的输入输出格式和牛客常见的写法一致标准输入输出按行读取这个准备工作后来帮了大忙。从这一批的题目构成来看小红书iOS开发岗的笔试不是纯算法题选择题占了相当大的比重而且不全是iOS技术题混合了数据结构、操作系统、计算机网络和一部分Swift/Objective-C的语言特性题。这意味着你如果只刷LeetCode选择题部分大概率会翻车反过来如果你只在iOS八股上花大力气编程题又会拖后腿。两边都要抓但优先级可以分开。1.2 “第二批”笔试的特殊性在哪里第一批笔试一般在秋招正式批前一两周放出来第二批和第一批的题目很少完全一样但存在明显的“互文关系”——同一个知识点会换着考法出现。比如第一批考了RunLoop的Source1事件处理第二批很可能就考autoreleasepool的释放时机第一批考了Swift的struct与class区别第二批就有可能考闭包的捕获列表。这意味着一个很实用的策略在笔试前去牛客、讨论区、一亩三分地翻一遍第一批笔试的面经注意不是手动搜而是通过平台的面经帖。我当时花了一个多小时整理第一批出现过的考点发现高频考点集中在内存管理、消息传递、GCD、KVO这些iOS核心机制上算法题则以字符串处理、DFS/BFS、动态规划为主。第二批的题目出来后虽然具体题目变了但考点的分布并没有跳出这个框架证明这个策略是成立的。另外第二批笔试通常还承担了一个功能——补录和调整批次。有些同学通过了第一批笔试但面试没发挥好可能会被重新安排到第二批后面批次继续流程也有部分在第二批笔试中表现特别突出的会被优先安排面试。这虽然是我推测的但从整体秋招节奏来看笔试成绩依然是进入面试环节的核心门槛。1.3 我的准备清单和知识优先级划分距离笔试大概还有六天的时候我列了一张复习优先级表按“必考且高频”“必考但相对低频”“可能考但不好押”三档来安排时间。第一档是操作系统的进程线程、死锁产生条件、虚拟内存页面置换算法计算机网络里的TCP三次握手、四次挥手、HTTP/HTTPS区别、DNS解析流程。这些内容不只是选择题爱考编程题里的多线程并发场景也会用到属于必须拉满的基础功底。第二档是iOS专项知识。我把Apple官方文档里的内存管理章节、自动释放池的运行机制、RunLoop的源码分析尤其是CFRunLoopRunSpecific和__CFRunLoopDoObservers这两段、GCD的队列与任务关系、KVO的底层实现原理重新过了一遍。这些内容是选择题里最能拉开差距的部分也是面试时肯定会追问的点。第三档是Swift语言特性比如optional的底层实现、map/flatMap/compactMap的区别、值类型和引用类型在Array和Dictionary嵌套时的行为差异、闭包的逃逸和非逃逸。这些知识点考察的方式比较灵活但换汤不换药理解原理比死记结论有用得多。算法方面我没有再花大时间刷难题而是把LeetCode的Top 100热题中easy和medium难度的经典题按类型重刷了一遍哈希表、双指针、滑动窗口、DFS/BFS、二叉树遍历、简单DP。大厂校招笔试的编程题一般不会超过medium上限但会放在一个场景化的包装里所以读懂题、识别模式的能力比闷头做出难题更重要。2. 选择题部分八股文怎么考考点全拆解2.1 通用技术题数据结构、操作系统与网络选择题一开始就是一道数据结构题考的是“给定一个数组用堆排序找出最大的K个数时间复杂度是多少”。这题算是送分题答案是O(nlogK)但选项里放了O(nlogn)、O(nlogK)、O(nklogK)、O(n^2)来混淆。这里面有个坑就是如果你只记得堆排序整体复杂度O(nlogn)很容易选错。找最大K个数时堆的大小被限制在K所以每步操作用的是logK的代价整体就是nlogK。但如果你直接对整个数组建堆再取K个那才是O(nKlogK)甚至O(nlogn)的范畴。别小看这个区分很多人在考场上就是栽在这种单位运算量的细节点上。操作系统题目里有一道考死锁的四个必要条件——互斥、持有并等待、不可剥夺、循环等待。多选题问“下列哪些方法可以破坏死锁的循环等待条件”。选项包括“资源一次性分配”“按序分配资源”“允许资源剥夺”“允许进程互斥访问资源”。正确答案是B和C。按序分配资源破坏了循环等待允许剥夺破坏了不可剥夺条件而资源一次性分配破坏的是持有并等待不是循环等待。这种题目如果不能从原理上理清每个条件对应的破法很容易多选或少选。我的经验是把每个条件和对应的破坏手段绑成一个记忆组考场上按组对号入座不要凭感觉选。网络题考了TCP的拥塞控制——慢启动、拥塞避免、快重传、快恢复。具体题目是“TCP发送窗口的增大过程由哪些机制控制”这题本身不难但选项里混进了流量控制的“接收窗口”。注意了TCP的滑动窗口有两个维度一个是接收方通告的收窗口控制的是流量另一个是拥塞窗口控制的是拥塞。两者取小才是最终的发送窗口。如果你在复习的时候没有把这两个概念彻底分开这种复合题就是最容易被绊倒的地方。还有一道DNS的题目输入一个URL后浏览器从输入到渲染的完整链路中DNS解析发生在哪一步。这个考察的是HTTP请求的完整流程并不是只考DNS。其实大厂笔试里网络题经常这样出表面考一个点实际是把整个知识链串在一起看看你知不知道每一步的前后顺序。2.2 iOS专项RunLoop、内存管理与消息传递iOS部分的第一个重头戏是RunLoop。有道题问的是“在main runloop中把耗时任务放到哪个模式下运行不会阻塞界面滑动”选项包括NSDefaultRunLoopMode、NSRunLoopCommonModes、UITrackingRunLoopMode、NSModalPanelRunLoopMode。答案是UITrackingRunLoopMode。这个知识点如果你只是背结论很容易忘记它背后的原因——因为UI事件和滑动事件是在UITrackingRunLoopMode下处理的如果耗时任务默认在DefaultMode跑滑动的瞬间系统要把RunLoop切换到TrackingModeDefaultMode下的任务就会被打断所以不会阻塞界面。但如果你把任务主动放到TrackingMode滑动过程中任务和UI事件都在这个模式下执行那就会造成卡顿。所以这道题问的是“不会阻塞滑动”而不是“既能执行任务又不卡顿”审题很关键。内存管理考的是一道MVVM场景下的循环引用题——delegate、block、NSTimer这三种情况中哪些会造成循环引用。选项有UIView的delegate、Block被self持有且Block内部又强引用self、NSTimer在dealloc里没有invalidate、UIGestureRecognizer的action。正确答案是Block和NSTimer。delegate之所以不是是因为UIView的delegate通常是weak属性assign也可以但一般用weak而Block里捕获self如果没有用weakify会形成self持有block、block又持有self的环NSTimer本身持有targettimer又被self持有的话环就出现了。这里有一个额外的好习惯NSTimer应当在合适时机invalidate最好不是dealloc里而是在viewWillDisappear或者具体业务结束的时候避免在视图已经销毁后timer还在跑。消息传递这部分的题集中在“调用一个对象的某方法时如果该方法未实现会经历怎样的流程”。这几乎是大厂iOS笔试的常驻题答案应该包括objc_msgSend查找、动态方法解析resolveInstanceMethod、快速转发forwardingTargetForSelector、完整转发methodSignatureForSelector和forwardInvocation最后才抛unrecognized selector。考这种题的重点并不是让你把步骤背出来而是看你能不能答出每一层的作用和触发条件。2.3 Swift语言特性与iOS开发周边技术点Swift的题里有一道考闭包捕获列表的题目给了一段代码代码里闭包内部用了一个变量外部已经改成另一个值问闭包执行时打印的是什么。如果不理解捕获列表的本质——它在闭包创建时对变量做了一次快照——就很容易把“闭包执行时去读当前值”的错误直觉带进来。还有个经典坑是用strong和weak对比捕获self和局部变量的区别直接映射到内存管理的循环引用问题。还有一道关于struct和class的题考察点是在函数传参时的赋值行为差异。struct作为值类型传参时发生拷贝修改函数内部的参数不会影响外部class作为引用类型传参传递的是引用两者行为相反。这题看起来简单但它换了一个包装——把struct和protocol、泛型放在一起考问泛型约束为struct时传参是什么行为实际上还是在考最基本的语义。iOS开发周边技术题里考了分屏适配和App启动流程。分屏相关的那道题问的是iPad分屏Split View状态下viewSafeAreaInsetsDidChange方法会被触发几次以及如何适配不同宽度下的布局。这个考察的是对多任务环境下尺寸变化的敏感度。App启动题是问从点击图标到delegate回调didFinishLaunching中间经历了哪些步骤——dyld加载动态库、ImageLoader解析、objc初始化、UIApplicationMain这些属于iOS开发进阶题。这道题我没有押中但好在我之前读过一篇讲启动流程源码分析的文章勉强能答出来。2.4 多选题的“宁缺毋滥”策略小红书这次的笔试有多选题而且多选题的计分规则写得很清楚漏选得部分分错选不得分。这意味着你在考场上必须调整策略——拿不准的选项不要选宁可少拿一点分也不能整题丢分。我当时遇到了一道多选问的是“以下哪些操作会导致离屏渲染”选项包括添加圆角、masksToBounds、阴影、layer.shouldRasterize设为YES。圆角和阴影都会触发离屏渲染shouldRasterize是开启离屏渲染缓存严格来说是先把内容渲染到offscreen buffer再复用所以也算。但是masksToBounds本身并不会产生离屏渲染它是被设置圆角时一起使用才导致圆角裁剪单独设置masksToBounds是图层裁剪并不触发离屏渲染。这道题我就保守地选了圆角和阴影因为shouldRasterize这个到底是“触发离屏渲染”还是“处理离屏渲染后的结果”在不同语境下存在争议不该在这个选项上赌。多选里面还有一个重要的技巧——绝对化的选项多数是错的。比如“所有对象创建都要在堆上”“delegate必须用weak修饰”“Block内修改变量必须加__block”这类带“所有”“必须”的选项基本都需要打个问号。尤其是在Swift和Objective-C混编的场景下规则往往有例外看到绝对化表述时要格外警惕。3. 编程题部分三道题完整复盘3.1 编程题第一道字符串处理哈希表第一道编程题大概是这样的给定一个字符串找出其中所有长度为k且没有重复字符的子串数目。比如字符串是“abcabc”k是3那“abc”是符合的“bca”也是“cab”也是答案就是3。这题属于典型的easy到medium过渡题核心解法是滑动窗口固定窗口大小每次加入一个新字符移除一个旧字符同时用一个哈希表统计窗口内字符的出现次数。注意这里不能用set因为窗口滑动时移除的字符可能仍然出现在窗口的其他位置所以必须用计数方式。我在实现时直接用了一个字典或者数组来存字符频率当窗口内最大频率小于等于1时说明没有重复字符然后计数加一。写这题的第一版时我直接用了一个哈希表统计窗口函数在每次for循环里重新计算窗口内字符频率这样时间复杂度是O(nk)虽然对于小数据量能过但如果字符串长度到10^5量级就会超时。这个是我在第一版提交后立刻意识到的问题改正后的写法把窗口外循环和字符移除操作合在同一层循环里总体复杂度降到O(n)。笔试的在线评测系统对超时判断很敏感所以你写完后最好自己脑内跑一遍边界数据而不是直接交卷。3.2 编程题第二道DFS/BFS网格类搜索第二道题是一个经典的网格类搜索题在一个由0和1组成的二维矩阵中计算岛屿的个数。1表示陆地0表示海洋岛屿是上下左右相连的陆地。这题就是LeetCode 200的岛屿数量在笔试里出现的频率非常高几乎每个做校招内容的人都刷过。我的解法是遍历每个格子如果遇到一个未被访问的“1”就从这个点出发用DFS把所有相邻的“1”标记为已访问岛屿数量加一。实现上有两种常见的标记方法一种是用visited的二维数组记录另一种是直接在原数组上把访问过的“1”改成“2”省掉额外空间。我用的第二种因为在线笔试环境不会复用你的原始数据直接原地修改更简洁也更快。这里有一个值得说的坑DFS用的是递归函数递归深度在极端情况下会达到矩阵格子总数比如矩阵是1000×1000且全部是“1”递归调用层数会到100万层有些在线判题环境会直接栈溢出。所以更稳妥的做法是用BFS或显式栈实现DFS。我当时因为时间紧张直接写了递归DFS后来提交前自己检查了一下果断改成BFS用队列实现。这道题在批量提交时会有大量测试用例栈溢出的代价是整题判0分所以不要在递归深度上赌运气。3.3 编程题第三道动态规划与贪心的综合分析第三道题是我觉得整套试卷里最拉分的一道考的是一个类似“打家劫舍”变种的问题一排房子每个房子里有一定数量的金币但是相邻两个房子不能同时被抢求能抢到的最大金币数量。这题的核心是动态规划的状态转移方程如果用dp[i]表示抢到第i个房子时的最大金币数量可以分成两种情况要么不抢第i个房子那么最大金币是dp[i-1]要么抢第i个房子那么第i-1个房子不能抢最大金币是dp[i-2] nums[i]。取二者最大值。边界条件就是dp[0]nums[0]dp[1]max(nums[0], nums[1])。之所以说它拉分是因为题目在外面包了一层比较复杂的场景描述讲了一个“小偷在一个环形街道上作案”的故事如果你没有识别出它其实是“不能抢相邻房子”这个模型很容易被绕进去。我看到环形两个字一开始也愣了一下后来把“环形”拆成两种情况要么不抢第一家要么不抢最后一家分别跑一次线性DP取最大值就解决了。环形问题转线性问题是DP题里最常见的一个变形思路如果复习时专门练过环形数组的题目这道题就是白给的。3.4 编程题的时间分配与调试策略笔试120分钟我大概用了40分钟做选择题10分钟检查选择题剩下70分钟全砸在编程题上。三道编程题的难度是递增的第一道简单第二道中等偏易第三道中等偏难。我的策略是先把第一道题做完并确保通过第二道题争取半小时内完成第三道题留30分钟。这个分配在真实考试里非常管用因为第一道题如果做不出来后面心态容易崩而第三道题即使做不出来前面的分数也已经足够让你进入下一轮。代码写完以后我的调试顺序是这样的先看一遍有没有语法错误再看变量名是否拼错然后脑内过三组测试数据——最小边界比如空输入或只有一个元素正常输入最大规模数据。这三个测试都过了再提交。有一次我第一道题第一次提交后显示“运行错误”没有任何输出信息我立刻怀疑是数组越界检查后发现是窗口右指针在最后一轮循环里多走了一位修掉就过了。赛码网的错误提示可能比较模糊所以你自己要有能力从报错位置反推问题。4. 考场环境、平台细节与常见问题处理4.1 赛码网在线笔试的使用注意事项赛码网这几年在大厂校招笔试里出现频率很高它有几个特点你必须提前知道。第一个是代码编辑器的体验和本地IDE差得比较多没有智能提示缩进处理也偶尔会怪异。所以平时刷题如果一直依赖IDE的自动补全一定要提前在赛码网或牛客的在线编辑器里练几道题把常用函数名和语法结构都变成肌肉记忆。第二个是输入输出格式。赛码网的编程题一般用标准输入输出文件头不一定有模板。我当时第一道题读的是字符串直接用了input()第二道题读二维矩阵的时候因为输入格式是先给两个整数m和n表示行列数我一开始按照每次读取一行字符串然后直接split发现第一行数字被当成数据了。后来改成先读第一行得到m、n再循环读取后面m行问题才解决。这个过程看似简单但考场上如果卡在这里浪费十分钟很影响心态。第三个是防作弊系统。赛码网会记录你切出浏览器窗口的行为切换次数多了会被标记为异常。所以不要试图切换到本地编辑器、聊天工具或笔记软件查资料。我考前把所有常用的API和模板写在脑子里或者提前用备忘录打开一个无关紧要的窗口放着千万不要在考试过程中切走。虽然真要看的话摄像头也可能拍下来但风险完全没必要承担。4.2 网络中断、摄像头异常和页面崩溃的应急预案在线笔试最怕的不是题目难而是设备出问题。我这场笔试开考大概二十分钟后有一次我在选择题上停留太久系统突然弹出一个“注意力检测”的提示意思是检测到我长时间没有操作问我是否还是本人作答。我当时心跳都加速了赶紧动了一下鼠标继续往下做。考后才明白这是平台在防止有人离开电脑去查答案所以设定了一个无操作时限。你在考场上如果遇到这种提示千万别慌按一下任意键继续即可不需要做额外操作。如果真的遇到网络断线赛码网一般会保留已提交的内容但长时间断线可能导致系统判定退出。建议在考前把网络换成一个稳定热点或者有线网络并且保证电脑不要进入睡眠模式。我当时把电脑的电源策略改成了“永不睡眠”这个细节可能没用上但绝对值得做。如果页面崩溃第一时间截屏保存你写过的代码内容然后强行刷新页面重新进入考试。很多时候进度会被保留但如果没保留你至少还能根据截屏重新打字不至于从零开始。4.3 笔试后的复盘思路与心态调整笔试结束后的两三天是最焦虑的因为结果往往一周左右才出。我复盘自己这场笔试时主要看三个维度一是有没有在选择题上因为自己熟悉的知识点丢分二是有没有在编程题上因为边界条件或者数据范围判断失误而丢分三是整体时间分配是否合理。这次我比较满意的是没有在一道题上死磕。第二道DFS题我已经写好了递归版但在提交前我花了几分钟评估递归深度风险果断改成BFS这个决策在事后看是保命的。如果当时图快直接交递归版一旦后台有超深网格的测试数据整道题就是零分。另一个教训是多选题的保守策略让我稳稳拿到了部分分而不会因为贪多错选丢掉整题。心态上我也想说一句大厂笔试的批次并不能直接说明你的水平只是招聘流程的一个时间节点。第二批题目确实会比第一批稍微灵活一点但不代表更难很多时候只是换了包装。考完试我发现自己有很多选择题是靠直觉和平时积累的“肌肉记忆”答出来的而不是靠考前突击这反而说明长期阅读源码和文档的习惯比临时刷题更有价值。4.4 后续面试可能会追问的知识点预测笔试和面试之间有很强的关联性。笔试中考到的知识点面试官大概率会沿着这些问题继续展开追问。比如笔试考了RunLoop模式下任务不阻塞滑动面试时很可能就会让你手写一个不卡顿的滚动列表优化方案涉及异步绘制、预排版和runloop空闲时机的利用笔试考了循环引用面试时就会让你讲一讲delegate、block、NSTimer和闭包捕获各自的循环引用风险以及weakifystrongify的底层实现原理。我在笔试后的准备里把笔试中没把握的题都重新过了一遍重点看了“离屏渲染的优化”“Swift闭包捕获列表与循环引用的关系”“环形DP状态转移的推导过程”这三个方向。后来面试时果然被问到离屏渲染的优化我当时能比较自然地回答圆角shadow的坑、shouldRasterize的适用场景、以及用Core Graphics绘制圆角图来代替系统圆角裁剪这些都跟笔试中那道多选题直接相关。这算是一个很典型的“笔试为面试画重点”的例子所以考完试绝对不能把试卷丢到一边复盘才是把笔试价值放到最大化的阶段。再补充一点笔试的编程题往往会成为面试的算法题素材。我这次笔试的第三道环形DP在后续面试中虽然没有原题出现但面试官变形成了“环形街道抢金币且街道两端不能同时抢”的口述题需要现场推导状态转移方程。因为我在笔试后彻底理解了环形转线性的本质所以面试时只用了两三分钟就把思路讲清楚了。这就是笔试复盘的实际回报它不只是一次筛选也是在给你提前做面试训练。5. 写在最后的一点实操体会现在回看这场笔试我最大的感受是iOS开发岗的笔试不像后端那样把算法作为唯一王炸也不像纯客户端面试那样只看项目经验它处于一个衔接的位置——通用基础算法、操作系统、网络和客户端专项技术iOS原理、Swift特性、渲染机制各占一半。你如果只刷算法选择题会暴露你的技术深度不足你如果只抠iOS源码编程题又会让你连门槛都迈不过去。平衡这个关系是准备这类笔试的核心策略。最后分享一个小技巧。笔试前我把常用的Swift和Objective-C代码片段、RunLoop源码中的关键路径、GCD常用的并发模式都整理成了几页纸但不是为了在考场上翻阅而是为了在复习的最后一天闭眼默写一遍。默写不出来或者卡壳的地方正好就是你的薄弱点考前一晚补上它们比乱刷十道题管用得多。这个方法我后来在面试前也在用效果很稳定推荐给正在准备校招笔试的你。