小红书2020年的校招iOS卷子我印象还挺深。那年我帮朋友内推过简历自己也收了好几份笔试题整理文档后来看了一圈发现这套卷子不是那种偏题怪题满天飞的类型反而把iOS开发最底层的几个模块翻来覆去地问。语言基础、Runtime、内存管理、UI布局、并发、网络、架构每一块都踩在面试高频点上。你要是准备校招或者工作两三年想回头补基础这套题完全可以当成一次自测清单逐项过一遍比自己闷头刷题有效得多。我身边有不少同学吐槽“笔试题又不代表真实开发”这话对一半。笔试确实不会考你“产品需求怎么排期”但它能快速筛出谁对底层机制有真实理解谁只是把API背熟了。小红书这套卷子的风格就是看着题目很常规答起来很考验深度尤其是Runtime和Block那几题基本能把只会surface level的人拦住。所以这篇文章不只是把答案列出来我会把每道题背后的原理、推导过程和面试官想听的点都说清楚。1. 卷面整体布局与考察思路拆解1.1 笔试考察的六大模块从2020校招的卷面分布来看小红书iOS方向笔试题主要分六块Objective-C语言特性、内存管理、Runtime与消息机制、并发编程、UI布局与事件处理、网络与数据持久化。另外还会穿插一两道架构设计和简单算法题算法难度通常在LeetCode Easy到Medium之间重点不是秀技巧而是考察代码规范度和边界处理。先说语言与内存这块题目密度最高因为它是iOS开发的地基。分类Category能否添加成员变量、Block对局部变量的捕获方式、weak关键字的生命周期这些题基本是必出的。原因很简单日常开发躲不开而且答得越深入越能体现功底。比如Category能不能添加属性表面答案是不能直接加成员变量但通过关联对象其实可以面试官这时候就会顺着问关联对象的存储位置。UI和架构题放在中后段AutoLayout相关题目一直是热门小红书2020这套卷子里还有一道用UIStackView实现动态列表的题考察点不只是API调用而是布局思路和性能取舍。并发和网络部分则通常以选择题加简答题混合出现考察GCD死锁、NSOperation依赖关系、HTTPS握手流程等。最后的架构题没有标准答案重点看你能否从代码复用、可测试性、模块通信几个维度进行权衡。1.2 为什么小红书偏爱基础题很多校招同学会好奇大厂笔试为什么不考更“高级”的内容其实不是不想考而是校招候选人的项目经历普遍同质化简历上写“使用MVC模块化开发”“优化APP启动速度”这类描述面试官很难判断真实水平。相比之下一道“解释消息转发流程”的题能直接暴露你对Runtime是真懂还是装懂。另外基础题最容易评估潜力。小红书内部非常强调客户端稳定性与体验优化而这两个方向都依赖底层原理。比如线上Bug排查时如果不懂Runloop很难理解卡顿是怎么产生的如果不懂内存管理内存泄漏问题基本就只能靠Instruments盲扫。这套卷子其实是在用题目画一条“入职后能独立解决什么问题”的能力线。还有一点基础题并不是死记硬背就能过的。同一个知识点试卷里会多角度追问比如先问“weak修饰的变量在对象销毁后自动置nil是怎么实现的”再问“为什么使用unsafe_unretained不安全”这种层层递进的设问方式对只会背答案的人非常不友好。所以备考时要把每一个结论背后的机制搞清楚而不是只记结论。2. 核心必考点语言基础与内存管理2.1 Category扩展与关联对象的底层区别这道题几乎是所有iOS笔试的“开胃菜”小红书的卷子里当然也有。考试形式一般是Category能否添加成员变量Extension和Category的区别是什么很多同学第一反应是“Category不能添加成员变量但可以添加方法”这个答案能拿一半分但要拿满分还得说出关联对象。Category在编译期生成的结构体是category_t它会把自己的实例方法列表、类方法列表、协议列表在运行时通过runtime的resolveInstanceMethod等机制合并到类对象中。但需要特别注意的是合并是发生在运行时类在加载时会把Category的方法列表插入到类方法列表前面所以同名方法会被Category覆盖而两个Category的同名方法则取决于编译顺序这个结论很多人不知道。至于属性Category里面写property只声明了getter和setter不会自动生成带下划线的成员变量。要实现真正的属性需要借助objc_setAssociatedObject和objc_getAssociatedObject。关联对象的值在内存中存放在一个全局的AssociationsHashMap中以对象地址为key关联值不会自动释放必要时要在dealloc中手动移除。这块面试官会追问关联对象的释放时机是否和对象一致实际上系统会在对象销毁时自动处理关联对象但如果你关联的对象被retain仍需小心循环引用。Extension则完全不同。Extension在编译期就把方法、属性、成员变量写进了类结构体里相当于把类的私有实现藏在了.m文件中。所以Extension添加的属性会自动生成成员变量和getter/setter。理解这一点你就能答出“Category是运行时添加Extension是编译期添加”这个最关键的差异。2.2 Block的捕获机制与循环引用Block不管在校招还是社招中都是必考题小红书这套卷子用了一道“写出下列代码输出的值”的选择题考察局部变量和__block修饰变量的捕获区别。这题的坑在于不理解Block捕获机制的人会以为Block内部随时能读取外部变量但实际情况是Block会把自己的变量捕获到结构体中并在Block对象创建时保存变量的值。默认情况下Block捕获局部变量是一份const拷贝所以在Block内部修改变量是编译不过的。要修改外部的局部变量必须用__block修饰。加了__block之后变量会被包装成一个结构体Block持有的是这个结构体的指针因此在Block内部和外部读到的就是同一份数据。这个机制类似C语言里的指针传递区别在于__block包装体还负责管理引用计数和变量生命周期。循环引用则是更深一层的考察点。self持有blockblock又强引用self就形成了retain cycle。解决办法是用__weak修饰self。但这里有个细节如果Block内部在异步任务完成后需要继续持有self用__weak之后self可能已经被释放所以更稳妥的写法是配合__strong在Block内部临时持有self确保执行期间self存活。很多面试官会追问“用weakSelf后为什么还要strongSelf”实际上是为了避免执行过程中self被提前释放导致状态不一致。2.3 ARC下内存管理陷阱ARC不是“自动垃圾回收”很多同学搞混了。ARC只是编译器在合适的位置插入了retain、release、autorelease代码本质上仍然是引用计数管理。小红书笔试题里有一道“ARC下dealloc中做什么”标准答案是释放非对象资源比如C语言指针、CoreFoundation对象、通知观察者移除等。但要注意ARC下dealloc里不能调用[super dealloc]编译器会自动处理。另外还有一道关于字符串、数组等容器在ARC下的内存语义。常见坑点是用“”创建的字符串常量存储于常量区不参与引用计数管理所以copy和strong没区别但NSMutableString使用copy修饰时得到的是不可变副本如果你在setter里对copy后的属性调用appendString就会崩溃。这个点面试官特别喜欢用来区分“字符串用copy还是strong”的问题。内存管理的另一个重灾区是NSTimer。NSTimer会强持有target而target又持有timer如果你在ViewController的dealloc中不手动invalidate timerdealloc永远不会被调用页面释放不了。解决方案是iOS 10之后使用带block的scheduledTimerWithTimeInterval:repeats:block配合weakSelf来打破循环或者使用中间代理对象包装target让timer只持有这个代理。笔试中不需要写出完整代码但一定要把循环链说明白。3. Runtime与消息机制详解3.1 消息发送完整流程Runtime是iOS开发者从初级跨越到中高级的分水岭。小红书这套卷子里出了好几道与Runtime相关的题其中消息发送流程是必考。Objective-C的方法调用本质上是objc_msgSend方法的动态绑定发生在运行时而不是编译时。当代码执行[self doSomething]时编译器会把它转换成objc_msgSend(self, selector(doSomething))。进入objc_msgSend后系统会先去查找对象的isa指针找到所属类然后在类的method_list中查找方法缓存。Runtime里有一个Cache每个类都会缓存最近调用的方法IMP查找时会先查缓存缓存没有命中再去方法列表里二分查找找到后替换到缓存并执行IMP。如果方法在类本身上没有找到就沿着superclass链继续向上找一直找到NSObject如果最终仍没有找到就会进入消息转发流程。消息转发可以拆成三步第一步是动态方法解析resolveInstanceMethod允许你以动态方式向类添加方法实现第二步是快速转发forwardingTargetForSelector允许你直接把消息转发给其他对象处理第三步是完整转发methodSignatureForSelector和forwardInvocation需要你生成方法签名并处理参数。笔试通常会问“消息转发发生在找不到方法后能否用来实现多继承”答案是可以模拟但不能替代真正的继承。3.2 Method Swizzling的注意事项Method Swizzling在笔试中出现的形式是“你如何在不改动原类的情况下给系统的viewWillAppear添加统计代码”。正确解法是写一个UIViewController的Category在load方法中交换viewWillAppear和自定义方法的IMP。但这里有一个面试官喜欢追问的点load方法里不需要调用[super load]因为系统会自动调用父类的load。写Swizzling时有几个容易踩的坑。第一方法交换必须在load中执行因为load是线程安全的且在类初始化时就执行一次同样initialize方法虽然也会调用但它可能被子类继承触发多次不适合做Swizzling。第二交换方法时不能通过self调用原方法否则会造成死循环正确做法是把原实现保存成另一个SEL并在新实现里调用objc_msgSend。第三交换分类中的方法不会自动影响其他已交换的方法如果多个类都Swizzle同一个方法调用顺序会变得非常难排查所以项目中建议做一个统一的Swizzling管理类。还有一个更高级的细节交换后原Class的layout可能发生变化泛型类型、KVC调用等也可能受影响。正常情况下Swizzling的IMP参数应该保持一致的函数签名不然会出现“unrecognized selector”或参数错位。写单元测试时尤其要覆盖原先调用系统的场景避免因为交换导致崩溃。3.3 KVO与KVC底层实现KVO和KVC也是高频题。KVO的底层原理是当对一个对象添加观察者时Runtime会动态生成一个子类比如NSKVONotifying_XXX并将对象的isa指针指向这个新子类。这个子类会重写被观察属性的setter方法在setter里给旧值和新值发送willChangeValueForKey和didChangeValueForKey通知从而触发观察者的回调。KVO有个必须记住的坑如果使用直接访问成员变量赋值比如_obj newValue是不会触发KVO的因为它在重写的setter中才发送通知。另外KVO对集合类型的支持需要手动调用mutableArrayValueForKey和mutableSetValueForKey或者用willChange/didChange包裹批量操作否则只有addObject第一次能触发后续操作不会发通知。在ARC下观察者移除是一个大坑对象释放前必须调用removeObserver否则会野指针崩溃。KVC则是一种更通用的键值编码机制它通过setValue:forKey:给属性赋值时会按照setKey、_setKey、直接访问成员变量的顺序查找找不到时会调用setValue:forUndefinedKey:默认结果是抛出异常。KVC在数据模型转换中使用非常频繁但它绕过了setter方法的封装如果属性readonly或受保护KVC可能仍然能直接修改这会导致一些意料之外的行为笔试题里通常会问“KVC能修改readonly属性吗”答案是能。4. UI与架构题从AutoLayout到组件化4.1 事件传递链与响应链关于UI部分小红书这套卷子有一道关于点击事件从屏幕到处理对象是如何传递的选择题。整个事件传递可以分为两大部分传递链和响应链。传递链从UIApplication开始顺着window、view层级向下寻找hitTest和pointInside都返回YES的view最终确定最底层的触摸View响应链则从最底层的View开始沿着superview链向上传递直到找到能处理事件的对象。hitTest方法会先通过pointInside:withEvent:判断触摸点是否在自身bounds内然后从subviews中倒序遍历子view递归调用hitTest。注意是倒序因为后添加的subview显示在最上层应该优先被点中。如果所有子view都不命中则view自身返回self。如果view的userInteractionEnabled、hidden、alpha小于0.01则不会被hitTest命中。这些细节面试官会逐条问尤其alpha为0的button到底能不能点击很多人记反。响应链方面UIResponder是核心UIViewController、UIView、UIApplication都是UIResponder的子类。如果最底层view不处理事件事件会顺着nextResponder向上传递。注意ViewController的nextResponder是window而不是superview这是一个容易记错的地方。另外手势识别器会优先于响应链处理触摸事件这也是为什么给button添加tap手势可能让button的action失效。4.2 UIStackView与自动布局实战小红书的架构题部分嵌入了一个UIStackView的实战场景要求在不使用Frame计算的情况下实现一个动态增删的标签列表。这道题的出现很典型因为UIStackView在iOS 11之后大量用于复杂页面布局而且它背后隐藏着很多约束计算的细节。UIStackView本身不会做布局它只是把对内部arrangedSubviews的约束管理统一处理。往UIStackView里添加或移除子视图时它自动更新子视图的translatesAutoresizingMaskIntoConstraints、设置alignment和distribution。但使用时要记住不要给stackView内部的arrangedSubviews单独设置太多与外部的约束否则可能引起约束冲突或ambiguous。正确做法是只设置stackView自身的位置约束以及每个子视图的尺寸相关约束。关于distribution属性的选择也是面试热点。fillEqually会让所有子视图宽高相等适合等宽导航按钮fillProportionally根据内容大小按比例分配适合带不固定宽度的标签equalSpacing则是保证间距相等适合动态长度文本。实际开发中我习惯用fillProportionally和自定义spacing来模拟流式标签它可以避免手动计算contentSize和换行但要注意不同系统版本对spacing行为有细微差异建议在iOS 11统一适配。4.3 iOS 11适配与分屏尺寸处理虽然题目是2020年但iOS 11的适配在当时仍是重点因为从iPhone X开始安全区、HomeIndicator、分屏多任务都成了必须处理的问题。笔试中有一道关于safeAreaLayoutGuide和自动布局关系的简答题核心点就是要保证自定义View的底部不被HomeIndicator遮挡顶部不受刘海屏影响。safeAreaInsets是从iOS 11开始引入的UIView属性它表示安全区域相对view frame的偏移。用AutoLayout时比较规范的做法是让子View锚定到safeAreaLayoutGuide而不是直接锚定view的边缘。如果使用frame布局需要在layoutSubviews中读取safeAreaInsets并重新计算。这里有个很常见的坑在viewDidLoad里读取safeAreaInsets通常都是0因为布局还没完成正确时机是viewDidLayoutSubviews之后。分屏和iPad尺寸适配同样会进入校招题目。iPad的分屏功能要求应用必须支持所有屏幕方向和任意窗口尺寸如果使用iPhone的固定Frame逻辑分屏后布局会直接错乱。美团、小红书这类内容产品对iPad支持也比较重视所以笔试中的场景可能是“实现一个在iPhone和iPad上都能正常显示的列表详情页”本质上就是要求你使用StackView结合大小class适配而不是根据屏幕宽高写死。5. 并发与系统机制常见题5.1 GCD队列与死锁GCD是iOS并发编程的重头戏笔试题里“以下代码会不会死锁”几乎是标配。典型代码是这样的dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(hello); });这段代码在主线程执行时一定会死锁。因为主线程在执行dispatch_sync时会等待block执行完而block又要放到主队列中执行但主队列被当前任务阻塞互相等待就死锁了。但如果这段代码是在子线程执行的那么主队列没有被当前线程任务占用dispatch_sync到主队列反而可以正常执行。这个区别非常重要很多同学只会背“主线程同步主队列死锁”但不理解本质。除了死锁GCD还有一个高频考点全局队列与主队列、串行队列与并发队列的区别。全局并发队列可以通过QOS指定优先级但不保证FIFO执行串行队列严格按添加顺序执行适合共享资源保护。用dispatch_queue_create创建串行队列时要注意不要在主线程同步任务到当前串行队列也会死锁原理和主队列一致。信号量也是常见扩展点。dispatch_semaphore可以用于控制并发数和同步等待比如等待网络请求返回再继续执行。使用信号量时要注意超时时间dispatch_semaphore_wait的第二个参数填DISPATCH_TIME_FOREVER会永久阻塞如果信号量一直没被signal线程会卡死。笔试中通常会让你写出“使用信号量把异步回调变成同步”的代码考验对线程阻塞的敏感度。5.2 NSOperation依赖与取消NSOperationQueue虽然是GCD封装出来的但它的高级特性让它在面试中也占有一席之地。小红书这道题问的是有三个网络请求A、B、C其中C依赖A和B都完成后再执行用NSOperation如何实现。答案是创建三个NSOperation给C设置addDependency:A和addDependency:B然后把它们加入同一个操作队列。依赖操作的核心是状态管理。当A或B执行到isFinished时队列才会判定依赖满足进而启动C。所以自定义Operation时必须在正确时机更新isFinished状态否则依赖永远不会触发。这个坑在并发Operation中尤其常见因为系统调用KVO监听isFinished而不是轮询。取消操作也要展开讲。调用[operation cancel]并不会真正停止正在执行的任务它只是把isCancelled置为YES。如果你在main方法里执行的是一个长耗时任务需要在任务内部分阶段检查isCancelled发现被取消后立即清理资源并设置完成状态。如果不处理这一点取消操作会导致任务继续跑浪费资源甚至出现竞态问题。5.3 通知、KVO与Delegate的选择系统机制题里还有一道关于“什么时候用通知、什么时候用KVO、什么时候用Delegate”的设计题这题没有标准答案关键是展示你的架构判断能力。通知适合一对多事件比如登录状态变化、网络状态切换这种场景下如果使用Delegate会非常拧巴KVO适合监听某个属性值的连续变化比如进度条、播放器状态但要注意KVO的观察者生命周期管理Delegate适合一对一的事件回调比如列表点击、网络请求返回。实际开发中KVO和通知经常被误用。比如有人用KVO监听多个对象的同一属性然后在一个observeValueForKeyPath里写一长串if判断极难维护。更合理的方式是用block型KVO封装或使用响应式框架。通知中心虽然解耦但滥用也会导致代码流难追踪尤其是没有移除观察者时有可能回调已经释放的对象导致崩溃。笔试答题时要把三者的适用场景说清楚然后补充一句“它们不是互相替代而是互补关系”这句话很加分。面试官更希望看到你理解每种机制背后的实现方式和线程限制比如KVO回调默认发生在修改属性的线程而通知的post和接收默认是同步的。这些细节体现出你是否在真实项目中踩过坑。6. 网络与数据持久化高频题6.1 HTTPS握手与数据抓包排查网络题在2020年的卷子里一般不会太深但HTTPS握手机制几乎是必备。面试官经常问“HTTPS为什么安全”答案不是简单的加密而是非对称加密交换密钥、对称加密传输数据、数字证书验证身份三者的结合。具体到iOS开发NSURLSession的ATS默认要求2017年后强制HTTPS如果你的服务端证书是自签名或过期请求会直接失败。除了理论做题时还常遇到一个场景题APP请求数据一直失败请你列出排查步骤。这里回答框架可以包括先用Charles或代理工具抓包看请求是否到达服务端再看返回的状态码和错误信息然后检查ATS配置是否阻断了HTTP请求接着确认证书校验策略、缓存策略、请求参数编码。抓包时如果遇到SSL握手失败多半是证书验证未通过可以在工具中设置SSL Proxying并安装相应的调试证书。需要特别提醒的是笔试中让你说“用Charles做iOS抓包”只需要写出大概原理在电脑上启动代理手机设置HTTP代理指向电脑IP和端口安装Charles根证书到手机信任列表这样才能解密HTTPS流量。但现在的APP很多都做了防代理或证书校验面试官可能会追问“如果APP做了SSL Pinning怎么办”这时候就要说可以使用HOOK方案或查看校验代码通常不会要求你真正实现破解。6.2 数据持久化方案对比持久化题一般会问iOS常用的数据存储方案有哪些怎么选择标准答案是NSUserDefaults、plist、归档NSKeyedArchiver、SQLite、Core Data、Keychain。NSUserDefaults适合存轻量用户偏好但它底层在应用启动时读取整个plist放大量数据会影响启动性能。归档可以把自定义对象序列化为Data适合存单个对象模型但整存整取代价高不适合频繁修改。SQLite是大多数IM和内容型APP的存储主力小红书的笔记数据和搜索记录大概率是模型层用SQLite或封装的数据库来管理。笔试中会问FMDB和系统SQLite的关系FMDB是SQLite的Objective-C封装处理了线程安全、语句绑定和查询结果映射减少手动写C函数的成本。另外还有WCDB等更高级的封装面试题不会考太深但要能说清楚事务和索引可以提升大量写入查询性能。Core Data和SQLite的区别也常考Core Data是对象图管理框架不只是存储层它的底层可以用SQLite存储也可以使用内存存储。Core Data通过NSManagedObjectContext管理对象状态有Undo/Redo机制但学习成本高、线程模型复杂。笔试中问到存储方案你最好结合项目实际场景说出选择理由比如“笔记列表需要频繁插入和查询使用SQLite加索引更有优势”这种回答比背书式列举方案得分高。6.3 简历项目与架构追问最后一类网络与架构题其实已经不纯粹是笔试题了更像是“笔试题电面连线”的开放式问题。卷子上可能只写一句“谈谈你项目中使用的网络层架构”但实际上考察的是MVC、MVVM、分层思想、依赖注入、网络层错误处理等综合能力。小红书的iOS开发向来比较关注稳定性和模块化所以这类题往往是压轴。作答时可以围绕三层网络封装展开第一层是网络请求工具比如基于NSURLSession封装GET/POST、统一管理header和参数签名第二层是业务请求层每类请求一个方法负责参数序列化和结果映射第三层是数据管理层负责缓存、数据库落库和错误状态通知。如果项目里用到了Swift还可以讲讲如何用泛型和协议约束请求响应类型。架构题的分在于逻辑自洽而不在于标准答案。如果你的方案是“所有请求都堆在一个APIManager里”面试官会继续追问如何测试、如何扩展这时候往往就暴露问题了。更好的回答是点出职责分离比如网络层的错误码统一处理、业务层的成功失败回调定义清晰、持久层和UI完全解耦甚至可以提一下用依赖注入来mock网络请求做单元测试这些都是没有实际经验沉淀很难编出来的内容。7. 解题策略与备考心得7.1 拿到题目后的拆题顺序我见过不少同学笔试时时间分配很迷选择题纠结十分钟简答题草草写两行。小红书这套卷子题量不小建议按分数价值和时间顺序来拆先做简单的选择题和判断题控制在15到20分钟内再做基础简答题比如Category和Block这些是送分题但也要写完整最后留充足时间给UI场景题和架构题因为它们需要构思代码和文字说明。如果遇到不会的题不要直接跳过尽量写一部分思路。阅卷者通常更看重你的推导链路。比如问到“消息转发流程”如果你能画出resolveInstanceMethod到forwardInvocation的完整步骤并写出一段示例代码即使细节有偏差也会比只写“我不会”强得多。答题时一定要分点用序号和段落标题方便阅卷者快速抓住重点。7.2 被追问时如何引导面试官部分笔试会在答题后面附带“如果你在真实项目中遇到这个问题会如何继续排查”的追问这其实是一种压力测试。应对方式是把问题往自己熟悉的方向引导。比如被问到“多线程并发读取同一数组如何保证安全”可以先答串行队列或synchronized加锁然后延伸到“datalist的读写分离”提到你曾经用读写锁优化的实践经验。这样既回答问题又展示项目能力。不要为了显得高端而主动抛出自己完全不懂的概念。面试官一旦顺着往下问你答不上来会非常减分。更稳妥的做法是“答完原理后补充一句我目前在生产环境主要用xxx方案另外xxx方案我也了解原理但没有实际落地过”这种诚实姿态反而更容易获得好感。7.3 常见失分点结合我的观察校招同学在这套卷子上的失分点集中在几个地方。第一是术语不准确有人把“消息转发”和“消息发送”混为一谈或者把“自动释放池”说成“GC”这都是印象分硬伤。第二是答得太表面比如问到Block为什么导致循环引用只写“因为相互引用”但没有画清晰引用关系、没有给出weakSelfstrongSelf的典型写法。第三是代码不规范手写的代码不写分号、命名随意、没有处理边界条件阅卷官一看就知道没怎么上过线。应付这些失分点我的建议是考前把基础篇的常见代码手写一遍不是背而是真的一行行敲然后对照标准答案找差距。笔试中的代码题不要求能直接编译运行但逻辑完整、风格统一、注释恰当会让阅卷官觉得你是“自己人”。毕竟校招笔试拼的是基本功和心态这两样都准备到位了通过率自然就上去了。说回这套2020年的小红书iOS笔试题它给我的整体感觉是考的东西都不偏但每道题都留了深入的入口。你在准备时不用去背那些冷门API反而是把Category、Block、Runtime、GCD、KVO这些平时天天用的东西往底层多挖一层效果最好。我个人带过的几个朋友里能过这套笔试的都是真能坐下来把问题讲明白的人而不是简历上堆了一堆“熟悉底层原理”的人。如果你现在也在准备校招我建议你按这套卷子的模块做一次自测把每个主题都写成一篇能讲给别人听的笔记这一轮下来比盲目刷几十道题都管用。