小红书校招iOS笔试题考点解析:从内存管理到性能优化 📅 发布时间:2026/9/1 15:23:34 👁 浏览次数: 小红书2020校招iOS方向笔试题卷一在当年秋招那一批开源笔试题里算是含金量比较高的。我印象里不少同学提前准备时都会拿它做摸底因为它的考察点不是单纯堆iOS API而是把语言特性、系统机制、网络、性能、架构揉在一起试图在两个小时左右判断一个候选人有没有完整的iOS知识体系。当时我带过的几个实习生秋招前都会把这套卷子的考点过一遍再去刷LeetCode效果比盲目刷题好很多。这篇文章不打算逐题报答案因为笔试题目本身会随着年份调整而且很多公司也不公开官方标准答。我更想做的是把这套卷子里高频出现的考察方向、背后隐藏的技术原理、以及我答这类题的经验拆开来讲清楚。无论你是正在准备校招的应届生还是想借一套好题做基础复盘的在职开发应该都能从里面挖到一些东西。1. 整体试卷结构与高频考点分布1.1 笔试到底在筛选什么样的候选人很多在校生对笔试的理解有偏差觉得笔试就是翻书式地考知识点背一背就有分。实际上像小红书这类以内容社区为核心业务的互联网公司iOS岗位笔试更偏向工程能力筛选。它想看到的不是“你记住了什么”而是“你在遇到一个具体问题时会怎么思考、怎么选型、怎么落地”。这套卷一最典型的特点是主客观结合。客观题覆盖语言基础筛掉连底层概念都模棱两可的人简答题和编程题则专门用来暴露候选人的思路是否清晰。我记得有个高频问题是“解释一下weak修饰符”看起来很简单但真正答好的候选人能从weak表、运行时维护、循环引用三个层面展开而不只是说“weak引用不增加引用计数避免循环引用”。笔试的区分度就是这么拉开的。1.2 考点分布与备考优先级从多届同学回忆的题目信息来看这套卷子的考点分布大致可以归纳成下面这张表。如果你时间有限建议直接按照这个优先级去准备先抓主干再补边缘知识点。考察模块题型常见形式高频知识点优先级语言基础选择、判断weak/strong/copy、分类与扩展、Swift可选型极高内存管理选择、简答ARC底层、循环引用、autoreleasepool极高多线程选择、编程GCD队列、锁、线程安全、dispatch_group极高RunLoop选择、简答mode切换、事件循环、滑动卡顿高运行时选择、简答isa、消息转发、KVO原理高网络选择、简答HTTP缓存、TCP握手、DNS中高性能优化设计、简答卡顿优化、耗电、启动时间中高架构设计设计MVC/MVVM、组件化、依赖管理中高算法编程链表、字符串、LRU、二叉树中高这里面的规律很明显内存管理和多线程是必考核心RunLoop和运行时是拉开分数的地方性能优化和架构设计是考验工程思维的分水岭。把这张表吃透你就知道该往哪个方向使力了。2. 语言基础与内存管理核心题解析2.1 OC与Swift的常见出题方式这套卷子里语言基础题的数量不少比例上OC略多于Swift但Swift的分值也在逐步增加这和当时互联网公司iOS项目的技术栈过渡状态是一致的。OC题目喜欢考修饰符辨析、分类与扩展的区别、load和initialize的调用时机Swift题目则偏向可选型、闭包捕获列表、值类型与引用类型的选择。先挑一个最容易出错的点来说copy修饰符。属性声明为NSString时用copy基本是标准答案但你知道为什么吗因为传入的实参可能是一个NSMutableString如果属性是strong外部修改这个可变字符串时属性值也会跟着变这会导致UI显示被动改变或者数据被意外篡改。copy属性会把传入对象深拷贝成不可变字符串外部怎么改都不影响内部状态。这道题几乎所有候选人都会说“用copy防可变”但只有少数人能补上“因为属性本身就隐含了所有权策略和拷贝策略的组合语义”这就把一题答成了加分题。Swift部分有一个特别容易翻车的知识点struct和class的选择。比如题目问“定义一个RGB颜色类型用struct还是class”很多人第一反应是“颜色是对象用class”但其实颜色本质上是值两个颜色相同就是相同不需要引用语义。struct在栈上分配、值拷贝天然线程安全、还能通过值和值比较做出高性能逻辑在Swift标准库中也有大量应用。答题时如果能提到“值语义避免了对象被多处引用导致的状态混乱”分数会明显不一样。2.2 循环引用与ARC底层机制内存管理是iOS笔试的重头戏。这套卷子里关于ARC的选择题至少有四五道简答题也经常会问到Block循环引用。提到ARC很多候选人会直接说“编译器自动管理内存”但这个回答太笼统了面试官想要的是更底层的解释ARC本质上是在编译期往适当位置插入retain、release、autorelease调用同时在运行时维护weak表处理弱引用归零和autoreleasepool的自动释放。循环引用最经典的场景是Block捕获self。这里有个很隐蔽的细节如果self持有blockblock捕获了self的成员变量而非直接捕获self会不会形成循环引用答案是不会。Block捕获成员变量时等价于捕获self但它捕获的是对象本身还是对象的引用要看实际代码。如果你在block内部使用了self的成员编译器会默认强引用捕获self这时候即使你没有写“self”三个字就形成了self持有block、block持有self的循环。这个点经常以“ViewController持有一个block属性block中访问了self.view会造成循环引用吗”的形式出现答案是“会因为访问self.view等价于捕获self”。更好的补充是把修复方案写完整使用弱引用包装self在block内部先声明weakSelf如果还需要在block执行期间持续持有self再用strongSelf做一次强引用保护。这种先weak再strong的写法既避免了循环引用又能保证block执行期间self不会被提前释放。笔试中能把这一层逻辑写清楚的人基本就算掌握了内存管理的核心。2.3 autoreleasepool到底在什么时候真正发挥作用autoreleasepool也是这套卷子喜欢追着问的知识点。常见题目是“在for循环里创建大量临时对象内存会不会暴涨如何解决”。这个问题的正确回答要点有两个第一autoreleasepool的作用域决定了一个延迟释放对象的生命周期第二App启动后主线程会自动维护一个runloop级别的autoreleasepool但每次事件循环结束时才会自动释放一次如果循环体很大大量临时对象会堆积在池子里直到出池才释放。所以解决办法很直接在循环体内部手动创建一个autoreleasepool块让临时对象每次迭代结束时尽快释放。这里还可以补充一个性能细节ARC环境下局部变量会在作用域结束时释放但如果产生了autorelease返回值的对象还是要依赖池子。很多候选人能把“在循环里加autoreleasepool”背出来却说不清为什么要加这本身就是知识不扎实的信号。3. 多线程与RunLoop从原理到应用3.1 GCD队列、死锁与线程安全多线程相关的题目在这套卷子里占比很高而且往往不会直接问你“GCD有几种队列”而是把场景和队列组合在一起让你判断输出结果或是否死锁。比如“在主队列上同步执行一个任务会怎样”答案是死锁因为主队列是串行队列同步提交的任务会阻塞当前线程而当前线程正是主线程它没有机会去执行这个任务两边互相等待就卡住了。类似的题还有“并发队列中执行同步任务会创建新线程吗”答案是同步任务不一定会创建新线程如果某个并发队列正好有可用的线程资源同步任务会在那个线程上执行否则会等待。这个知识点在编程题中出现时常让你用信号量控制并发数量比如“同一时间最多只能有3个网络请求同时发起”。实现方式是用dispatch_semaphore创建value为3的信号量每个请求开始前wait结束后signal。笔试中能给出这种代码说明你不仅仅是记住了API还理解了GCD的调度模型。线程安全方面atomic和nonatomic的辨析是常客。atomic能保证属性的读取和写入是原子的但不能保证业务逻辑的线程安全。举个例子一个atomic的数组属性两个线程同时执行self.array self.array [newObject]结果依然是不可预测的因为读取和写入之间不是同一原子操作。真正需要并发访问的共享数据必须配合锁或者改用并发安全的容器。这套逻辑在笔试题里经常以“判断两个线程同时操作同一属性结果是否正确”的形式出现答案是否定的并且要解释为什么。3.2 RunLoop的Mode切换与滑动卡顿RunLoop是iOS笔试一个绕不开的硬核话题。这套卷子里有一道非常经典的选择题“滑动ScrollView时主线程处于哪种RunLoop ModeNSTimer会正常回调吗”。答案是滑动时RunLoop会切换到UITrackingRunLoopMode而默认添加的Timer是在NSDefaultRunLoopMode下注册的两种mode不兼容Timer会停止回调直到滑动结束页面又切回默认模式Timer才恢复。这个现象背后对应着RunLoop的源码执行顺序每次运行循环会先处理观察者事件再处理Source0、Source1然后处理Timer最后处理唤醒事件。滑动场景下源是触摸事件优先级更高因此Timer会被跳过。这种机制本身是为了保证滚动体验足够流畅但对我们做业务开发来说就带来了一个实际问题如何在滑动时保证定时任务依然执行。解决方案不外乎三种把Timer加入到UITrackingRunLoopMode、使用dispatch_source_t替代Timer、或者使用CADisplayLink保证逐帧回调。笔试中你如果能从Mode切分机制讲到Timer失准的原理再给出至少一种替代方案基本上就能在这次问答中站稳脚跟。3.3 并发任务组与通知回主线程的工程题历年版本里有一道很值得重复咀嚼的编程题有多个异步任务需要等全部完成后统一回调主线程更新UI。这道题看起来简单其实能拆出三个考察点任务并发执行的能力、等待全部完成的能力、以及回到主线程更新UI的线程安全习惯。最简单的答法是dispatch_group_enter配合dispatch_group_leave在每个任务开始时enter结束时leave再用dispatch_group_notify指定全部结束后的回调。这里有两个坑容易让候选人丢分第一enter和leave必须成对出现否则group永远无法完成第二notify在哪条线程执行不一定如果直接在里面操作UI可能引发线程安全隐患所以要用DispatchQueue.main.async包裹一层。难度升级一点的答法是用OperationQueue给每个任务封装成BlockOperation再用addDependency设置依赖关系配合maxConcurrentOperationCount控制并发数最后用completionBlock收尾。如果候选人能再提到Swift Concurrency中的TaskGroup和async let说明你已经有主动跟进新技术的习惯这在面试官眼里是明显的加分项。4. 网络、性能优化与实战题拆解4.1 网络层高频提问从HTTP到底层细节这套卷子里关于网络的题目不会特别深但覆盖面比较广。常见的是HTTP和HTTPS的区别、TCP三次握手的过程、DNS解析的耗时点以及HTTP缓存策略。这些知识点单独来看都不难但把它们串起来考察候选人网络基础是否扎实是出题人很喜欢做的事。回答HTTP和HTTPS的区别时不要停留在“HTTPS有SSL/TLS加密”这个层面要展开说明HTTPS在TCP握手之后还有一层TLS握手在这个阶段会完成证书校验、密钥协商和加密套件确认。另外HTTP/2的头部压缩和多路复用也可以作为补充但要根据问题的时间来决定展开深度。DNS解析题通常会问“为什么首次访问一个域名耗时较长第二次就变快”答案是DNS有本地缓存。更深一层要说明DNS解析过程可能涉及递归查询和迭代查询可能导致几十到几百毫秒的耗时。如果候选人能提到HTTPDNS和连接复用技术比如在移动端SDK中提前解析、预建立连接就展现了工程优化意识。4.2 性能优化设计题的答题框架设计题在卷一里通常以“如何优化性能”的形式出现。比如“列表滚动卡顿怎么排查和修复”。这种题必须拿出一个完整的排查链路而不是零碎凑点。我建议按序回答首先用Instruments做CPU和内存分析定位耗时的具体函数其次检查主线程是否被阻塞优先排查网络数据解析、图片解码、文件读写是否被同步执行然后分析UI层面的离屏渲染、自动布局冲突和图层混合问题最后通过监控FPS和卡顿时间检查优化效果。每个环节都要给可落地的优化手段。图片解码可以放到子线程解码完成后再回主线程渲染Auto Layout如果约束复杂可以考虑使用frame布局或预计算高度离屏渲染大量出现时可以检查是否对layer做了圆角裁剪、shadow和mask操作。如果你能在答题时提到“用CADisplayLink统计FPS、用Time Profiler定位热点函数”说明你不是纸上谈兵而是真的有调试经验。还有一道和耗电优化相关的题也很常见在一个LBS类App中怎样降低定位耗电量。合理的优化方向包括降低定位精度、减少回调频率、在App处于后台时关闭持续定位、以及根据业务场景动态切换定位策略。比“如何降低耗电”更值钱的回答是“我理解定位精度的需求是动态的所以我会设计一个策略管理器根据页面可见性和用户行为调整参数”这就是工程思维。4.3 这种“找错题”其实考的是工程习惯卷一里还有一种看似简单但也是拉开差距的题给一段有问题的代码让你指出问题并改进。这类题通常会故意放入以下几个坑在cellForRowAtIndexPath里做同步网络请求、在重复创建的闭包里使用strongSelf、在主线程上执行一个耗时文件读写。以同步网络请求为例最直接的改法是把它改成异步并配合数据源更新和UI刷新。但更好的回答是建议用网络图片框架来替代手动逻辑因为框架内部已经把缓存、解码、线程调度都做完了业务层只需要设置一个URL。这种“从造轮子到复用组件”的思路正是企业级开发中最看重的工程判断力也是这套笔试题想通过找错题观察的点。5. 架构设计与系统设计题的答题思路5.1 校招笔试为什么会考架构题架构设计题在笔试卷子里出现时很多同学第一反应是“这不是高级工程师该干的吗”。但从出题人的视角来看架构题真正考察的并不是你能不能写出一套微服务而是你有没有结构化拆解问题的能力。你可能没有架构经验但如果你能把一个大而模糊的问题拆成边界清晰的子模块本身就说明综合能力不错。小红书这套卷一中的架构题通常给出一个具体业务场景比如“为一个图片社区App设计图片缓存组件”。这种题回答的重点是定义组件职责、说明接口设计、明确缓存策略、考虑内存与磁盘的配合、以及处理并发访问和淘汰策略。你不需要用UML画图但思路必须严密最好从使用方视角出发先说明调用方如何获取图片再层层往里设计。5.2 一道启动优化设计题的完整答题模板如果题目是“如何优化App冷启动时间”一个完整的回答框架可以分四段pre-main阶段、main函数到didFinishLaunching阶段、首帧渲染阶段、启动完成后的异步任务阶段。每一段里都有不同的优化手段和取舍。pre-main阶段的优化核心是减少加载时间包括减少动态库依赖、合并类和分类、移除无用代码。main函数之后的阶段要关注SDK初始化顺序把非关键路径的初始化延迟到首帧之后。首帧渲染阶段要降低启动页复杂度、减少首屏I/O操作。异步任务阶段则要控制启动后立即执行的任务数量避免CPU抢占。这套框架本身已经很完整如果你能补充自己的实测数据比如“项目启动时间从1.8秒降到1.1秒”会更有说服力。校招同学如果没有实际项目数据也可以说“我做过一个Demo来验证xxx方案”但一定要说清楚实验条件和结果。5.3 组件化与依赖管理的概念题怎么答组件化相关概念在这类笔试题里也时有出现形式包括“Podfile的作用”“什么是私有Pod库”“如何解决模块间跳转耦合”。这类题想考察的是你是否理解大型项目的组织方式以及你是否有工程抽象能力。组件化的核心目标是解耦和复用。模块之间不直接依赖而是通过中间层通信。常见的落地形式包括URL路由、Target-Action、Protocol-Class以及基于CocoaPods的私有库管理。答题时不要只罗列方案名称要说明每种方案对应的场景URL路由适合页面间解耦Target-Action适合动态化Protocol-Class适合服务抽象。能讲到这一层说明你已经不是只知道概念而是理解方案背后的工程动机。6. 算法题与笔试时间分配策略6.1 算法题如何做到保分与冲分结合算法题在卷一中的占比不是最高的但却是很多同学最焦虑的部分。我的建议是目标分层基础题要拿稳中等题要能写完整难题不要死磕。基础题包括字符串反转、判断回文、链表反转、二叉树层序遍历中等题包括LRU缓存、最长公共前缀、两数之和变体难题则可能是动态规划或图相关没有十足把握可以跳过把时间留给简答题。刷题方法也要分阶段前两周重点过一遍数据结构后两周刷高频题并练习手写代码。笔试环境往往没有自动补全和语法提示所以手写能力至关重要。我建议你在刷题时不要依赖IDE直接在网页编辑器或者纸笔上先写一遍再放到编译器里验证。这样到了笔试现场遇到没见过的题也不会慌。6.2 高频算法题的参考答案示例以链表反转为例这是校招iOS方向笔试中出现频率极高的一道基础题。这道题考的是指针操作的熟练度和临界条件处理本质上是一种“反向遍历构建新链表”的思路。func reverseList(_ head: ListNode?) - ListNode? { var prev: ListNode? var current head while current ! nil { let next current?.next current?.next prev prev current current next } return prev }另一个值得手写到滚瓜烂熟的是LRU缓存。它同时考察了哈希表、双向链表和算法设计能力。我在培训候选人时经常说LRU是一道“性价比”很高的题因为一旦面试官在笔试题或面试题里看到你能干净地实现它对你的算法素养会有明显加分。class LRUCache { private var capacity: Int private var dict: [Int: Int] [:] private var order: [Int] [] init(_ capacity: Int) { self.capacity capacity } func get(_ key: Int) - Int { guard let value dict[key] else { return -1 } if let index order.firstIndex(of: key) { order.remove(at: index) } order.append(key) return value } func put(_ key: Int, _ value: Int) { if dict[key] ! nil { dict[key] value if let index order.firstIndex(of: key) { order.remove(at: index) } order.append(key) return } if dict.count capacity { let leastKey order.removeFirst() dict.removeValue(forKey: leastKey) } dict[key] value order.append(key) } }这个简化版用数组代替了双向链表胜在逻辑清晰。如果笔试时间充裕可以把双向链表版本写上并说明为什么会比数组版本更优数组删除任意位置元素的时间复杂度是O(n)而双向链表可以在O(1)时间内完成移动和删除。6.3 90分钟卷子的时间分配实例以90分钟为整卷时长来算我建议这样分配前5分钟通读全卷把题目按“会做、有点难、没思路”分类客观题控制在20分钟简答题30分钟编程题30分钟最后留5分钟检查关键得分点。具体执行时会遇到很多意外比如某道简答题突然卡住这时不要恋战画个图写下关键词先跳到下一题。编程题如果写了一半发现思路错了不要急着删代码可以在旁边注释里补充正确的方案阅卷人看到你思路清晰还是会有印象分。笔试既考技术也考心态稳住节奏比多拿一两道难题更实际。7. 备考这套卷的常见问题与避坑实录7.1 做题时最容易翻车的几个点第一年是全真练习时我发现候选人最常翻车的地方不是记忆类知识而是判断类题目里的“陷阱字眼”。比如选择题问“关于block的描述哪个是正确的”很多选项本身是对的但放在特定上下文里就变成错的比如“block可以修改外部局部变量的值”这句如果不加限定词严格来说是错的因为没有__block修饰时不能修改。这种题考的不是背概念而是概念的边界。第二个翻车点是简答题答得太短。很多同学以为笔试阅卷人看关键词其实人工阅卷更看重逻辑链条。比如问“为什么NSTimer在滑动时会暂停”你写“因为runloop mode切换”只能拿一半分能补充说明主线程从默认模式切换到追踪模式、而timer注册在默认模式下导致它得不到回调机会才能拿满。第三个翻车点是编程题不写注释。笔试阅卷人看代码时会在脑内模拟执行没有注释的代码很难快速理解。你哪怕写两行“这里获取下一个节点”的注释都能有效降低阅卷成本这也是工程习惯的体现。7.2 我认为最靠谱的备考路径如果你有三个月时间我建议这样分配第一个月把语言基础和内存管理彻底吃透所有修饰符、所有循环引用场景、所有生命周期相关知识点全部整理成表格每天过一遍第二个月集中攻多线程和RunLoop配合Demo实验验证结论比如自己写一段代码观察runloop在滑动时的mode变化第三个月进入实战刷题阶段每天一套模拟卷加两道算法题重点训练手写代码和答题节奏。备考过程中不要只刷题不看原理尤其不要死记答案。你可以把每道题背后涉及的知识点拆成一篇两三百字的小笔记比如“从一道block题延伸到runtime消息转发”这样刷完10套题你手里就有了一份属于自己的知识图谱。这比收集10份标答更能提升综合能力。根据我带过的实习生和身边朋友的经验这套小红书2020校招iOS方向笔试题卷一最大的价值不是当成题库来背而是当作镜子来用。你把卷子做一遍对照答案解析复盘一遍就会发现哪些知识点你以为懂了但实际说得不透。把每一个“模棱两可”变成“能解释清楚”比多刷十套题更有效。希望你也能借这套题搭建起属于自己的iOS知识体系。