迅雷校招iOS笔试A卷深度解析:内存管理、RunLoop和多线程

迅雷校招iOS笔试A卷深度解析:内存管理、RunLoop和多线程 1. 整卷概览与命题思路分析1.1 2018迅雷校招iOS笔试到底在考什么2018年迅雷的校园招聘iOS在线笔试A卷放在今天来看依旧有很强的参考价值。这份卷子不像很多大厂那样动辄出偏题怪题它的命题风格更偏向“基础扎实 工程落地 底层理解”三者兼顾。我记得当时做完整个试卷的第一感受是真正拉开差距的并不是最后的大题而是前面那些看似常规的选择题和简答题——它们会从非常细节的角度切入考察你到底是真的用过还是仅仅停留在“看过文档”的层面。整份A卷的题型分布大致是这样的单项选择题占了一部分主要覆盖OC语言特性、内存管理、RunLoop等基础多项选择题会稍微加大难度经常出现“以下哪些说法是正确的”这种组合型判断题需要你对每个选项都有确定的把握简答题则会让你写出某个方法的内部实现思路比如copy和mutableCopy的区别、weak属性的实现原理等最后还有一道综合题通常和网络层设计或者多线程并发场景有关。从考试时间来看整场笔试的题量并不算小在线笔试系统还会限制每道题的停留时间或者整体倒计时。这意味着你不仅要对知识点熟练还要有快速判断和取舍的能力。很多人在前面的单选上纠结太久导致后面的简答和综合题写得非常仓促这是比较典型的失分原因。1.2 为什么说这套题对今天依然有参考价值iOS开发这几年虽然从OC转向Swift的趋势明显SwiftUI也在逐步普及但迅雷这套2018年的笔试题所涉及的知识体系并没有过时。原因很简单一个iOS开发者需要的底层能力——内存管理、多线程、RunLoop、网络状态机、UI布局原理——在任何时代都是核心。Swift再怎么变化ARC的内存管理思想没有变GCD和OperationQueue依然是并发的主力RunLoop依然是理解系统交互的基础。另外从行业面试的角度来看现在很多公司在校招时依然会参考类似2018年迅雷A卷这样的题库出题。原因也很现实这些题目能把“刷过题库的人”和“真正理解系统运行机制的人”区分开来。比如大家都知道assign和weak都能修饰代理属性但为什么实际开发中几乎全部用weak如果只背答案这道题可能选对但问你“weak表的结构是什么”“什么时候会触发置nil操作”很多人就会卡住。这篇博文我会把这份A卷涉及的几个核心知识模块拆开讲透每个模块结合当年的真题考察方向、延伸到现在的iOS开发场景再给出实际可落地的心得。说白了这篇文章不只是在“对答案”更是一份iOS基础能力自检清单。2. Objective-C语言特性与内存管理高频题2.1 copy与mutableCopy一题能带出整个内存管理框架“用copy修饰的NSString属性在赋值一个NSMutableString时会发生什么”这是2018年迅雷A卷里我非常笃定的一道题也是后来很多iOS面试中出现频率极高的一道。很多基础不牢的候选人会答“会把可变字符串拷贝一份”但细节一问就露馅。先把结论说清楚如果用一个NSMutableString去给一个copy修饰的NSString属性赋值系统会执行一次深拷贝得到一个不可变的NSString副本。如果用一个NSString去赋值系统通常会做指针拷贝也就是retain一次而不是真正拷贝内容。这是NSString的copy方法在底层做了优化——对于不可变对象copy直接返回自身并加引用计数对于可变对象copy才会真正拷贝一份新的不可变对象。这个行为在工程上有一个非常典型的意义你永远不希望自己的属性被别人传进来的可变对象影响。举个实际场景假如你的类有一个property (nonatomic, copy) NSString *title;外部调用方传进来一个NSMutableString并且在赋值之后又修改了这个可变字符串的内容。如果属性用的是strong你会发现自己类里的title也跟着变了这种“外部改值导致内部状态异常”的bug非常隐蔽。而用copy修饰之后赋值那一刻就已经把内容固定了外部后续怎么改都影响不到你。接下来还有一个高频延伸题属性用copy修饰NSArray和NSMutableArray有什么区别答案是如果属性声明为property (nonatomic, copy) NSMutableArray *dataArray;这在运行时会出现非常隐蔽的问题——你赋值一个NSMutableArray进来系统会按上面的规则拷贝成一个不可变副本但你的属性类型却仍然写着NSMutableArray。之后你调用addObject:时运行时会直接崩溃报unrecognized selector。这是很多初级开发者在实际开发中踩过的坑也是当年A卷选择题里非常喜欢埋的陷阱之一。2.2 weak与assign从“崩溃现场”反向理解对象生命周期先说当年A卷的一道多选“以下关于weak和assign的说法正确的是哪些”。选项里有“weak可以修饰OC对象”“assign可以修饰基本数据类型”“weak修饰的对象在释放后会自动置nil”“assign修饰的对象释放后再访问会野指针”等等。这道题如果你没有真正在项目里遇到过内存问题很容易把第三个选项想当然地勾给assign。核心差异一句话就能讲完assign不会改变对象的引用计数也不参与对象的生命周期管理weak同样不改变引用计数但系统会在对象释放时自动将weak指针置为nil。这个操作是通过运行时维护的一个weak表来完成的——每个对象有一个对应的weak指针列表对象销毁时runtime会遍历这个列表把所有指向该对象的weak指针都置空。工程上最常见的例子就是代理属性property (nonatomic, weak) idSomeDelegate delegate;这里为什么用weak而不用assign因为如果用了assign代理对象在某个时刻被释放了这个delegate指针并不会自动置空。当事件发生时你的类向这个悬空指针发送消息大部分情况下会直接崩溃EXC_BAD_ACCESS。用weak之后即使代理对象提前释放了delegate会被自动置为nil向nil发送消息是安全的不会崩溃最多就是事件不再被处理。我曾经在一个直播类App里遇到过这类崩溃后来把一批assign修饰的代理属性全部改成weak崩溃率立刻下来了。这个案例后来我在面试时也经常讲用来表达“我不是只会背概念而是真的处理过线上问题”。2.3 ARC时代为什么还要关注引用计数2018年迅雷A卷里有一道简答题问的是“ARC环境下strong、weak、unsafe_unretained的区别以及在block中使用weakSelf和strongSelf的原因”。这个问题放到现在依然是面试官的心头好因为它层层递进能测出你对内存管理的真实理解。先说strong和weak的区别以及为什么block里要用weakSelf。常见场景是一个ViewController持有一个block属性block内部又使用了self。如果这个block同时被self持有就会形成循环引用retain cycle两个对象互相持有谁也释放不了最终造成内存泄漏。解决办法是在block外部声明一个__weak typeof(self) weakSelf self;block内部使用weakSelf打破循环引用链条。但如果你在block内部只用了weakSelf也会碰上一个新问题如果block执行过程中self刚好被释放了weakSelf就会变成nil后续调用self的方法或者访问属性都得不到正确结果。尤其在一些需要多步操作的场景里比如先判断weakSelf不为nil然后调用weakSelf.methodA再调用weakSelf.methodB这两次调用之间self可能就已经被释放了。所以就有了block内部第一行加__strong typeof(weakSelf) strongSelf weakSelf;的写法。这样做的好处是先判断weakSelf不为nil然后用strongSelf把对象暂时持有住保证在这个block的执行周期内self不会被释放。执行完之后strongSelf是局部变量出了作用域就自动释放不会造成循环引用。提示在block开头判断if (!weakSelf) return;然后再使用strongSelf是iOS开发中非常标准的内存安全写法。不要嫌麻烦线上崩溃很多时候就来自这种细节。2018年那会儿很多应届生没有实际处理过循环引用所以这种题目能答出“weakSelf打破循环strongSelf防止提前释放”两层含义基本上就能拿到不错的分数。2.4 属性关键字组合的真正含义A卷的选题题里还有一个高频考点property的各关键字组合。比如“atomic”和“nonatomic”有什么区别“atomic”真的能保证线程安全吗答案会分两层atomic能保证属性的原子性也就是读写的完整性不会被多线程打断。但它不能保证业务层面的线程安全比如两个线程同时读取属性、做计算、再写入这个“读-改-写”的流程依然会产生数据竞争。nonatomic不保证读写完整性但访问速度更快性能更好。在实际iOS开发中几乎所有属性都用nonatomic包括UIKit框架里的各种控件属性。为什么因为iOS主线程是UI操作的唯一线程UIKit本身就要求在主线程上操作atomic所带来的性能损耗完全没有必要。而且atomic的实现代价是使用锁对性能敏感的场景会有明显影响。类似的还有readwrite和readonly、getter和setter的定制等。比如property (nonatomic, getterisFinished) BOOL finished;这种写法在源码里经常见到主要为了让命名更符合语义。笔试如果考到这个更多的是想看你平时有没有认真读第三方库的源码或者有没有留意过系统API的声明方式。3. Runtime与RunLoop底层理解3.1 “消息发送”到底是怎么发生的2018年迅雷A卷有一道选择题题干大意是“调用一个OC对象的方法底层会发生哪些步骤”选项里包含了objc_msgSend、查找缓存、查找方法列表、消息转发等步骤。这道题单看选项不难但要选对顺序需要你对Runtime的整体流程有清晰认识。OC的方法调用本质上是消息发送调用[obj doSomething]时编译器会改写成objc_msgSend(obj, selector(doSomething))。这个函数会先去obj的类对象里查找方法缓存缓存没命中就去方法列表查找找到后调用函数指针如果方法列表里也没找到就会进入消息转发流程完整路径是动态方法解析resolveInstanceMethod - 快速转发forwardingTargetForSelector - 完整转发methodSignatureForSelector forwardInvocation。把这个机制延伸到实际开发里有几个很有意思的用法Method Swizzling利用Runtime在运行时替换方法实现常用于AOP埋点、统计、异常处理。经典场景是交换viewWillAppear:和自定义方法实现页面自动埋点。动态方法解析dynamic关键字配合resolveInstanceMethod可以实现属性的动态getter/setterCoreData的模型类就是这么做的。消息转发如果对象收到无法识别的selector可以通过消息转发把消息转给其他对象相当于给对象增加了一种“容错”和“代理”机制。对于校招笔试来说关键是能把objc_msgSend的流程画清楚。我记得当年我是在简历上写了“熟悉Runtime”然后这道题答得也比较顺算是加分项。3.2 RunLoop的mode与UI事件的关系RunLoop是iOS笔试中的常客2018年迅雷A卷的简答题部分就问到了“NSTimer在UIScrollView滚动时为什么暂停如何解决”。这个问题背后的原理是RunLoop的mode切换。iOS的RunLoop有多种mode最常见的是kCFRunLoopDefaultMode默认模式和UITrackingRunLoopMode滑动跟踪模式。当你滑动UIScrollView时主线程RunLoop会切换到UITrackingRunLoopMode而NSTimer默认注册在DefaultMode下所以在滑动过程中Timer的回调不会被触发。解决方案主要有三种把Timer加入到UITrackingRunLoopMode但这样做会让Timer在非滑动时不走效果相反。用NSRunLoopCommonModes把Timer加入commonModes标记的集合中这样无论是DefaultMode还是TrackingMode都能触发。用GCD的dispatch_source_t或DispatchSourceTimer替代NSTimer从底层绕开RunLoop mode的限制。当年我在笔试里答的是NSRunLoopCommonModes面试官追问“为什么加commonModes就能两种模式都能执行”我抓住关键点回答commonModes并不是一种实际运行的mode而是一个标记集合加入这个集合的RunLoopMode都会在切换时执行这个source。这是我后来研究RunLoop源码才彻底搞清楚的也建议大家不要只背答案多去读一读CFRunLoop.c的实现。3.3 AutoreleasePool的底层作用A卷里还有一道关于AutoreleasePool的判断题大意是“ARC环境下autoreleasepool还有存在的意义吗”。很多应届生看ARC自动管理内存就觉得不需要手动写autoreleasepool了其实不是。AutoreleasePool本质上是一个对象引用计数延迟减一的机制。在没有ARC的年代手动调用autorelease会把对象加入当前的自动释放池等池子销毁时统一release。ARC环境下系统自动插入release/retain/autorelease调用但自动释放池的结构仍然存在。它的典型应用场景是循环里大量创建临时对象比如遍历一个很大的数组并生成很多NSString、NSNumber、UIImage对象for (NSInteger i 0; i 10000; i) { autoreleasepool { // 创建大量临时对象 NSString *str [NSString stringWithFormat:item-%ld, i]; NSData *data [str dataUsingEncoding:NSUTF8StringEncoding]; // 处理 data } }如果不加autoreleasepool这些临时对象会等到当前RunLoop周期结束才释放峰值内存会非常高加上autoreleasepool后每一轮循环结束都会释放一次临时对象内存曲线就会平稳很多。这个优化技巧在校招项目和实习经历中很能体现工程素养。4. 多线程与并发处理4.1 GCD队列与死锁的经典陷阱2018年迅雷A卷有一道大热门题“在主队列上同步执行一个任务会发生什么”答案是死锁。这道题几乎成了iOS多线程面试的代名词基本上大家都会背“会死锁”但问题在于很多人说不清楚为什么会死锁。主队列是一个串行队列它只有一个线程主线程在执行任务。当主线程正在执行某个任务时如果在这个任务内部再调用dispatch_sync向主队列提交新任务这个新任务需要等待主线程空闲才能执行。但主线程当前的任务又在等待这个新任务执行完毕于是互相等待形成了死锁。把这个概念理清楚了再去看其他GCD死锁场景就容易很多。比如串行队列内部再同步提交到同一个串行队列也会产生同样的问题。而并发队列内部同步提交到自身一般不会死锁因为并发队列可以有多个线程同时执行任务系统可以开辟新线程来执行同步提交的任务。当年A卷的多选里就出现过类似的变体选项包括“在主队列执行sync会死锁”“在串行队列执行sync会死锁”“在并发队列执行sync会死锁”“在全局并发队列执行sync会死锁”。正确答案涉及前两个。很多人栽倒是因为没有理解sync和队列类型之间的关系只死记了“主队列不能sync”。4.2 线程安全问题与atomic的矛盾多线程、数据竞争、锁这些都是笔试和面试中绕不开的话题。2018年迅雷A卷简答题部分有一道题是“iOS中如何保证多线程访问可变集合的线程安全”。这道题刚看到有点懵因为当时准备笔试的重点都放在了API记忆上很少思考“怎么设计一个线程安全的数据结构”。标准答案可以从几个层级展开最简单的是加锁用synchronized、NSLock或者pthread_mutex更优雅的是用GCD的串行队列把对可变集合的所有读写操作都提交到同一个队列利用队列的串行特性保证线程安全再进一步是使用读写锁pthread_rwlock实现“多读单写”的高性能并发控制。我当时在笔试里写了NSLock 串行队列的方案同时补充了“atomic修饰的NSMutableArray并不等于线程安全”面试官显然很满意这个细节。因为很多应届生会以为atomic能处理这种场景但实际上atomic只能保证属性赋值和取值的安全不能保证你往可变数组里添加对象的整个操作是原子的。关于iOS开发中的锁这里有一个实用的小知识synchronized会自动创建一个异常处理的锁使用简单但性能开销较大适合低频操作NSLock是标准的互斥锁性能中规中矩os_unfair_lock是底层的高性能锁但需要注意加锁和解锁必须配对。现代iOS开发中也会用到Swift的actor但在OC项目里GCD串行队列依然是最高频的线程安全方案。4.3 多线程场景下的网络请求并发控制迅雷做下载和加速起家所以它们笔试题里出现网络相关的多线程并发也很自然。A卷的综合题部分有一道和“并发请求合并结果”相关的题目大意是有多个网络请求需要并发发起等所有请求都完成后统一刷新UI怎么设计。这个场景有几种解法使用GCD的dispatch_group_tdispatch_group_t group dispatch_group_create(); for (NSURLRequest *request in requests) { dispatch_group_enter(group); [self sendRequest:request completion:^{ dispatch_group_leave(group); }]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 所有请求完成刷新UI });使用dispatch_barrier_async做并发写控制但这个更适合对同一数据源进行读写隔离的场景。使用NSOperationQueue的maximumConcurrentOperationCount和NSBlockOperation的依赖关系设置最后一个操作依赖前面所有操作。使用信号量dispatch_semaphore_t做等待但需要注意不能在主线程等待否则会卡UI。这些方案里dispatch_group在“并发请求 统一回调”的场景里最直观代码也最简洁。但在真实业务中如果某一个请求失败也要走统一回调你需要额外处理错误状态确保leave一定被执行。我在笔试里专门强调了这个细节“要注意enter和leave的配对避免某个请求失败后无法执行notify导致UI一直不刷新。”4.4 同步和异步、串行和并发的组合理解这里额外分享一个我后来辅导校招学弟学妹时经常用的方法把GCD的API分成两个独立维度去理解。第一个维度是“同步/异步”描述的是提交任务的线程是否会等待同步执行sync当前线程会阻塞等待任务执行完成才继续往下走。异步执行async当前线程不会阻塞提交任务后继续执行下一步任务会在别的线程或当前线程的后续时机执行。第二个维度是“串行/并发队列”描述的是队列中多个任务之间的执行关系串行队列同一时间只能有一个任务在执行任务按提交顺序一个接一个地执行。并发队列可以有多个任务同时执行具体并发度由系统决定。把这两个维度交叉组合就能把死锁场景分析得很清楚。很多人死记“主队列 sync 死锁”却不知道为什么并发队列 sync一般不死锁原因就在于sync阻塞的线程并不是执行任务的唯一线程系统可以安排别的线程去执行这个同步任务所以不构成循环等待。理解了这套框架之后再看2018年迅雷A卷的那道选择题就能很自然地推理出答案而不是靠猜。5. UI布局与Auto Layout考点5.1 UIView的frame和bounds2018年迅雷A卷有一道和UI相关的选择题考的是frame与bounds的区别。这道题看似基础但如果对视图坐标系理解不深刻很容易答错。先说定义frame是视图在父视图坐标系中的位置和大小也就是“相对于爸爸”的坐标bounds是视图在自身坐标系中的位置和大小默认情况下bounds的origin是(0, 0)大小等于视图的大小。一个经典例子是把一个子视图的bounds的origin改成(50, 50)会发生什么答案是它的所有子视图都会向右下角偏移50个点而它自身在父视图中的位置不变。很多人第一次接触这个知识点时会觉得反直觉。实际上移动bounds的origin等于把子视图的内部坐标系原点进行了偏移相当于把它内部的“画布”整体挪动。这个考点在笔试里出现的意义在于它决定了你是否能正确理解和操作UIScrollView的滚动机制。UIScrollView的滚动本质就是不断修改bounds.origin让内容视图在窗口里移动。如果你把这个概念搞清楚了后面理解scrollView的contentOffset也会顺理成章。5.2 Auto Layout的优先级与Content HuggingA卷简答题里出现过“两个UILabel水平排列左边的label内容变长后右边的label被挤出屏幕怎么解决”。这道题考察的是Autolayout的Content Hugging和Content Compression Resistance。简单解释这两个概念Content Hugging Priority内容拥抱优先级值越大视图越不愿意被拉伸。Content Compression Resistance Priority内容压缩抵抗优先级值越大视图越不愿意被压缩。当两个label水平排列时如果约束不完全固定系统会根据这两个优先级决定哪个label被拉伸、哪个label被压缩。左边label内容变长时如果左边label的压缩抵抗优先级比较低系统就会优先压缩左边label的文字区域而不是把右边的label挤出去。反过来如果希望右边label先被压缩就应该降低右边label的压缩抵抗优先级。在实际工程中比较常见的做法是把左边label的Content Compression Resistance Priority设为750右边label设为250这样左边label优先保持完整右边label可以适当压缩。如果两个label都希望保持完整那就需要设置右边的label的宽度约束为小于等于某个值或者让左边label的宽度可以变化。A卷这道题的迷惑性在于很多应届生只学过拉伸和压缩优先级的概念但没有实际处理过UILabel自适应宽度的场景所以答得比较浅。只要结合“优先级决定谁让步”这个核心思想再把Content Hugging和Compression Resistance分开说清楚就能拿分。5.3 UIScrollView的Auto Layout“陷阱”2018年迅雷A卷里有一道综合题涉及UIScrollView和Auto Layout配合的问题。这个问题到现在依然是很多iOS开发者的痛点因为在Auto Layout下正确使用UIScrollView需要特定的约束方式。经典的约束方式是把UIScrollView添加到父视图给scrollView本身设置四边约束然后在scrollView内部添加一个contentView给这个contentView设置四边与scrollView对齐的约束最关键的是contentView的宽度约束要等于scrollView的宽度或者设置成一个固定的宽度约束。这样contentView的高度由内部内容撑开scrollView就能正确滚动了。如果只给contentView设置了top/left/bottom/right四个约束没有设置宽度等于scrollView宽度系统会报约束冲突或者滚动范围不正常。因为Auto Layout需要用约束来确定contentSize而contentSize又会反过来影响约束的计算循环依赖很容易出问题。在校招笔试中这类题目更偏向考察你是否理解“约束是确定大小的依据而不是单纯的位置关系”。如果平时没有实际操作过临时靠逻辑推理很难写对。6. 网络层与常见系统框架题6.1 HTTP与HTTPS的握手流程迅雷是做下载工具的因此A卷的网络部分占比比一般公司多一些。有一道简答题是“描述HTTPS的握手过程以及为什么HTTPS比HTTP安全”。这道题看起来是老生常谈但拿高分需要把“对称加密”和“非对称加密”的关系讲清楚。先把流程说一遍简化版是这么几步客户端发起HTTPS请求服务端返回证书。客户端验证证书合法性如果证书合法客户端从证书中取出服务端的公钥。客户端生成一个随机数pre-master secret用服务端的公钥加密后发给服务端。服务端用私钥解密得到这个随机数。双方用这个随机数生成对称加密密钥之后的数据通信都使用对称加密。HTTPS比HTTP安全的核心在于它通过非对称加密安全地交换了对称密钥之后用对称加密高效地传输数据。也就是说非对称加密解决了“如何安全地协商密钥”的问题对称加密解决了“传输效率”的问题。两者结合既保证了安全性又保证了性能。这道题在校招笔试里很常见但很多人的回答只停留在“HTTPS是加密的、HTTP是明文”这显然不够。你要能把握手流程、证书校验、密钥交换过程说清楚才能体现出你是真的理解而不是背了结论。6.2 TCP的粘包问题和可靠传输A卷的网络题里有一道“TCP如何保证可靠传输”这道题对iOS开发者来说看似有点远但在迅雷这种做传输业务的公司里属于基础中的基础。TCP的可靠传输主要靠几个机制序列号、确认应答ACK、超时重传、流量控制、拥塞控制。每个机制各司其职序列号让接收方能按序重组数据ACK告诉发送方“我收到了”超时重传保证丢失的数据能被重新发送流量控制通过滑动窗口防止接收方被数据淹没拥塞控制通过慢启动、拥塞避免、快速重传等手段避免网络过载。还有一种常见的面试追问是粘包问题。TCP是流式协议没有消息边界如果应用层不定义协议格式接收方就不知道一次recv拿到的是半个消息还是多个消息。常见的解决办法是在消息头部加入长度字段接收方根据长度字段读取完整消息或者使用特殊的结束符比如HTTP的\r\n\r\n或者使用固定长度消息。这些内容当时笔试没有直接考到很深但如果你能在答案里带上“TCP无边界、需要应用层协议”这个认知面试官会认为你对网络的理解是体系化的而不是零散背概念。6.3 性能优化从TableView卡顿到内存峰值2018年迅雷A卷的综合题部分最后一道问的是“一个列表页滑动时出现卡顿你会怎么排查和优化”这几乎是iOS面试里的必考题。虽然这句话已经快被说烂了但每次遇到还是会刷掉一批人原因在于很多人答得太泛没有真正说出“我会怎么做”。我的答案一般分层次展开第一层先定位卡顿是主线程耗时还是子线程冲突。用Instruments的Time Profiler看一下主线程耗时在哪些方法。第二层如果耗时在Cell的布局和绘制优先做异步绘制、提前计算高度、缓存高度。最典型的问题是cellForRowAtIndexPath里写了大量计算、图片解码、圆角裁剪。第三层如果耗时在图片加载考虑图片的解码放到子线程并用缓存机制避免重复下载和解码使用更小的缩略图不要直接把原图拿来做列表图。第四层如果有离屏渲染比如大量使用cornerRadiusmasksToBounds考虑把圆角裁剪放到后台绘制或者直接用带圆角的图片资源。我记得当时笔试里还专门强调了“不要在主线程同步加载网络图片”。这是一个非常经典的坑很多应届生的项目里都有类似写法刷到主线程加载图片列表一滑动就卡成PPT。在答案中主动点出这一点会让面试官觉得你有真实的性能调优经验。6.4 其他系统框架高频题KVO、NSNotification、BlockA卷的选择题里还有一小部分涉及KVO、通知和Block的使用场景这里简单梳理一下。KVOKey-Value Observing用来监听某个对象属性的变化常见场景是监听scrollView的contentOffset、监听模型数据变化刷新UI。但在OC中使用KVO有几个注意点观察者需要在dealloc中移除否则会崩溃被观察对象和观察者之间会形成一种隐式的观察关系不会强持有观察者但被观察对象强持有观察者的情况要特别注意。NSNotification是iOS中一种松耦合的通信方式适合跨模块、跨层级的消息传递。它最大的特点是解耦——发通知的一方不需要知道谁在监听监听的一方也不需要在同一个类层级。但也正是这种解耦会导致代码难以追踪大型项目中滥用通知会让调试变得非常痛苦。Block在OC中本质上是一个带有自动捕获变量的函数指针对象。它在ARC下需要考虑循环引用的问题这也是为什么所有闭包使用场景都需要检查是否形成了环。但Block也是现代iOS开发中MVP和高频用的语法特性网络回调、动画回调、集合遍历回调处处都是它。7. 从A卷复盘看校招准备策略7.1 做题之外的真正分水岭项目深挖2018年迅雷A卷整套题目做完我的最大感受是笔试并不是只靠刷题就能准备的它更像是对你真实项目经验的提炼。比如综合题里会问你网络层怎么设计、列表卡顿怎么排查、多线程并发怎么处理这些问题如果你在校招项目里真实做过答起来自然有血有肉如果只是背了几道题最后的综合题会明显透出“空”。我建议准备校招的同学在笔试前把自己项目里的几个关键技术点复盘清楚每一个都按“场景 - 方案 - 为什么”的结构写一遍。比如你的项目里图片加载是怎么做的是同步还是异步有没有缓存缓存策略是什么你的项目里有没有处理过内存泄漏怎么发现的怎么解决的你的项目里网络层是怎么封装的有没有考虑过错误重试和超时这些内容一旦在笔试综合题和面试中被问到就是实打实的亮点。同样的知识点从“我看过文档知道有这个API”变成“我在项目里遇到过这个问题我是这样解决的”可信度和专业度完全是两个量级。7.2 知识点梳理要按“原理-场景-代码”三维度关于基础知识的准备我的经验是不要只背结论要把每个知识点按照“原理-场景-代码”三个维度去整理。以“weak”为例原理对象释放时runtime会通过weak表将weak指针置为nil。场景代理属性、block中避免循环引用。代码__weak typeof(self) weakSelf self;以“RunLoop”为例原理RunLoop是线程内部的事件循环机制通过CFRunLoopSource、Timer、Observer驱动。场景Timer滚动暂停、AutoreleasePool释放时机、卡顿监控。代码把Timer加入commonModes。这样整理出来的知识网络笔试时遇到变形题才能快速定位到背后的原理而不是靠记忆去匹配题干。7.3 在线笔试的时间分配建议最后分享一下在线笔试的时间分配策略。2018年迅雷A卷总共题量不小而且是在线考试系统里完成很多人在单选题和多选题上过度纠结导致简答题和综合题没有写完整。我的建议是单选题如果30秒内没有明确思路先标记并跳过不要恋战。多选题可以先判断绝对拿不准的选项用排除法缩小范围。简答题哪怕不能写出完整答案也要把关键术语和思路分点列出来不要空白。综合题至少要写一个可运行的思路框架配合关键代码片段比写一大段空话强得多。在线笔试经常会因为网络波动、浏览器兼容之类的问题浪费时间像迅雷这套A卷如果是在线答题心里也要提前打好预防针。尽量提前十分钟进入系统检查摄像头权限、网络连接、浏览器兼容性避免开考后手忙脚乱。这类校园招聘的在线笔试本质上是一场“短时间内的能力展示”。你需要做的不是把所有知识都背到一字不差而是让阅卷人从你的答案里看到“这个人知道原理会写代码能解决实际问题”。把这三点做好了2018年迅雷A卷的题目即使换成2025年的公司你也能稳定发挥。