news 2026/8/31 20:15:15

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
迅雷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 候选人连NSURLSessionURLSessionConfiguration里的超时、缓存策略都说不清楚,那后面涉及传输优化的面试环节大概率也过不了。

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,最后是完整转发methodSignatureForSelectorforwardInvocation。这个链条几乎年年考,值得背下来。

还有一个高频细节是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_storeWeakSideTable上:weak 表里维护了一个从对象地址到 weak 指针地址的映射,对象 dealloc 时系统会根据这个映射把所有指向该对象的 weak 指针置为 nil。这套机制听起来简单,但实现要处理多线程访问同一 weak 指针的同步问题,所以底层用了自旋锁。

还有一道题我记得很清楚:一段代码里,一个局部对象[[NSObject alloc] init]在非 ARC 和 ARC 下分别怎么释放。很多人会掉进“ARC 下局部对象随作用域结束自动释放”这个模糊印象里,但严格说,ARC 是编译期在合适的插入点加了release调用,并不是运行时的自动释放。更细一层是dealloc里不应该直接调用[super dealloc](ARC 下编译器会帮你加)。这些边界条件才是区分“背过面经”和“真正理解内存管理”的关键。

再补充一个实际开发中经常踩的点:循环引用。A 卷简答题里给了一个经典场景,ViewController持有 block,block 里又使用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: bytes=1024-,服务器返回206 Partial Content,然后从字节偏移 1024 继续回传数据。客户端要做的事情包括:第一,在下载开始前先通过一次HEAD请求或GET请求拿到文件的Content-LengthETag,前者用于判断文件总大小,后者用于判断文件内容是否变化;第二,暂停时记录当前已写入磁盘的字节数;第三,继续下载时用该字节数拼装新的 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往全局并发队列里丢任务,然后忽略并发上限。正确做法是用NSOperationQueuemaxConcurrentOperationCountdispatch_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,或者用NSOperationQueuewaitUntilAllOperationsAreFinished做批次控制。这些并发细节在笔试里不一定全展开,但写出“避免主线程阻塞”“信号量初始值=最大并发数”这类关键词,面试官一眼就能看出你有实战经验。

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 续传”,但没有指出如果服务端文件发生了版本更新,断点续传继续下载前必须比对ETagLast-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 那道题的双向链表摘除节点部分加了一行注释“先把旧节点从链表中摘掉,防止重复插入”,后来面试官在面试时特意提到这行注释,因为这意味着我不仅知道要写什么,还知道为什么这样写。对校招候选人来说,这已经是相当好的品质了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 20:13:10

menu html

HTML 分栏目录 基础 HTML 简介 HTML 骨架 html, xhtml, xml 基础语法 & 注释语法 标签 常用标签 简单的标签放在这里:label、textarea、script、small; html 语义化标签 h1~h6; header; nav;main; article; section; aside; footer; small; s…

作者头像 李华
网站建设 2026/8/31 20:10:00

Herdr事件订阅:构建实时代理状态监控面板

Herdr事件订阅:构建实时代理状态监控面板 【免费下载链接】herdr the runtime your coding agents live on 项目地址: https://gitcode.com/GitHub_Trending/her/herdr Herdr 事件订阅(events.subscribe)是构建实时代理状态监控面板的…

作者头像 李华
网站建设 2026/8/31 20:08:40

GLM-OCR Ollama本地部署教程:最简单的本地文档OCR方案

GLM-OCR Ollama本地部署教程:最简单的本地文档OCR方案 【免费下载链接】GLM-OCR GLM-OCR: Accurate Fast Comprehensive 项目地址: https://gitcode.com/GitHub_Trending/gl/GLM-OCR GLM-OCR 是一款面向复杂文档理解的多模态 OCR 模型(总参数量…

作者头像 李华