经常有准备校招的同学问我网易iOS开发工程师北京的笔试卷到底是在考什么我前前后后帮团队筛过不少iOS校招笔试卷“网易2018校园招聘iOS开发工程师(BJ)笔试卷”这类卷子我现在还会拿来当复习蓝本。它不考偏门操作而是把Objective-C语言基础、内存管理、多线程、网络、算法和系统设计这几块内容结结实实地串了一遍。这篇文章我会把这类笔试题的题型分布、每类题背后的考点以及我当时准备校招的思路完整梳理一遍不只给答案更把“为什么这么答”讲清楚。1. 笔试卷整体画像一份校招笔试卷的考察逻辑1.1 题型分布与分值节奏网易2018校园招聘iOS开发工程师(BJ)笔试卷采用的是线上限时答题模式整场时间一般在90到120分钟之间。我根据历年校招iOS笔试的常规形态把卷型结构做了一个还原方便你建立整体认知题型题量考察能力建议分配时间单选/多选题1015题OC语言基础、内存管理、对象生命周期15分钟简答/程序阅读题58题Runtime、Runloop、KVO、多线程原理20分钟算法编程题23题数组、链表、字符串、二叉树40分钟方案设计题12题图片缓存、IM消息、性能优化30分钟这个分值分布是有内在逻辑的。选择题能快速筛出基础是否扎实比如strong和copy的区别、atomic到底原子了什么、GCD死锁的经典场景这些不需要长篇大论会就是会不会就是不会。程序阅读题看的是你对系统框架的理解深度像“这段代码在子线程更新UI会出现什么结果”这种题靠背面试题很难应付必须真正理解Runloop和主线程的关系。最后两道设计题才是拉开差距的地方大部分人的答案停在“用SDWebImage就行”而高分答案会主动拆解内存设计、磁盘策略、缓存淘汰机制和弱网降级方案。1.2 从岗位JD反推考点你如果翻过网易当年的iOS开发工程师校招JD会发现核心要求翻来覆去就几条熟练掌握Objective-C理解iOS应用生命周期和内存管理有良好的数据结构和算法基础有实际项目或实习经验。笔试题目其实就是在验证这几条。所以2018年的笔试不会刻意追求新框架更不会拿SwiftUI或最新的Swift并发特性来考你因为校招生普遍缺少这部分实战积累。整份卷子真正想确认的是你有没有把iOS最底层的那套机制吃透ARC是怎么维护引用计数的、Runtime发消息走了哪条链路、Runloop在App启动和交互过程中扮演什么角色、KVO的底层实现是不是只靠addObserver就完事了。这些东西在简历上都能写“掌握”但笔试一出手就知道是真掌握还是假掌握。我见过不少准备充分的同学一上来就猛刷Swift语法、SwiftUI组件结果笔试选择题里考的都是Objective-C的__weak、autoreleasepool、methodSwizzle当场傻眼。方向错了努力越多越吃亏。1.3 这份卷子对当下的参考价值有人可能会问2018年的卷子放到现在还有参考价值吗我的判断是价值反而更高了。iOS开发的底层考察逻辑这几年变化并不大日常开发用到的关键机制依然是ARC、Runloop、GCD、Block、KVC/KVO这一套。即使现在项目主流语言从OC切换到了Swift底层依然绕不过objc_msgSend、引用计数和Runloop。把这份卷子的考点吃透再去面对市面上大部分iOS岗位的笔面试你都能做到心中有底。2. 核心考点逐项拆解OC、内存、Runtime与并发2.1 内存管理从引用计数到循环引用内存管理是iOS笔试的绝对主力几乎每份卷子都会考而且一考就是连环题。先说引用计数。Objective-C里每个对象都有一个retainCount当引用计数降为0时对象会执行dealloc并释放内存。ARC在编译期帮我们插入retain/release省去了手动管理的心智负担。笔试常考的是ARC下几种修饰符的区别修饰符作用关键注意点__strong默认持有对象引用计数1赋值即持有__weak弱引用不增加引用计数对象释放后自动置nil__unsafe_unretained类似weak但对象释放后不置nil继续访问会野指针崩溃copy拷贝一份新的不可变对象NSString、Block常用assign简单赋值常用于基本类型用于对象时极易悬空__weak底层依赖Runtime维护的弱引用表SideTable里的weak表。当一个对象被释放时系统会遍历所有指向它的weak指针把它们统一置为nil这就是我们常说的“安全”所在。很多同学只知道weak指向的对象释放后会变成nil却答不出底层表结构这就会在简答题中丢分。另一类高频考点是循环引用。最常见的是Block循环引用对象持有BlockBlock内部又强引用了self形成self - block - self的引用环导致双方都无法释放。解法是使用__weak typeof(self) weakSelf self在Block内部再通过strongSelf转一下避免执行期间对象被提前释放。此外还有NSTimer循环引用、delegate循环引用、CADisplayLink循环引用这些场景都要能举例说明。我建议复习时不要只看概念要做一道经典题写出下列代码的打印顺序并解释为什么。代码一般长这样- (void)viewDidLoad { [super viewDidLoad]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(1); }); [self performSelector:selector(printSomething) withObject:nil afterDelay:0]; NSLog(3); }想拿分光知道performSelector:afterDelay:依赖Runloop还不够你还要解释主线程Runloop在点击事件和定时器之间的工作顺序。这类题就是典型的“基础题但陷阱深”。2.2 Runtime消息传递、动态解析与方法交换Runtime是iOS开发中“高级感”最强的部分也是网易这类大厂笔试的常客。它考察的不是你会不会用某个API而是你能不能说清消息发送的完整路径。在Objective-C中方法调用[obj doSomething]编译后其实会变成objc_msgSend(obj, selector(doSomething))。objc_msgSend会沿着对象的isa指针找到类对象再在方法列表里查找doSomething对应的IMP。找到就叫函数找不到就进入消息转发流程。消息转发流程需要按顺序记忆resolveInstanceMethod:动态给类添加方法的机会。-forwardingTargetForSelector:把消息转发给另一个对象处理。-methodSignatureForSelector:返回方法签名配合-forwardInvocation:做完整转发。笔试如果出简答题让你设计一个“防崩溃组件”本质上就是在考你能否利用这三级转发把未实现的方法拦截下来避免抛出unrecognized selector异常。方法交换Method Swizzling也是高频题。它利用Runtime在运行时替换方法的IMP经典用途是给系统方法打点、HOOK某个方法。你要能说清为什么交换后要在自定义方法里调用原来的IMP、为什么不要在load里做复杂逻辑、以及为什么在Swift中扩展系统类方法时使用Swizzling要格外小心。只背结论不追原理很容易在追问环节翻车。2.3 多线程与锁从GCD到线程安全App要处理异步任务GCD是绕不开的工具。笔试对多线程的考察集中在三块队列类型、死锁场景、线程安全。GCD有两种队列串行队列和并发队列。serial队里任务按顺序执行concurrent队里任务可以同时执行。同步执行dispatch_sync和异步执行dispatch_async组合起来又会产生不同效果。最经典的死锁题长这样dispatch_queue_t queue dispatch_get_main_queue(); dispatch_sync(queue, ^{ NSLog(不会执行到这里); });在主线程同步向主队列提交一个BlockBlock在等待主线程执行主线程在等待Block完成两个互相等待直接死锁。类似的还有在串行队列内部再同步向这个队列提交任务同样死锁。这类题几乎每年都考理解了队列和线程的关系就能稳拿分。线程安全方面atomic是一个高频陷阱。很多同学以为atomic就是线程安全实际上它只保证了属性的getter/setter原子性并不保证整个对象或业务逻辑的线程安全。举个简单例子两个线程同时读取atomic数组的count再根据下标取元素中间如果另一个线程修改了数组依然会越界崩溃。真正要保证业务安全还得靠加锁。锁的性能可以从快到慢排序os_unfair_lock、NSLock、synchronized。笔试中一般只会问区别不会让你背性能表但能说出“synchronized本质上是给对象加了一次递归锁性能成本偏高适合低频保护”就是加分项。2.4 Runloop与事件循环Runloop是iOS事件处理的引擎。一个App启动后主线程的Runloop会不断跑圈处理触摸事件、定时器、网络回调、界面刷新。笔试常考Runloop的几种Mode、Source/Timer/Observer三类事件源以及autoreleasepool在什么时候释放。一个很经典的题目是为什么NSTimer在滑动ScrollView时会暂停答案是在滑动过程中Runloop切到了UITrackingRunLoopMode默认只在kCFRunLoopDefaultMode下工作的定时器就被暂停了。解决方法是把Timer加到NSRunLoopCommonModes下或者改用CADisplayLink。这类题考的不是API而是你对事件驱动模型的理解。Runloop和autoreleasepool的关系也值得写进答案App启动后主线程会注册两个Observer一个监听kCFRunLoopEntry进入Runloop时创建自动释放池另一个监听kCFRunLoopBeforeWaiting和kCFRunLoopExit在Runloop即将休眠和退出时释放自动释放池。所以autorelease对象并不是立刻释放的而是要等当前Runloop周期走到特定时机。3. 架构与系统设计题怎样回答更占优势3.1 三类高频设计题的递进解法方案设计题是网易笔试中拉分最明显的部分。校招生容易答成“知识点堆砌”但高分答案要有明确的模块拆分和取舍逻辑。第一类是图片缓存设计。推荐答案是分三层表述内存缓存、磁盘缓存、网络加载。内存缓存使用NSCache因为它在系统内存紧张时会自动回收磁盘缓存按路径或数据库存储网络层负责下载和缓存写入。再往后可以补充缓存淘汰策略LRU、缓存键生成规则、图片解码是否放在子线程、避免主线程卡顿以及弱网下的占位图和重试机制。这样答面试官能看出你确实考虑过真实业务。第二类是IM消息架构。一个即时通讯模块至少要拆成连接层、协议层、业务层和存储层。连接层负责长连接建立、心跳维持、断线重连协议层处理消息编解码业务层管理会话、未读数、消息发送状态存储层解决离线消息和历史记录。追问时大概率会问你“消息发出去对方没收到怎么办”你要能说出ack确认、超时重传、消息幂等性这些点。第三类是启动速度优化。这题在2018年校招中就已经出现现在更是高频。回答可以从pre-main阶段和非pre-main阶段分开讲。pre-main阶段可以压缩动态库数量、减少类分类数量、检查load方法App启动阶段可以统计耗时方法、把非UI操作延后到viewDidAppear、使用启动清单优化顺序。这个答案没有固定标准核心是让面试官看到你有“先量化再优化”的工程思维。3.2 万能的分析框架需求、约束、模块、容错设计题如果没思路我建议用一套通用框架先说需求再说约束再拆模块最后谈容错。举个例子题目是“设计一个头像上传功能”。很多人上来就写“用AFNetworking上传图片到七牛”这显然太浅。按框架来走需求是用户修改头像上传后全端同步约束包括图片大小限制、上传速度、弱网、流量消耗、失败重试模块拆成图片选择与压缩、上传任务管理、进度展示、失败队列容错部分要处理弱网导致的中断、服务器返回失败、CDN同步延时。把这套逻辑讲完整个答案就有了骨架。面试官追问任何一环你都有内容可聊。校招考察的不是结果而是你有没有意识去考虑这些边界问题。3.3 架构选型MVC、MVVM与组件化“iOS架构”这个词在面试中很容易被问但笔试题通常不会让你写一个大架构更多是让你评价某种架构的优缺点或者在阅读代码时指出职责混乱的问题。MVC是iOS默认架构问题在于Controller太重容易膨胀成“上帝类”。MVVM引入ViewModel层把视图逻辑和业务逻辑解耦但校招生在讲述时容易把MVVM和“把代码拆开写”划等号。高分回答会点出MVVM的核心是数据绑定在iOS里要么通过KVO要么通过ReactiveCocoa或Swift Combine并说明双向绑定的代价是调试成本变高。组件化是另一个常被问的概念但笔试一般不展开最多在简答题里让你“谈一谈模块化设计和CocoaPods私有库的关系”。你能说出来组件化解决的是团队协作、编译速度、代码隔离问题CocoaPods私有库只是载体真正困难的是组件间通信方案就已经很有深度了。4. 编程题准备刷题范围、手写代码与常见误区4.1 刷题优先级的规划算法题不是iOS专场但网易笔试一定会安排。编程题不会出特别偏的题范围集中在数组、链表、字符串、二叉树、栈和队列。如果你想在短期内复习完我建议按优先级来第一优先级链表反转、合并两个有序链表、寻找链表中间节点、环形链表判断。第二优先级二叉树前中后序遍历、层序遍历、二叉树最大深度、最近公共祖先。第三优先级字符串翻转、括号匹配、最长无重复子串。第四优先级排序算法手写和复杂度分析尤其是快速排序和归并排序。第五优先级LRU、手写线程安全的单例、生产者消费者模型。这个优先级不是随便拍的它们直接对应iOS日常开发里“列表数据、缓存管理、任务调度”这些真实场景。大厂喜欢考这些题是因为它们能反映一个人的基本功。4.2 一道高频手写题LRU缓存LRU最近最少使用缓存是图片缓存框架的核心也是笔试和面试里反复出现的“老朋友”。我在这里给一个基于哈希表双向链表的Swift实现笔试时用OC或Swift写核心逻辑都可以关键是让评委看到你的数据结构设计能力。final class LRUCache { private final class Node { var key: Int var value: Int var prev: Node? var next: Node? init(_ key: Int, _ value: Int) { self.key key self.value value } } private let capacity: Int private var cache: [Int: Node] [:] private let head Node(0, 0) private let tail Node(0, 0) init(_ capacity: Int) { self.capacity capacity head.next tail tail.prev head } func get(_ key: Int) - Int { guard let node cache[key] else { return -1 } moveToHead(node) return node.value } func put(_ key: Int, _ value: Int) { if let node cache[key] { node.value value moveToHead(node) return } let node Node(key, value) cache[key] node addToHead(node) if cache.count capacity { let removed tail.prev! removeNode(removed) cache.removeValue(forKey: removed.key) } } private func addToHead(_ node: Node) { node.prev head node.next head.next head.next?.prev node head.next node } private func removeNode(_ node: Node) { node.prev?.next node.next node.next?.prev node.prev } private func moveToHead(_ node: Node) { removeNode(node) addToHead(node) } }这段代码有几个注意点哈希表保证O(1)查找双向链表保证O(1)删除和插入每次get或put都会把节点移到头部尾部自然就是最久未使用的节点删除时直接移除尾部。背代码没有意义你要能说出“为什么用哈希表”“为什么需要双向链表而不是单向链表”。4.3 编程题的高分习惯编程题想拿满分除了答案正确还要注意两点边界条件和代码规范。边界条件包括空数组、只有一个节点、链表头尾操作、整数溢出。很多同学写代码时不会主动检查这些笔试系统一跑边界用例就崩。我的建议是落笔前先花30秒列出边界条件写到方法入口处统一的guard或if判断。代码规范包括命名清晰、不要写过长函数、注释只加在关键位置、复杂度要用一行字写清楚。不要小看这个习惯我在批卷时经常看到同一个算法一个同学只写了核心循环另一个同学把时间复杂度、空间复杂度、边界用例都注释清楚后者即使代码有小瑕疵分数也会更高。原因很简单笔试筛选的是未来要一起协作的人代码可读性本身就是工程能力的一部分。5. 从笔试卷延伸到工程化基本功iOS开发者的日常积累5.1 证书、签名、真机调试与模拟器很多校招生对笔试试卷里出现“证书”“签名”这类题目不理解觉得这是上架阶段才用得到的东西。实际上面试官问这些是想确认你是否有过完整的真机调试和打包经验。iOS开发者证书的核心逻辑是“Apple信任的链条”开发者通过Apple Developer账号生成Certificate再配合Provisioning Profile把设备和App绑定起来才允许在真机上安装运行。平时开发时我一直开着Xcode的Automatically manage signing它会自动帮你管理证书和描述文件。遇到“ios开发者app证书更新”的问题大多发生在证书过期或设备UUID变更时去Apple Developer后台重新下载描述文件即可。模拟器和真机的区别也要能讲明白模拟器模拟的是x86/arm64架构很多系统能力都可以跑但CPU架构、传感器、推送行为并不完全一致。所以“ios设备模拟”适合快速调试UI和逻辑但涉及到相机、定位、后台任务和性能优化必须用真机验证。“ios模拟器”也是自动化测试的重要载体后面会提到。5.2 网络调试Charles抓包和数据流分析笔试时网络相关题目一般只考HTTP层和Socket层但真正做完一个项目后你会发现不会抓包会非常痛苦。我日常排查网络问题最常用的工具是Charles它本质上是一个本地代理工具职责是转发和展示请求/响应数据。用Charles做iOS抓包基础步骤是电脑上启动Charles开启SSL Proxying手机跟电脑连同一局域网把手机WiFi代理指向电脑IP和端口8888然后在手机浏览器访问chls.pro/ssl下载安装Charles的根证书最后在Charles里配置Enable SSL Proxying填入需要解密的域名。这套流程做完你在App里的HTTPS请求就能在Charles里看到明文了。需要提醒一句抓包工具只能用于调试自己参与的App或测试环境不要用于抓取他人App的隐私数据这是职业底线。在笔面试中主动说出“我只在自己开发的App和测试环境中使用”反而会加分。5.3 自动化测试从手动点点点到自动化回归很多同学对“ios自动化”的理解停留在用脚本点击屏幕。实际上iOS自动化包含三个层次单元测试、UI测试和持续集成。单元测试用XCTest针对函数和类做隔离验证UI测试通过XCUITest模拟用户点击、滑动、输入持续集成则把这些测试接到构建流程里每次提交代码自动跑一遍。理解自动化对笔试有直接帮助因为在回答“如何保证代码质量”这类开放题时能说出“通过单元测试覆盖核心工具类通过UI测试做关键路径回归再通过CI在每次merge前触发全量测试”这种答案明显比干巴巴回答“会写测试”更有说服力。6. 高频考点自查与复习建议6.1 能不能30秒答出来自查清单我在陪同学做模拟面试时经常用一套“30秒问答”清单这些问题如果不能在30秒内给出完整回答说明基础还需要再补问题回答要点容易丢分的地方Category和Extension区别Category在运行时增加方法不能直接添加实例变量Extension是编译期可以添加属性混淆运行时和编译期NSString为什么用copy防止可变字符串被外部修改只答“安全”不解释KVO底层原理isa-swizzling动态生成子类重写setter不关注willChange/didChangeload和initialize区别load是类加载时initialize是首次使用时说不清调用顺序hitTest事件传递从父视图向子视图递归调用pointInside说反查找顺序Block变量捕获局变量用const拷贝__block用指针传递说不清静态变量捕获方式这些问题看起来简单但每一条都可以往深挖。比如KVO不仅要答出“利用Runtime动态生成子类”最好还能说出观察者释放时要调用removeObserver否则会崩溃。这类细节在笔试多选题里经常设陷阱。6.2 从笔试到面试的衔接点笔试只是第一关面试官大概率会拿着你的笔试答案继续追问。比如你选择题选了atomic他会问“atomic到底能保证什么”你设计题里写了LRU他会问“为什么NSCache不直接暴露LRU控制接口”你提到SDWebImage做了磁盘缓存他会问“磁盘缓存的失效策略怎么设计”。所以我建议复习时不要背题要把每一个答案都当成“展开话题的起点”。每个考点至少准备30秒的观点陈述外加一个业务场景。比如你回答“Block循环引用”时主动举一个NSTimer持有self的例子面试官大概率会认可你的工程经验。6.3 最后两周怎么学如果距离笔试只剩两周我的复习节奏是第一周回归基础用《Objective-C高级编程》这本书把内存管理、Block、GCD三章精读一遍顺手把书里的代码示例在Xcode里跑一遍第二周进入刷题和模拟模式上午刷LeetCode热题中的easy和mid下午做一份完整的模拟卷晚上复盘错题。模拟卷不要只做一遍做完后一定要把每道题的知识点拆开写清楚“这道题在考什么”“为什么我会错”“如果换成另一种问法我能不能识别”。这个方法听起来朴素但坚持两周效果非常明显。我记得当初自己准备笔试时把一份模拟卷翻来覆去做了四五遍真正到考场上时很多答案几乎已经长在肌肉记忆里了这份手感比临时抱佛脚背一百道题都管用。希望这篇复盘能帮你在面对网易或同类公司的iOS校招笔试时少走一些我走过的弯路。