2026/8/31 4:01:25

小红书iOS校招笔试题全解析:底层原理与高频考点复盘

小红书iOS校招笔试题全解析:底层原理与高频考点复盘 这几年校招季我身边不少准备iOS方向的同学都在翻一套题——小红书2020校招iOS方向笔试题卷二。提起这套卷子大家的评价高度一致难度不低但难得很典型。它不是那种靠刷LeetCode就能硬刚的纯算法卷也不是网上搜一份“iOS面试题库”背一背就能应付的套路题。这套卷子覆盖了Objective-C底层运行机制、Swift语言特性、内存管理与多线程、UI布局与事件传递、网络与存储等方方面面每一块都在考察同一个东西你有没有真正理解iOS开发的底层逻辑而不只是“用过没有”。我完整复盘过这套题也拿它给几个准备校招的同学做过模拟测试。今天这篇文章我就按模块把高频考点、易错细节、以及我当时分析题目时的思路完整拆一遍。无论你是正在备战iOS校招的应届生还是工作一两年想系统补底层基础的开发这篇复盘应该都能给你一些参考。1. 为什么校招笔试要单独拆出“卷二”从卷型设计看考察逻辑过去几年主流互联网公司的校招笔试普遍会按方向拆卷子通用的算法卷一册iOS方向、Android方向各自再出一册。小红书2020校招的iOS方向试卷也延续了这个思路。很多人不理解为什么算法卷之外还要单独出一套“方向卷”这不是增加负担吗实际上方向卷的考察目标和算法卷完全不同。算法卷筛的是“基础智商”和“编程功底”而方向卷筛的是“你进入团队之后能不能快速上手干活”。对于iOS方向来说卷二这类试卷真正想验证的能力有三层第一你是否理解iOS开发中常用机制背后的原理。比如消息发送到底怎么走、weak变量为什么能自动置nil、GCD里同步和异步的本质区别是什么。这些不是靠调用API能回答的必须对底层机制有清晰认识。第二你是否具备移动端特有的工程意识。客户端开发不是把界面画出来就结束内存占用、滚动流畅度、网络策略、数据持久化选型每一项都直接影响线上体验。方向卷会通过场景题来考察你有没有这种意识。第三你是否能在限定时间内把思路落到规范的代码上。笔试不是聊天所有分析最终都要写成可运行、可读性好的代码。能不能在紧张状态下保持代码质量本身就是一个重要的考察维度。所以我一直觉得这套卷二的设计逻辑很值得琢磨它没有追求冷门偏题所有考点都在日常开发中能遇到但每个点都往下挖了一层。这其实代表了校招笔试的一个趋势——不再考察“你知道多少API”而是考察“你能不能在真实业务里做出正确的技术决策”。2. 语言基础环节Swift与Objective-C混编语境下的高频失分题2020年正好处于Swift逐步成熟、但存量代码大量使用Objective-C的过渡期所以这套卷子在语言基础部分相当强调两种语言的混合理解。如果你只熟悉其中一种在这个模块会比较吃亏。我复盘时发现失分最集中的是下面几个点。2.1 Swift值类型与引用类型的边界题Swift里struct和class的区别几乎是每年必考的基础题。但真正的区分点不在于“一个在栈上、一个在堆上”这种粗糙记忆而是值语义和引用语义带来的行为差异。这套卷子里有一类典型题目是一个struct里包含一个class属性那这个struct是值类型还是引用类型答案是值类型但要注意struct本身在赋值或传参时会被复制内部的class属性不会深拷贝两个副本的内部对象仍然指向同一个实例。如果你在答题时只说“struct是值类型”没有点出内部引用类型对象的特殊性分数大概率拿不全。另一类常见的延伸题是Swift的Array和Dictionary在参数传递时为什么不会被意外修改答案涉及写时复制Copy-on-Write机制赋值时并不马上复制内存只有发生写入时才真正复制。明白这一点才能理解Swift为什么能在保持值语义的同时把复制性能控制在可接受范围。如果题目再进阶会让你判断某个网络请求模型应该用struct还是class。我的判断标准很简单作为不可变数据模型用struct需要被多处共享并修改状态、或者有继承需求就用class。把这个思路写清楚阅卷人就知道你不只是背概念。2.2 闭包捕获与循环引用闭包和循环引用是Swift入职笔试中最容易翻车的点。这套卷子里给过一段很经典的代码class NetworkManager { var completionHandler: (() - Void)? func startRequest() { completionHandler { self.handleResponse() } } func handleResponse() { print(done) } }问这段代码是否存在内存问题标准答案是completionHandler强持有selfself又持有completionHandler形成循环引用需要改成[weak self]。但笔试想看的往往不止这一个答案。更深入的追问是能不能用[unowned self]如果self在闭包执行前已经释放[unowned self]会导致崩溃而[weak self]会得到nil。所以网络回调、异步操作这类生命周期不确定的场景优先用weak只有当你确定闭包生命周期不会超过self时才考虑unowned。另外有一个细节值得特别注意Swift的闭包捕获发生在定义闭包的时刻而不是调用时刻。有些变体题会故意在捕获列表里写一个值类型变量然后在闭包定义之后修改它问你闭包打印结果是多少。如果不知道“捕获时机”这个点很容易凭直觉答错。2.3 Category与关联对象的坑Objective-C的Category是区分“会用”和“理解”的一个分水岭。笔试里出现频率最高的问法是Category里能添加属性吗标准回答是不能直接添加实例变量但可以通过关联对象Associated Object实现类似属性的效果。很多同学漏掉了后半句而这一半恰好是阅卷人想看到的。进一步还会考Category和Extension的区别。Extension在编译期决议是类的一部分可以添加实例变量和属性Category在运行时合并方法不能直接添加实例变量。还有一个容易被忽略的点分类方法最终会编译成category_t结构体在运行时通过runtime的加载逻辑合并到类的方法列表中所以分类中的方法可以被正常调用。如果题目问你“多个Category都实现了同一个方法哪个生效”答案是编译顺序决定的而且这属于未定义行为实际开发中应该避免。这类题目考的是工程判断力不是单纯的知识记忆。2.4 property修饰符与copy的底层逻辑几乎每一套iOS笔试题都会考property修饰符但这套卷子喜欢把问题往深处引。比如这道经典题NSString类型的属性为什么常用copy而不是strong背后的原因是如果把一个NSMutableString赋值给一个copy修饰的NSString属性copy会把可变对象复制成不可变副本后续外部再修改原来的可变字符串属性值不会跟着变如果用了strong属性会指向同一个可变对象外部稍作修改属性值就被改了容易引发数据错乱。从这道题延伸出去还会考NSArray与NSMutableArray的copy和mutableCopy区别。这里有一个很容易混淆的知识点对不可变数组执行copy得到的是浅拷贝同一对象对可变数组执行copy得到的是新的不可变数组而mutableCopy得到的是新的可变数组。但无论哪种拷贝数组里的元素默认都是浅拷贝不会被一起复制。这个细节建议自己在Xcode里跑一段代码验证一下比死记硬背牢靠得多。3. 内存与Runtime这套卷子里的“分水岭”考点如果语言基础题筛掉的是没好好复习的人那内存管理和Runtime这部分筛掉的就是停留在API层面的候选人。这套卷子在Runtime这块的出题密度相当高而且每道题都值得好好展开。3.1 消息发送机制与消息转发先看一个最经典的题型在Objective-C中调用一个方法底层到底发生了什么完整的回答链路是编译器把[obj doSomething]转换成objc_msgSend(obj, selector(doSomething))运行时通过对象的isa找到其类对象在类对象的方法缓存里查找该方法缓存未命中就去方法列表中查找还找不到就沿继承链继续向上查找如果最终没有找到就进入消息转发流程。这里有一个细节特别容易丢分消息发送的第一步不是查方法列表而是查缓存。objc_msgSend在性能上做了极致优化包括用汇编实现快速查找路径。如果题目里出现“查找顺序”相关的选择题很可能在“缓存优先”这个点上设坑。消息转发流程也是必考的重点完整阶段如下resolveInstanceMethod:允许动态添加方法实现forwardingTargetForSelector:把消息转发给另一个对象处理methodSignatureForSelector: 和 forwardInvocation:构造NSInvocation做完整转发以上都未处理调用doesNotRecognizeSelector:触发崩溃。答题时如果能把每个阶段举一个实际场景比如动态添加方法联系到dynamic属性在CoreData模型里的应用会明显提高分数。3.2 weak的实现原理weak指针是iOS开发中最常用、也最容易被低估的机制。这套卷子有一道问答题weak变量在对象释放后为什么自动变成nil参考回答Runtime维护了一个全局的SideTable里面存放着weak指针的弱引用表。当一个对象被释放进入dealloc流程时runtime会从弱引用表中找到所有指向该对象的weak指针把它们全部置为nil避免出现悬垂指针。再深一层面试官可能追问多线程环境下读取weak指针有什么风险SideTable内部有锁保护但读取weak指针时仍然要注意时序问题——对象可能在读取过程中被释放weak指针被置nil在极端情况下可能导致逻辑判断出错。这个问题能答出来说明你真的看过源码不是背的八股。3.3 ARC、循环引用和自动释放池ARC环境下循环引用几乎成了唯一常见的“内存泄漏”场景。笔试常见的判断题有block中对self的引用、NSTimer对target的强持有、delegate用weak还是strong、block对局部变量的捕获。我的答题原则是只要两个对象互相强引用并且外部已经无法打破这个环就是循环引用。NSTimer这个坑尤其典型。timer强持有targettarget又强持有timer如果不在合适时机调invalidate就会形成一条谁也无法打破的引用环。很多线上bug的根源就是这个笔试里拿出来考一点都不意外。自动释放池通常考的是autoreleasepool的使用场景。最典型的代码题是for循环中创建大量临时对象为什么应该在循环体内包autoreleasepool因为如果不包这些临时对象要等当前RunLoop周期结束才会释放在循环次数较多时内存峰值会明显上涨。包了之后每个迭代结束就能及时释放。这里还有一个进阶考点autorelease机制本身是一种延迟释放对象被放入自动释放池在池子销毁时才统一release。理解这个机制才能理解RunLoop和自动释放池之间的配合关系。4. 多线程与GCD常用不等于真正理解多线程是iOS方向笔试题的必考模块也是“实际用过”与“真正理解”差异最明显的模块。很多人写代码时会用GCD但要答清楚队列、死锁、线程安全这些底层细节没有系统梳理过很容易翻车。4.1 队列与任务的四种组合GCD最基本的分类是串行/并发队列以及同步/异步任务交叉得到四种组合。这张表建议烂熟于心组合是否开启新线程执行方式典型场景串行队列 同步任务不开逐个执行当前线程阻塞保护共享资源的临界区串行队列 异步任务开一般一次一条按顺序执行任务有先后依赖关系并发队列 同步任务不开依次执行但队列本身是并发的避免多线程同时操作同一资源并发队列 异步任务开可同时执行多个任务网络并发请求、大量独立计算这里最容易出错的点是“并发队列 同步任务”会不会开新线程。很多人误以为并发队列就一定会开线程但实际上同步任务不会开新线程它只是在当前线程上串行执行那些任务。决定开不开线程的关键因素是任务是同步还是异步而不是队列类型。把这个关系理清楚这四个组合基本就不会再混淆了。4.2 死锁的经典场景死锁题目在笔试里几乎是送分题但也是丢分题。最经典的一行代码dispatch_sync(dispatch_get_main_queue(), ^{ // 这里的代码永远不会执行 });原因很简单主线程正在执行这段代码它被同步任务阻塞等待block执行完而block又被提交到主队列上主队列要等主线程空闲才能调度它。主线程在等blockblock在等主线程形成死锁。变体题还包括在串行队列里嵌套dispatch_sync到同一个串行队列同样会死锁而同步到并发队列一般不会死锁。答题时最好把“谁在等谁”说清楚。这个分析的思路比答案本身更重要阅卷人看到你能拆解等待关系就知道你是真的理解而不是背过这道题。4.3 读写锁与并发控制在做多线程数据读写时最常见的方案是“并发队列 dispatch_barrier_async”读操作并发执行写操作通过barrier保证独占。这套组合既能保证读性能又能保证写入互斥是实际项目中很常用的设计。另一个高频考点是dispatch_semaphore。有一种典型题目现在有100个任务希望同时最多并发10个如何实现标准做法是创建一个值为10的信号量每个任务开始前wait结束后signal。这个方案在控制并发数、限流、资源保护等场景下都会用到属于必须掌握的基础工具。4.4 atomic并不等于线程安全“atomic修饰的属性是否是线程安全的”这个判断题几乎每年都会出现。标准回答是atomic只能保证属性的getter/setter操作是原子性的也就是读写这个属性本身不会出现数据竞争但它无法保证属性对象内部的变化也无法保证“先读后写”这个复合操作的原子性。举个例子一个atomic修饰的NSMutableArray多线程同时往里添加对象还是会崩溃或者数据错乱。因为addObject:这个操作不是setter/getteratomic管不到。正确的做法是加锁或者用串行队列配合不可变模型。笔试里能答到这个深度基本就能拿全这题的分数。5. UI体系与事件传递从渲染原理到响应链UI相关题目在笔试卷里通常占比较大比重因为客户端开发最终要落到界面上。这部分靠临时刷题突击效果有限需要在平时写界面时自觉思考“为什么这样写”。5.1 事件响应链这套卷子里有一道很经典的问题点击屏幕上的一个按钮系统是怎么把事件传递到按钮的完整回答分两段第一段是Hit-Testing。系统从UIWindow开始倒序遍历子视图调用hitTest:withEvent:和pointInside:withEvent:找到最合适的视图。这个过程中如果某个视图的userInteractionEnabled为NO或者hidden为YES或者alpha小于0.01系统会直接跳过它。第二段是响应链传递。如果这个视图没有处理事件事件会沿响应链向上传递从视图到父视图、到视图控制器、到window、到UIApplicationDelegate直到被处理。UIResponder是这条链的核心UIView、UIViewController、UIApplication都是它的子类。常见的进阶题是如何让一个超出父视图边界的子视图也能响应点击这需要重写父视图的hitTest方法让系统在判断时把子视图的坐标区域也考虑进去。这个知识点在做自定义控件时特别实用能答出来会加分不少。5.2 AutoLayout与约束冲突AutoLayout部分必考的知识点是约束冲突的排查思路。Xcode控制台输出“Unable to simultaneously satisfy constraints”时要能定位是哪几条约束冲突以及为什么冲突。笔试如果出代码题通常是给出几个约束让你判断布局结果或者要求写出等宽、等间距布局的约束写法。等间距这个需求最简单的方案是使用UIStackView。它从iOS 11开始已经非常成熟处理复杂排列布局比手写约束高效得多。但如果要兼容更早的系统版本就得用占位视图或者调整约束优先级来实现。这里有一个容易被忽略的点约束优先级不是摆设。用UILayoutPriority可以解决很多看似冲突的约束。比如两个约束都有需求但一个优先级更高系统会优先满足高优先级约束打破另一个。笔试里如果能主动提到优先级方案说明你对AutoLayout的理解超出了入门水平。5.3 离屏渲染与性能优化离屏渲染是UI性能的核心考点。笔试常问哪些操作会触发离屏渲染常见的有圆角加maskToBounds的组合、阴影、mask遮罩、图层光栅化。但要特别注意圆角本身不一定会触发离屏渲染。只有圆角和裁剪组合、且图层复杂到无法在合成阶段直接处理时才会触发。如果只是给一个UIImageView设置圆角系统有优化路径性能影响有限。这个细节最容易让人踩坑也最能在答题时体现水平。避免离屏渲染的常规实践包括用带圆角的图片替代动态裁剪、给静态内容开启shouldRasterize、为阴影明确指定shadowPath。把这些点答全工程经验就体现出来了。5.4 UITableView的复用与滚动优化UITableView复用机制的原理是tableView滚动时离开屏幕的cell会被放进复用池新进入屏幕的cell优先从复用池里取而不是重新创建。笔试可能会问如果注册cell用的是Class而不是Nib有什么区别为什么复用时需要Identifier标识更进阶的是性能问题。cell高度计算是滚动卡顿的大头所以需要估算行高、缓存行高、避免在cellForRowAtIndexPath里做复杂排版和大量图片解码。现在虽然有了DiffableDataSource等新框架但复用和高度优化依旧是iOS开发的底层功。这套卷子考这些本质是在考你有没有真正处理过复杂的列表页面。6. 网络、存储与工程化贴近实际业务场景的考察校招笔试的收尾部分通常用来考察候选人的工程化意识而不是单纯技术概念。网络请求、数据缓存、持久化选型这三块是最贴近App实际业务的也是判断“这个人入职后能不能很快参与项目”的有效手段。6.1 网络层设计思路一个项目里网络层做得好不好直接影响开发效率和问题排查速度。笔试里经常出现的开放题是如果让你设计一个网络层你会怎么设计一套完整的答案至少要包含这几个部分基于NSURLSession封装可配置baseURL、公共参数、超时时间请求模型与响应模型解耦使用CodableSwift或MJExtensionOC做模型转换统一错误处理区分网络错误、服务端业务错误、解析错误日志与监控方便排查线上问题缓存策略比如GET请求是否需要走缓存、缓存有效期多久。如果能再补充说明为什么选择NSURLSession而不是直接使用AFNetworking/Alamofire会显得比较有主见。合理的说法是第三方库封装了很多易用接口但项目网络层通常需要结合业务定制NSURLSession本身已经提供了足够底层的能力引入第三方反而会增加依赖和不确定性。当然如果团队更看重开发效率Alamofire也是合理选择。重点是你能说出选择的理由。6.2 HTTPS与安全校验关于HTTPS笔试常见考法是HTTP与HTTPS的区别、SSL/TLS握手过程、以及对称加密与非对称加密在握手各阶段的应用。能清晰讲出“非对称加密协商密钥、对称加密传输数据、CA证书用于校验服务器身份”这三点基本就能拿到这题的大部分分数。更进阶的问题是证书校验。App开发中常见两种方案系统默认校验和证书锁定。证书锁定的含义是客户端预置服务器的公钥或证书请求时主动校验服务器返回的证书是否匹配。这种方案能有效防止中间人攻击但一旦服务器证书更换客户端需要发版更新所以要谨慎使用。这部分内容考察的是候选人有没有真正处理过安全相关的需求而不是仅仅知道HTTPS比HTTP安全。6.3 数据持久化选型数据持久化的判断题通常是“在什么场景下选择什么存储方案”。我用一个表格来总结选型逻辑存储方案适用场景不适合场景NSUserDefaults轻量配置、用户偏好、开关标记大批量结构化数据文件Plist/JSON/归档中等量、非结构化的自定义对象需要复杂查询SQLite / FMDB数据量大、需要复杂查询和事务模型频繁变更的早期项目Core Data对象图关系复杂、需要模型层管理学习成本较高、调试稍复杂Keychain敏感信息如token、密码普通业务数据回答这类题时如果能说明“NSUserDefaults本质上是写入一个plist文件读取时会全量加载到内存”说明你真的理解内部机制而不是只会背API。笔试中这种“知其所以然”的回答往往比长篇大论更能打动阅卷人。7. 从笔试题到Offer复盘之外的备考路径最后聊一聊备考思路。这套卷子覆盖的知识面很广如果只在笔试前临时刷题效果通常不理想。根据我带过的同学和身边同事的经验下面这几条备考路径最有效。7.1 以源码阅读代替API背诵笔试里真正拉开差距的都是那些“你知道它存在但不知道它为什么这样实现”的点。比如weak自动置nil的底层、GCD死锁的原理、objc_msgSend的消息查找流程这些在官方文档里不会讲得很细需要去看开源代码或系统头文件。Objective-C的Runtime源码在Apple开源仓库可以下载Swift标准库也有开源实现。能把关键数据结构比如SideTable、method_list_t读明白笔试时就不怕题目变着花样出。7.2 用最小Demo验证模糊认知复习过程中如果遇到一个不确定的结论最快的办法是写一个小Demo去验证。比如想弄清楚copy与mutableCopy到底是不是深拷贝就写一段代码打印对象地址和内容跑一下看结果。几行代码的事但印象会比死记硬背深得多。我自己当年复习copy相关概念时就是靠一个十几行的命令行工程彻底搞明白的。一开始我以为copy一定是深拷贝结果发现不可变对象的copy根本就是同一对象当时挺意外但从此再也忘不掉了。7.3 笔试答题的顺序与策略笔试时间有限我的建议是优先做会做的题把确定性高的分数先拿到遇到不会的题先在草稿纸上写思路哪怕最后没写完代码也要把分析过程展示出来。很多阅卷人更看重思路的完整性而不是代码完美无缺。遇到选择题拿不准时先把明显错误的选项排除再结合自己掌握的机制推理正确率会高不少。尤其对Runtime和GCD相关题目很多选项就是针对常见误解设计的比如“并发队列一定会开线程”“atomic一定线程安全”这类排除掉这些错误选项剩下的往往就是正确答案。7.4 建立自己的错题笔记这个习惯我觉得是最容易被忽略但收益最高的。笔试后马上把错题和不确定的题整理成笔记写清楚题目、我的错误答案、正确答案、以及背后的原理。过一遍可能看不出效果但坚持一段时间你的知识体系会明显更完整。我见过不少同学刷题时只求“做对”做完就扔从来不总结。结果遇到同样的知识点换个角度考还是不会。错题笔记的核心价值在于逼你思考“我当时为什么错”这个思考过程比做十道新题都有用。我自己当年准备这类方向笔试题时最深的体会是iOS开发的知识面很宽但核心其实就那么几条线——语言、运行时、内存、并发、UI。每一条线往深挖一层都能串起一大片知识点。这套卷子最难能可贵的地方在于它逼着你去把散落的知识点连成网络而不是一个个孤立的API记忆。如果你正在准备校招建议把这篇复盘的模块当成checklist逐个攻破遇到卡住的点就动手写Demo验证。把这些都过一遍笔试稳了后面面试的技术深度也基本不会心虚。