ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

迅雷iOS笔试A卷复盘:Runtime、断点续传与并发设计考点全解析

2026/8/31 20:15:23 拓冰建站 浏览量
迅雷iOS笔试A卷复盘:Runtime、断点续传与并发设计考点全解析 2018年秋天我还在读研二投了迅雷的 iOS 开发岗。笔试通知来得比我想象中快是一个在线笔试链接打开后是 Web 页面限时 90 分钟题目分选择、简答、代码补全和设计题几大块。当时我在宿舍里关掉所有通讯软件把屏幕亮度调低深吸一口气点了“A卷”。现在回头看这场笔试基本决定了半个月后的面试节奏。迅雷的业务线集中在下载、加速、影音播放上所以它的 iOS 笔试并不像一些大厂那样全是算法堆砌而是把大量分数压在了网络协议、并发处理、内存管理和性能优化上。这也是为什么我一直建议准备校招的同学刷题之外一定要结合目标公司的业务场景去猜题。这篇文章就把当年 A 卷的考查逻辑、我印象比较深的题、以及后来从面试官视角反推出来的考点优先级整理一下。适合马上要参加 iOS 校招笔试的应届生也适合想补一补 Objective-C 和 Swift 底层知识的初级开发者。1. 迅雷 A 卷的题型全貌一场 90 分钟的 iOS 基础大体检1.1 题型构成与分值背后的考察意图A 卷整体卷面大概是这样的结构20 道左右选择题、4 道左右简答题、2 道代码题、1 到 2 道开放设计题。选择题分值大约占 40%简答和代码各占 20%设计题占剩下的 20% 左右。选择题覆盖范围很广从 KVC、KVO 的底层实现到dispatch_once的线程安全原理再到 copy 修饰符的深浅拷贝问题都有涉及。这个分布透露了几个信息。第一迅雷并不指望一个应届生进来就能直接写下载引擎但要求你对 iOS 底层机制有扎实理解因为下载和播放这类业务很容易踩到内存、线程、网络层面的坑没有底层认知线上出问题会非常难排查。第二选择、简答、代码、设计四层递进就是为了层层筛选——选择题考知识点记忆简答题考知识体系代码题考落地能力设计题考工程思维。第三点是迅雷自己很明显的偏向网络相关题目占比明显比普通互联网公司高。这也好理解迅雷的老本行是极速下载和视频加速客户端开发绕不开 TCP 连接管理、HTTP 请求、流量控制、文件 IO 这些基本功。如果一个 iOS 候选人连NSURLSession的URLSessionConfiguration里的超时、缓存策略都说不清楚那后面涉及传输优化的面试环节大概率也过不了。1.2 时间分配策略别在简答题上死磕在线笔试最大的敌人不是题目难而是时间失控。我记得当时一共 90 分钟系统不支持回头改题过了答题区域就会进入下一题提交之后无法修改。这个机制意味着你必须给每道题设定心理时间上限否则很容易出现“前面写得很嗨后面设计题开了天窗”的惨剧。我当时的策略是选择题控制在 25 到 30 分钟遇到犹豫超过 1 分钟的题直接先蒙一个标记下来回头再说简答题每道控制在 5 到 8 分钟只写核心逻辑不展开长篇大论代码题预留 30 分钟最后 15 分钟留给设计题。这个节奏实战下来是够用的但我听到身边有同学反映他简答第二题里为了把 RunLoop 的 Source0 和 Source1 的调用流程写得非常详细花了将近 20 分钟结果后面代码题基本是空白状态。这就是典型的本末倒置。还有一个容易忽略的细节在线笔试的环境不是 IDE代码题没有自动补全也没有编译验证纯靠手写。你平时习惯用 Xcode 自动提示的话最好在笔试前练几道白板手写题尤其是NSOperation依赖关系、dispatch_semaphore控制并发这类代码块动手写一遍和眼睛看一遍的差别非常大。2. 基础考点复盘从 Runtime 到 ARC容易丢分的细节题2.1 Runtime 消息机制笔试选择题的“钉子户”迅雷 A 卷选择题里关于 Runtime 的题目大概出现了 3 到 4 道密度很高。常见的考法是给你一段代码问最终调用的是哪个方法、消息转发过程会依次走哪些步骤、或者某个objc_msgSend场景下self和_cmd分别是什么。这里必须把消息发送的完整链路捋清楚调用一个 OC 方法时编译器会把它转换成objc_msgSend(receiver, selector)这一步会先去查receiver的 isa 指针找到对应的类对象然后在 class 的方法列表中查找没找到就沿着 superclass 指针逐级向上找。如果最终都没有找到会进入动态方法解析resolveInstanceMethod然后是快速转发forwardingTargetForSelector最后是完整转发methodSignatureForSelector加forwardInvocation。这个链条几乎年年考值得背下来。还有一个高频细节是Method Swizzling。笔试里会问为什么 swizzling 通常要在load方法里做或者让你说 swizzling 的坑。我当时的写法是第一步取两个方法的Method然后用method_exchangeImplementations交换。但加分答案是补充两个关键点交换之后IMP已经变了所以需要通过原始 selector 保存一个block或者用associatedObject做标记防止重复 swizzle 导致逻辑错乱另外在 iOS 14 之后的系统上如果在运行时动态添加类时用到method_swizzle还要注意stub消息发送方式的问题这个虽然 2018 年还没怎么展开但底层的严谨态度是面试官想看到的。2.2 ARC 的边界条件不只是强引用和弱引用ARC 相关的题大多数人能答上 strong、weak、copy、assign 这些修饰符的用法但 A 卷考得更细。有一道题是问weak属性在对象被释放后自动置 nil 的底层机制。答案要落到objc_storeWeak和SideTable上weak 表里维护了一个从对象地址到 weak 指针地址的映射对象 dealloc 时系统会根据这个映射把所有指向该对象的 weak 指针置为 nil。这套机制听起来简单但实现要处理多线程访问同一 weak 指针的同步问题所以底层用了自旋锁。还有一道题我记得很清楚一段代码里一个局部对象[[NSObject alloc] init]在非 ARC 和 ARC 下分别怎么释放。很多人会掉进“ARC 下局部对象随作用域结束自动释放”这个模糊印象里但严格说ARC 是编译期在合适的插入点加了release调用并不是运行时的自动释放。更细一层是dealloc里不应该直接调用[super dealloc]ARC 下编译器会帮你加。这些边界条件才是区分“背过面经”和“真正理解内存管理”的关键。再补充一个实际开发中经常踩的点循环引用。A 卷简答题里给了一个经典场景ViewController持有 blockblock 里又使用self让你写出几种解决方案。除了常见的__weak typeof(self) weakSelf self你最好还要说明在 block 执行期间需要强引用 self 防止被提前释放时可以用__strong typeof(weakSelf) strongSelf weakSelf这种方式在 block 内部先加固一层。这个细节笔试答案里写上通常能加不少印象分。2.3 RunLoop 与界面卡顿从 Timer 不触发说起RunLoop 在 2018 年校招笔试中出现频率极高迅雷 A 卷也不例外。我当时遇到的是这样一道简答题一个 Timer 被添加到NSDefaultRunLoopMode用户拖动 scrollView 时为什么 Timer 会不执行如何解决这个问题的背景是 iOS 为了提高界面滚动流畅度会把 RunLoop 切换到UITrackingRunLoopMode此时默认模式下的 Timer 暂停只有切回默认模式才会继续触发。解决办法通常是把它addTimer:forMode: NSRunLoopCommonModes或者用dispatch_source_t创建定时器。答题时如果能补充一句“dispatch 的 timer 是系统级调度不受 RunLoop mode 切换影响”会显得你对底层更了解。RunLoop 另一个常考点是它与线程的关系线程为什么要保活。最优解是让 RunLoop 在 source、timer、observer 都为空时直接返回反之若想让一个后台线程常驻就要给它添加一个 port 或者 source让 RunLoop 有事情可做。迅雷这种下载型 App 里后台线程需要持续处理数据块写入所以这个知识点不是死概念是真的会用在下载任务调度的长驻线程设计上。3. 下载场景背后的硬核知识网络大题里藏着的工程问题3.1 HTTP Range 断点续传整张卷子里最“迅雷”的题如果说这张卷子有什么题目一眼就能看出是迅雷出的那就是断点续传题。它的问法很直接在 iOS 客户端实现一个文件下载器支持暂停、继续和断点续传请问 HTTP 协议层面该怎么处理客户端本地需要保存哪些信息思路要从 HTTP Range 说起。断点续传的本质就是客户端在请求头里带上Range: bytes1024-服务器返回206 Partial Content然后从字节偏移 1024 继续回传数据。客户端要做的事情包括第一在下载开始前先通过一次HEAD请求或GET请求拿到文件的Content-Length和ETag前者用于判断文件总大小后者用于判断文件内容是否变化第二暂停时记录当前已写入磁盘的字节数第三继续下载时用该字节数拼装新的 Range 请求。如果只答到这里只能说拿到及格分。我后来跟朋友复盘时觉得迅雷想看到的答案还包括“服务端返回 200 而不是 206”该怎么兜底。有些服务器不支持 Range会直接返回 200 全量数据这种情况下客户端必须丢弃已下载内容重来或者强制走非断点逻辑。代码层面可以这样表达NSMutableURLRequest *request [NSMutableURLRequest requestWithURL:url]; if (resumeDataOffset 0) { NSString *range [NSString stringWithFormat:bytes%lld-, resumeDataOffset]; [request setValue:range forHTTPHeaderField:Range]; }这里最关键的一行是bytes%lld-注意用long long不然文件超过 2GB 之后 offset 会溢出我见过不少实际项目是栽在这上面的。另外一个容易被忽略的点是临时文件的组织方式。如果采用分片下载把文件切成几段并发下载那么每个分片都要独立记录 start、end、currentOffset、isFinished 四个字段最后再合并。合并时要小心不要用NSString stringByAppendingString去拼二进制数据一定要用文件流的seekToFileOffset定位后写入否则数据很容易错位。3.2 多线程并发设计信号量、队列和死锁下载器题后面通常会挂一道关于并发的简答题多个下载任务同时进行怎么控制并发数任务之间互相有依赖比如先下载种子文件再下载主文件怎么保证执行顺序常见的错误答案是一上来就dispatch_async往全局并发队列里丢任务然后忽略并发上限。正确做法是用NSOperationQueue的maxConcurrentOperationCount或dispatch_semaphore_t控制并发。我当时答题选的是信号量因为代码更直接dispatch_semaphore_t semaphore dispatch_semaphore_create(3); for (NSURL *url in urls) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [self startDownloadWithURL:url completion:^{ dispatch_semaphore_signal(semaphore); }]; }); }这段代码有一个致命陷阱如果startDownloadWithURL的 completion 是在主线程回调的而当前任务又在主线程上等待信号量就会造成主线程死锁。网上很多面试题喜欢用这段代码考人它看似逻辑不错实际上一跑就会卡死因为dispatch_semaphore_wait是阻塞当前线程的如果你在主线程调用它等一个永远不会从主线程发出的 signal就完了。迅雷这类下载型 App 的正确设计是把网络代理回调放在专门的 delegate 队列里所有信号量等待都在子线程进行主线程只负责 UI。如果要用NSOperation可以给每个任务添加addDependency或者用NSOperationQueue的waitUntilAllOperationsAreFinished做批次控制。这些并发细节在笔试里不一定全展开但写出“避免主线程阻塞”“信号量初始值最大并发数”这类关键词面试官一眼就能看出你有实战经验。3.3 HTTPS 证书校验与网络安全为什么下载 App 更在意这个提到下载就绕不开数据传输安全。2017 年苹果全面推行 ATS 之后iOS 应用默认只能访问 HTTPS 服务。迅雷 A 卷里也出现了一道相关的选择题HTTPS 握手过程中客户端如何验证服务器证书是可信的标准答案是证书链验证客户端内置了一批受信任的根证书服务器返回的证书一般是由中间 CA 签发的客户端需要一级一级向上找直到找到一个受信任的根证书然后验证签名、有效期、域名是否匹配。笔试里能把这些讲清楚基本就够用了。但如果想拿高分建议再写一下客户端侧的主动校验。URLSessionDelegate里有- (void)URLSession:didReceiveChallenge:completionHandler:在这个回调里可以拿到SecTrustRef通过SecTrustEvaluateWithError进行信任评估来做 SSL Pinning。下载 App 在做 SSL Pinning 时要格外小心证书过期后没有做平滑升级策略的话所有用户都会瞬间下载失败。好的做法是同时把新旧两个证书的 hash 内置在包里优雅换证。另外提醒一句那年 A 卷有一道选择题问“Charles 抓包 HTTPS 的时候为什么 iOS 客户端需要先安装并信任描述文件”。很多同学只知道操作步骤不知道原理。原理就是 Charles 自己签了一个中间人证书客户端如果不信任它第一次 TLS 握手就失败了。理解了这一点后面设计“防护中间人攻击”的方案时才能答得有理有据。4. 代码与算法不是 LeetCode 刷题而是工程思维4.1 手写代码题链表反转与 LRU 的边界处理A 卷的代码题没有考特别刁钻的算法我印象中有一道是链表反转另一道是设计 LRU Cache。链表反转作为笔试高频题很多人能写出来但迅雷的坑在于它要求用“只遍历一趟”的方式并且要考虑空链表、单节点链表、循环链表三种边界。这里给一个干净的迭代写法- (ListNode *)reverseList:(ListNode *)head { ListNode *prev nil; ListNode *curr head; while (curr) { ListNode *next curr.next; curr.next prev; prev curr; curr next; } return prev; }LRU 那道题我觉得更值得聊。它的要求是用最少的时间复杂度实现 get 和 put题目里明确提示可以参考NSDictionary加双向链表的方式。2018 年时 iOS 里没有现成的LinkedHashMap所以手写双向链表是必然选择。我做的时候是这样设计的一个NSMutableDictionary负责 O(1) 查找value 存节点对象每个节点包含 key、value、prev、next 指针每次 get 命中后把节点移到链表头部put 时若容量满了先移除尾部节点。这里面比较隐蔽的坑是当你用NSDictionary存节点时字典的 key 是业务 key但如果同一个业务 key 被 put 了两次旧节点还挂在链表上必须先把旧节点摘掉再插到头部否则链表里会出现重复节点导致容量计算错误。我记得自己当时第一版就漏了这个逻辑还好在线笔试结束前检查出来补上了。4.2 图片加载与内存优化影音业务躲不开的坎迅雷除了下载还有影音播放。所以笔试里有一道简答题问在 iOS 上一个 TableView 要展示大量网络图片如何保证滚动流畅且不产生内存峰值这道题其实就是考SDWebImage的原理但要用自己的话讲清楚。解答框架可以分四层加载层、内存缓存层、磁盘缓存层、解码层。加载层是用异步下载加并发控制同一个 URL 的多个请求要合并成一个避免重复请求内存缓存层用NSCache管理注意NSCache在系统内存紧张时会自动清理这是它比NSMutableDictionary更适合做图片缓存的根本原因磁盘缓存层一般写到 Caches 目录文件名用 URL 做 MD5防止非法文件名字符解码层是把下载完的图片先解码成位图再缓存否则每次UIImageView显示时都会在主线程解码丢帧就是从这里来的。这里有个常见的加分点不要把大图直接解成完整分辨率而要根据UIImageView的目标尺寸做降采样。iOS 里可以这样写CGImageSourceRef source CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL); NSDictionary *options { (__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways: YES, (__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize: (targetSize), (__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform: YES }; CGImageRef thumbnail CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef)options);如果只答出下载和缓存面试官大概率觉得你只是用过SDWebImage但能答出降采样就说明你认真想过“内存峰值”三个字到底意味着什么。4.3 开放设计题设计一个极速下载模块开放设计题是 A 卷最后一个大题题目大概是“假设你要在 iOS 上从零实现一个极速下载引擎请画出模块划分并说明每个模块的职责”。这题没有标准答案但它是最能体现候选人工程经验的一题。我当时的答案分了四层。第一层是任务管理模块负责维护一个待下载队列支持暂停、恢复、取消并把任务状态通过 delegate 通知给 UI第二层是网络层基于NSURLSessionDataTask实现分片并发下载每个分片一个 task通过URLSession的多实例来做连接复用第三层是存储层负责临时分片文件的读写和最终合并所有写操作放到串行队列用文件句柄 seek 定位第四层是缓存层管理已下载完成文件的保留策略比如只保留最近 N 天、剩余空间低于阈值时自动清理。如果只是列出模块显得太平淡。我额外加了一段“如何保证 App 退到后台还能继续下载”的策略通过beginBackgroundTaskWithExpirationHandler申请后台时间配合系统允许的后台传输NSURLSessionBackgroundConfiguration来保证断点续传的连续性。这个内容在 2018 年属于很有区分度的答案因为很多人根本不知道NSURLSession还有一个 background 模式是专门为此设计的。设计题的核心是展示“你考虑到了别人没考虑到的问题”所以不用面面俱到但是一定要把异常分支写出来。比如服务端突然返回 5xx、网络从 Wi-Fi 切到蜂窝网络、磁盘满了没有写入权限、文件在下载过程中被系统清理掉这几个场景分别要怎么处理你能写出其中两个就已经超过一大半候选人了。5. 考完之后我想明白的事笔试复盘与备赛建议5.1 错题分析我最开始在“弱网处理”上吃过亏那次笔试结束后我复盘发现自己丢分主要丢在一个地方网络层对弱网场景的处理。例如一道简答题问“下载到一半网络断开后重连时需要怎样校验文件是否完整”我只回答了“继续使用 Range 续传”但没有指出如果服务端文件发生了版本更新断点续传继续下载前必须比对ETag或Last-Modified如果不比对下载完的文件很可能是旧段和新段混合在一起直接导致压缩包解压失败、视频播放花屏。这个点后来我翻了不少迅雷的技术分享发现他们在实际的下载场景里会对每个分片做哈希校验完成后还要做全文件校验。校招笔试当然不会要求你写这么多但答出ETag比对就能在很多人中间脱颖而出。另外我也意识到准备笔试不能只背题得真的动手搭一个小项目去触发这些场景。比如用 Charles 模拟弱网、模拟断网、模拟服务器返回错误码然后观察自己的下载代码在各个环节的表现。这种实操带出来的直觉比刷十篇面经都有用。5.2 针对网络工具型厂商的备考方法先猜业务再猜考点如果你准备投迅雷、百度网盘、爱奇艺这类偏网络和媒体的公司备考逻辑要和大厂通用准备不太一样。通用的 iOS 八股当然要背但真正拉分的是和公司业务强相关的部分。我当时做了一个简单的推测表把迅雷的核心业务拆出来对应到技术考点上这个思路很笨但很有效下载——断点续传、并发控制、文件校验加速——多线程调度、网络性能优化、弱网容错影音——图片加载优化、视频播放缓存、内存控制。有了这张表我后面复习冲刺时就不是茫无目的了而是有方向地把 HTTP、TCP、GCD 这些基础知识往业务场景上靠。比如复习 TCP 三次握手的时候我会顺带想一下一个文件被切成了 20 个分片并发下载每个分片是独立的 TCP 连接还是同一个连接上跑多个请求这个问题的答案是为了避免连接数过多造成拥塞通常用HTTP/1.1的 keep-alive 在同一个连接上串行请求多个分片或者用HTTP/2的多路复用解决队头阻塞。把这个思考写进笔试答案里阅卷的人会觉得你不是死记硬背。5.3 给 iOS 应届生的几条实在建议笔试过了之后还有面试但我越来越觉得笔试环节其实比面试更考验“基本功的完整度”。面试官可以引导你、给你提示但笔试是白纸黑字不会就是不会。所以我给后来的学弟学妹的建议是三条第一基础知识点不要只看面经把面试题里提到的所有概念都拉回到官方文档和源码里看一遍特别是 Runtime、RunLoop、内存管理等面试必考点看懂官方的注解比背一百道题有用第二至少完整实现一个能跑通断点续传的 Demo把永久暂停、App 杀进程、网络切换几个场景都测一遍这个 Demo 讲清楚带来的说服力远大于你背出十个概念第三做笔试题的时候要有“刻意留白”的意识简答题写核心关键词设计题画清模块边界不要在一个细节上过于纠结而丢了全局。最后再说一个很实用的技巧在线笔试的答案代码如果拿不准尽量写注释把思路写在代码块里。我当时在 LRU 那道题的双向链表摘除节点部分加了一行注释“先把旧节点从链表中摘掉防止重复插入”后来面试官在面试时特意提到这行注释因为这意味着我不仅知道要写什么还知道为什么这样写。对校招候选人来说这已经是相当好的品质了。