2017年欢聚iOS校招笔试题解析:底层原理与高频考点全拆解 前阵子整理网盘里的旧资料翻出一份2017年欢聚时代iOS工程师校招的A卷笔试题目。当时我也在准备校招对YY这场笔试印象挺深——不是因为题有多偏而是题风很“典型”不炫技、不刁难但能把基础不扎实的人筛得明明白白。七年过去Swift早已成为主流iOS 16、17都迭代了几轮但这份A卷里涉及的很多知识点在今天的技术面试中依然是高频考题。很多人会问2017年的笔试题现在还有什么参考价值我的回答是价值非常大。原因很简单——笔试考的是语言特性和系统原理而底层机制不会因为API的更新换代就彻底改变。ARC在2011年就引入了Runtime从Object-C诞生那天起就是它的灵魂GCD的同步异步队列模型到今天依然是并发编程的基石。技术框架会变原理不会。这篇文章我不打算把原题逐条列出来“报答案”而是把这些考点拆开揉碎聊聊每道题背后的设计意图、常见错误以及放到今天应该怎么理解和应对。主要面向两类人一是正在准备iOS校招、社招的同学想看看老牌互联网公司怎么考察候选人二是工作了一两年的iOS开发者想系统性查漏补缺把之前“知其然不知其所以然”的内容补扎实。1. 一份2017年的笔试为什么到现在还能翻出来看1.1 欢聚时代当年的技术基本盘欢聚时代在2017年已经是直播领域的头部玩家。YY语音从游戏语音工具起家积累了庞大的用户体量后来转型做YY直播、多玩游戏网客户端技术栈覆盖iOS、Android、Windows桌面端。iOS团队在当时的核心业务是YY直播客户端、短视频录制、IM即时通讯这几个大模块。这块业务特点决定了它对iOS工程师的要求既要懂UI层复杂的直播交互也要懂音视频处理、网络优化、内存治理这些深度技术。所以笔试题目不会脱离实际业务去考太偏门的内容而是围绕“你日常开发中最应该掌握的东西”来出题。换句话说这份考卷考的不是“你会不会这个API”而是“你有没有真正理解自己每天都在写的代码”。1.2 这份考试卷考察的能力模型从整体结构看这份A卷大致覆盖了六个能力维度能力维度考察内容原因分析Objective-C语言基础属性修饰符、分类延展、KVC/KVO当时95%的iOS业务代码仍是OCRuntime底层机制isa指针、消息转发、方法交换排查崩溃、实现AOP绕不开Runtime并发编程GCD队列、线程同步、死锁直播间消息渲染、音视频解码都是高频并发场景网络与数据HTTP协议、数据解析、本地持久化所有客户端核心都在做“请求-解析-展示”数据结构与算法链表、二叉树、动态规划、字符串逻辑思维的普适性考察不问技术栈UI与性能TableView优化、离屏渲染直播聊天和刷礼物列表对性能极其敏感看到没有这个结构放到2024年依然是校招iOS笔试的标准模板。变化的只是语言从OC变成了Swift优先UIKit还在但SwiftUI开始普及性能优化的具体手段更新了一轮。核心考察逻辑没变语言基础 系统原理 并发能力 数据结构。1.3 哪些考点沉淀成了今天的“铁律”后来我问过身边做iOS面试官的朋友他们现在出题依然围绕上面这张表。有几个考点被反复验证是“真的好用”属性修饰符weak/copy/strong/assign的差异考察内存管理基本功Block的循环引用和变量捕获考察对内存语义的理解Runtime的关联对象考察动态特性是否真正掌握GCD死锁分析考察并发模型的底层理解链表反转、二叉树层序遍历考察基础算法思路三年五年的框架迭代这些东西始终稳定出现在笔试题里。因为它们代表的是底层原理而不是API使用方法。2. Objective-C基本功Swift时代也必须懂的“老底子”这一章节对应试卷中最前面的一组基础题。很多考生觉得“属性修饰符”太简单结果往往在这一块丢分最多。2.1 属性修饰符从声明到内存语义当年的经典考法是给你一段代码问你以下属性声明的区别以及为什么这样做property (nonatomic, copy) NSString *name; property (nonatomic, strong) NSMutableArray *dataArray; property (nonatomic, weak) idDelegate delegate; property (nonatomic, assign) NSInteger count;看起来平平无奇但这里藏着好几个关键理解copy 和 strong 的区别。对于NSString属性用copy是行业默认因为当外部传入一个NSMutableString并随后修改它时strong属性会跟着变化而copy属性不会。我在实际开发中就踩过这个坑——从服务端拿到的字符串拼接完存进属性结果别处修改了原字符串界面上的数据被连带改掉了。后来统一改成copy数据就稳定了。weak和assign的区别。两者都不会增加引用计数但weak在对象释放后自动置nil而assign会变为野指针dangling pointer。iOS 11之后这里还有一个容易被忽略的点用assign修饰的delegate在ARC下不会崩溃不对它大概率会崩溃。尤其当对象可能被其他地方释放时assgin指针指向的内存已被回收访问时就是野指针异常。所以delegate统一用weak这是一条铁律。readwrite和readonly。这个在笔试里容易被当成送分题。但实际工程里readonly的意义是让你的属性只读对外暴露简洁接口。很多新人写代码不管外部可不可写全都用readwrite后面维护的时候才发现状态被人随意改动查都查不到哪里改的。2.2 分类、扩展、关联对象一道题看出你到底写没写过大型项目这一块几乎年年出现在欢聚时代的笔试里。最常见的问法分类Category和扩展Extension有什么区别 分类里能添加属性吗不能添加的话如何实现类似的效果分类的底层结构是category_t结构体包含实例方法列表、类方法列表、协议列表、属性列表。它允许你在不修改原类的情况下给类添加方法。但它不能直接添加实例变量。扩展是在编译期就将内容合并进主类因此可以添加实例变量和属性。它的声明写在.m文件里只对当前类内部可见相当于私有接口。关于分类里的属性标准答案是通过关联对象Associated Objects来模拟property (nonatomic, strong) NSString *categoryProperty; - (void)setCategoryProperty:(NSString *)categoryProperty { objc_setAssociatedObject(self, selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)categoryProperty { return objc_getAssociatedObject(self, selector(categoryProperty)); }这里有个经验关联对象用selector(categoryProperty)作为key比随便定义一个字符串更安全因为selector不会重复。而且要在dealloc或者合适时机调用objc_removeAssociatedObjects吗基本不需要系统会在对象销毁时自动清理关联对象手动移除反而容易出问题。2.3 代理、通知、Block何时选谁考察你的架构意识很多笔试会出这样一道设计题同一句“用户登录成功”的消息你会用代理、通知还是Block来传递为什么这题没有绝对标准答案但面试官想听的是你脑子里的权衡代理一对一的回调语义清晰方法列表可显式查看适合“上传进度反馈给页面”这种强关联场景。缺点是代码分散一个页面代理方法多了之后管理成本陡增。通知一对多解耦能力强适合“登录状态变化影响了多个模块”这种广播场景。缺点是调试困难你不知道这条通知被谁监听也无法编译期检查。Block代码集中上下文连贯适合“网络请求完成后的局部回调”。缺点是容易造成循环引用而且比代理更容易让代码块变得不好维护。我个人的经验是单一回调、强关联用代理或Block跨模块、无强绑定的广播场景才用通知。这个问题在2017年问2024年也在问答法其实始终没变。3. Runtime与消息机制笔试里最能拉开分差的重头戏欢聚时代这份A卷里Runtime题量不小。这很合理因为Runtime是OC区别于Swift等语言的核心也是排查崩溃、实现AOP、搭建组件化框架的关键工具。3.1 对象、类、元类读懂这张关系图Runtime就懂了一半先看一段基本代码Person *p [[Person alloc] init];这一行代码背后Runtime到底做了什么笔试常考的点是p是一个指向Person类实例的指针实例的isa指向它的类对象Class类对象的isa指向它的元类MetaClass元类的isa指向根元类形成闭环对象的方法存储在类对象中类方法存储在元类中。这就是为什么一个实例能调用实例方法一个类能调用类方法的最本质原因。这张关系图在面试中经常被要求手绘。能画清楚的人说明对OC对象模型有整体认知而不是死记API。3.2 消息发送与消息转发笔试必考排查崩溃必备OC的方法调用“看起来”像函数调用其实是发送消息。具体流程[object doSomething]; // 编译为 objc_msgSend(object, selector(doSomething));objc_msgSend的执行流程通过object的isa找到类对象在类对象的方法缓存cache_t中查找方法找到了就调用缓存没命中在方法列表中查找类对象没找到沿着继承链往父类找都找不到进入消息转发流程消息转发有三次机会这是笔试最喜欢的考点// 第一步动态方法解析允许你动态添加方法 (BOOL)resolveInstanceMethod:(SEL)sel; // 第二步快速转发把消息转给其他对象 - (id)forwardingTargetForSelector:(SEL)sel; // 第三步完整转发封装方法签名后走NSInvocation - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; - (void)forwardInvocation:(NSInvocation *)invocation;我把这个流程理解为“打客服电话”先看看本地有没有这个选项缓存再翻翻服务手册方法列表还不行就问同事转发给其他对象最后实在解决不了就发工单NSInvocation。这样记就不容易乱了。实际开发中消息转发最常见的用途有两个防止崩溃找不到方法时返回一个兜底实现和组件化解耦把消息转发给真正的实现类。但说实话我强烈不推荐系统性地用它“防崩溃”滥用会让错误被吞噬查问题的时候痛不欲生。只在明确需要的方法签名动态性的场景使用才是合理的。3.3 Method Swizzling笔试考概念实际上手全是坑方法交换Method Swizzling是Runtime里最“出名”的技术也是笔试里大概率出现的题。标准实现 (void)load { static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ Class class [self class]; SEL originalSelector selector(viewWillAppear:); SEL swizzledSelector selector(yy_viewWillAppear:); Method originalMethod class_getInstanceMethod(class, originalSelector); Method swizzledMethod class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); }为什么先加后交换这是为了解决“父类实现了viewWillAppear:子类没有实现”的情况。先尝试把swizzled方法作为originalSelector的实现添加进去如果子类原本没有该方法的实现就直接添加成功若已有实现再交换两个方法IMP。这里如果不做检查直接交换可能会导致父类方法被错误覆盖。实际开发中的坑swizzle时机从load改成initialize的话会受子类继承影响导致多次调用所以应该放在load中用dispatch_once保证只执行一次。不要滥用每个人都在全局swizzle viewDidLoad这种通用方法会导致调用链混乱新增逻辑的副作用不可控。安全退出如果被Swizzle的方法所在类或框架发生变动旧逻辑可能失效。所以在实现里最好还是调用一下原方法保证原有行为不被破坏。3.4 KVC与KVO会用的很多真正理解原理的很少笔试里经常问KVO的实现原理以及setValue:forKey:触发KVO的底层机制。KVO基于Runtime动态生成子类并重写被观察属性的setter方法在setter里调用willChangeValueForKey:和didChangeValueForKey:。同时把对象的isa指针指向新生成的子类这样调用setter时就会触发通知回调。KVC则是通过setValue:forKey:触发setter系统查找顺序是setter方法 - 成员变量 - 私有变量最后还可能走setValue:forUndefinedKey:而崩溃。这里有价值的考点是用成员变量直接赋值不会触发KVO但用KVC一定触发。因为KVC会走setter方法。我在实际项目中遇到过一个问题数据源更新用了下划线成员变量直接赋值界面死活不刷新查了很久发现是忘了走KVO。这件事之后我记住了一条规律要触发KVO就必须通过setter或KVC改值。KVO还有一个经典坑多次添加同一个观察者系统会异常崩溃重复addObserver。实现protect的地方不多需要开发者自己管理注册和移除的成对性。4. 多线程考官真正想看的是你会不会写出死锁直播产品的多线程场景很多IM消息从socket通道进来要在子线程解析然后上主线程刷新UI礼物动画播放回调在子线程要切主线程展示。所以欢聚时代笔试里的并发题水平不低。4.1 GCD的常用考题模板我翻了下回忆大概有这么几类// 1. 异步执行 并发队列 dispatch_queue_t queue dispatch_queue_create(com.yy.test, DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(任务1); }); dispatch_async(queue, ^{ NSLog(任务2); }); // 2. 主队列异步 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(主队列任务); }); // 3. 串行队列同步 dispatch_sync(queue, ^{ NSLog(同步任务); });第一题问输出的不确定性很多人直接答错。并发队列异步执行任务1和任务2可能先后顺序不定。只要理解了并发队列是“多个线程同时跑”就不会搞错。第二题要区分“主队列里加异步任务”和“主线程直接执行”。主队列是串行队列异步任务依然会在主线程上运行因为主队列的特殊性就是绑定主线程。所以这个任务不会开新线程。第三题才是这里最关键的知识点如果在当前串行队列里调用dispatch_sync去同步提交一个新任务当前线程会等新任务完成而新任务又在等当前队列空闲于是死锁。类似于“自己等自己”肯定等不到。4.2 死锁场景一个经典误用案例笔试里最经典的一个死锁案例我到现在还是会在技术群里看到新人踩- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue dispatch_queue_create(com.yy.serial, DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ dispatch_sync(queue, ^{ NSLog(会不会执行); }); }); }这段代码在新创建的串行队列中执行了dispatch_sync目标队列和当前队列是同一个串行队列。串行队列的特点是同一时刻只允许一个任务执行外层的dispatch_async任务还没结束内层同步任务当然无法开始于是互相等待死锁。这个题目在笔试里出现率极高因为面试官想考的是你对“同步串行”的理解而不只是一个API的名字。放在今天Swift里如果你用Task和MainActor同样有类似的风险在MainActor上下文里同步调用一个会导致死锁的操作只是表现形式变成了任务阻塞和卡死。原理是相通的。4.3 从同步到并发信号量与栅栏除了死锁这份考卷还会考你“如何在并发队列中安全写文件”这类问题。经典做法是使用dispatch_barrier_asyncdispatch_queue_t queue dispatch_queue_create(com.yy.concurrent, DISPATCH_QUEUE_CONCURRENT); - (void)readData { __block id result; dispatch_sync(queue, ^{ result [self.data copy]; }); return result; } - (void)writeData:(id)newData { dispatch_barrier_async(queue, ^{ self.data newData; }); }这个模式叫多读单写读操作可以并发进行写操作必须是独占的。barrier保证了提交到队列中的block会等待前面所有任务执行完成再单独执行之后才会让后面的任务继续。这是iOS开发里写缓存、写数据库的常见优化。dispatch_semaphore也是高频考点。dispatch_semaphore_t sem dispatch_semaphore_create(0); // 异步任务A dispatch_async(queue, ^{ // 耗时操作 dispatch_semaphore_signal(sem); }); // 同步等待任务A完成 dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));这里要注意wait必须用超时来兜底。我见过无数线上卡死就是用了DISPATCH_TIME_FOREVER等待某个异步回调结果回调因为网络异常永远不来。4.4 NSOperationQueue为什么有些场景比GCD好用NSOperationQueue在2017年的笔试里算是比较进阶的题了。但坦白说很多回答都停留在“能取消、能设置依赖”这个层面没有讲清楚为什么需要它。我的理解是GCD处理“一次性任务”非常轻量但如果你要做“任务编排”比如三个网络请求后一个依赖前两个的结果或者用户离开页面时要取消所有未完成的任务GCD会很别扭。而NSOperationQueue天然支持这些能力取消任务操作执行前可以setCancelled彻底避免启动执行中通过检测isCancelled协作退出。依赖关系addDependency可以建立任务执行顺序而GCD需要自己用信号量或group来实现。最大并发数maxConcurrentOperationCount可以限制并发量避免同时发起太多请求。我在直播项目里做过一个图片预加载组件就是基于NSOperationQueue把图片下载包装为Operation设置依赖和优先级用户滑动时动态取消不可见区域的加载。这套逻辑用GCD写会很痛苦用OperationQueue则顺手很多。5. 网络层与数据流客户端工程师绕不开的硬知识客户端本质上是网络节点所以网络相关的笔试题目永远不会缺席。5.1 HTTP协议笔试里问得最多的是这几层2017年的笔试对网络部分的考察集中在这些点TCP三次握手和四次挥手的过程HTTP请求报文和响应报文的基本格式GET和POST的区别没有标准答案要根据语义理解HTTPS相比HTTP多做了什么SSL/TLS握手、证书校验其中让我印象最深的是HTTP的无状态性。很多面试者搞不清Cookie和Session在无状态协议里扮演什么角色。简单说HTTP本身不记忆用户身份所以客户端第一次请求时服务器创建一个Session并给客户端发一个SessionId客户端后续请求带上这个Id通常就是Cookie服务器才能知道“你是谁”。放到今天的场景很多同学开始用URLSession和async/await写网络层对底层TCP/UDP的细节更不关注了。但排查问题时你需要从“为什么这个请求会卡住”“DNS解析为什么慢”“为什么有时候TLS握手失败”等底层问题入手。基础不牢一旦线上故障就会无从下手。5.2 模型映射与JSON解析当年笔试里比较贴近业务的是这样一道场景题直播间弹幕数据返回一个JSON数组每条弹幕有用户名、内容、时间戳请设计数据模型并写解析代码。这题考的不只是API调用更是数据结构设计能力。我当年写的答案是interface DanmakuModel : NSObject property (nonatomic, copy) NSString *nickname; property (nonatomic, copy) NSString *content; property (nonatomic, assign) NSTimeInterval timestamp; property (nonatomic, assign) NSInteger type; end implementation DanmakuModel - (instancetype)initWithDictionary:(NSDictionary *)dict { self [super init]; if (self) { _nickname dict[nickname] ?: ; _content dict[content] ?: ; _timestamp [dict[timestamp] doubleValue]; _type [dict[type] integerValue]; } return self; } end这里面几个细节值钱用?:兜底防止服务端返回null导致数据模型里出现NSNull之后的UI渲染和逻辑判断都会出问题。doubleValue转换时间戳注意服务端可能返回字符串类型的数字。模型里所有属性都用copy或assign的语义明确规定避免外部修改。现在Swift时代用Codable核心思路完全一样兜底、类型转换、数据隔离。5.3 本地持久化的选型SQLite、CoreData还是后来者笔试里会问直播App的聊天记录、礼物记录应该怎么存储你会怎么选理由是什么这个问题今天依然适用。我当时给的答案是大量结构化数据、需要条件查询用SQLite或基于它封装FMDB/GRDB。简单字典数据、用户偏好用NSUserDefaults现在对应UserDefaults就够了。对象关系映射场景CoreData在特定场景下会加快开发但同步机制十分复杂团队不熟慎用。一个直播App的聊天记录表通常会有这种结构CREATE TABLE IF NOT EXISTS chat_msg ( id INTEGER PRIMARY KEY AUTOINCREMENT, room_id TEXT NOT NULL, sender_name TEXT, content TEXT, msg_time INTEGER, msg_type INTEGER ); CREATE INDEX idx_room_time ON chat_msg(room_id, msg_time);加索引是优化核心。查询某一房间一段时间内的消息走联合索引能避免全表扫描。这个优化思路在面试里讲出来会明显加分。6. 数据结构与算法笔试里真正决定你offer的压轴部分算法题在2017年的欢聚时代笔试里占了不少篇幅。很多iOS候选人想不通客户端开发为什么考这么重的算法我的理解是算法不是考你背模板而是考你的逻辑链条是否清晰这是做复杂客户端功能的基础。6.1 链表操作iOS面试里出场率最高的算法题链表反转几乎是标配中的标配。递归版和迭代版都要会// 递归版 ListNode *reverseList(ListNode *head) { if (head nil || head-next nil) return head; ListNode *newHead reverseList(head-next); head-next-next head; head-next nil; return newHead; } // 迭代版 ListNode *reverseList(ListNode *head) { ListNode *prev nil; ListNode *cur head; while (cur ! nil) { ListNode *next cur-next; cur-next prev; prev cur; cur next; } return prev; }递归版本要理解“把已反转部分看作一个整体”否则很容易晕。我面试别人时发现能写出迭代版的人很多能讲清楚递归版每一层返回什么的人很少。6.2 二叉树从遍历到层级结构的考察二叉树题目常见的有前中后序遍历递归非递归、层序遍历借助队列、求树高度、判断两棵树是否相同。笔试里比较常见的是层序遍历因为适合用队列考察基础能力- (NSArray *)levelOrder:(TreeNode *)root { if (!root) return []; NSMutableArray *result [NSMutableArray array]; NSMutableArray *queue [NSMutableArray arrayWithObject:root]; while (queue.count 0) { NSInteger size queue.count; NSMutableArray *level [NSMutableArray array]; for (NSInteger i 0; i size; i) { TreeNode *node queue[0]; [queue removeObjectAtIndex:0]; [level addObject:node.val]; if (node.left) [queue addObject:node.left]; if (node.right) [queue addObject:node.right]; } [result addObject:level]; } return result; }这里面有个细节循环前先记录size层内只处理当前层节点避免把下一层混进同一层。很多人在纸上写的时候逻辑容易出错实际上只要画一张小图推演一遍就好了。6.3 字符串与动态规划笔试题里另外一类是字符串和动态规划比如最长公共子序列、最长回文子串。这类题目的解题模板非常多关键在于先暴力递归再优化为动态规划。笔试的时间有限能给出DP模板并正确推导转移方程基本就能过关。一个非常重要的应试经验是如果时间不够先保送简单题不要在一道题上死磕超过20分钟。笔试不是竞赛是综合评估合理分配时间本身就是能力的一部分。7. 从这份卷子反推欢聚时代的用人标准这一章说说我从一份笔试题里读出的公司用人逻辑。也许对准备面试的同学有启发。7.1 基础与原理的权重非常高从卷面分布看基础原理题占了60%以上这传递出的信号很明确比起“你用过什么框架”公司更关心“你是否理解底层机制”。对于初级岗位来说框架可以在入职后快速学但语言功底、逻辑思维、排查问题的思路不是短期能速成的。所以面试官选择用笔试先把“原理不清楚”的人筛掉。7.2 业务场景题考察的是工程意识另外一部分题目像“如何设计弹幕数据模型”“直播聊天页卡顿怎么排查”“如何保证缓存与网络数据一致性”考的是候选人能不能把技术方案落到真实业务里而不是只会背答案。这类题没有标准解但能看出你是否考虑过数据流、异常分支、性能瓶颈这些工程层面的问题。7.3 这份卷子没有考到但同样重要的能力有两块是笔试考不到的但在面试和实际工作中极为关键代码风格变量命名、方法拆分、注释是否有意义。面试官通过简历和提问很难直接判断所以如果有笔试写代码环节一定要写清楚。沟通协作能力客户端开发需要跟产品、设计、测试、后端多方协作表达逻辑清晰、能有效对齐需求的人在实际工作中效率会高很多。8. 站在今天回看校招笔试到底应该怎么准备最后这一章我想结合自己带实习生、参与面试的经验说点实在的准备思路。8.1 经典考点复习清单如果你准备iOS校招以下清单可以作为自测基线模块必须掌握的内容自测方式OC语言属性修饰符、分类、Block能回答清楚每个修饰符的底层差异Runtimeisa、消息转发、关联对象手写消息转发三件套并发GCD同步异步、死锁场景能画出串行/并发/主队列四象限图网络HTTP/HTTPS流程、网络库封装思路能讲清楚一次请求从发起到底层的完整链路数据结构链表反转、二叉树遍历、DP入门在白纸上半小时内写出可运行代码前端渲染UITableView/UICollectionView复用机制能说清楚cell复用的底层原理和卡顿优化手段8.2 源码阅读的优先级想在笔试面试中拉开差距只看《Objective-C编程》是不够的。建议把时间按这个优先级投入读苹果开源的objc4源码搞懂对象、类、方法调用的实现。读YYKit或AFNetworking经典开源项目中你日常用到的模块源码看别人怎么设计接口、怎么容错、怎么做线程安全。读Apple官方文档和WWDC视频搞清楚各系统框架的设计意图和演进方向。对于Swift方向的同学核心要读的还有Swift源码中关于值类型、引用类型、内存管理的部分以及Combine和并发框架的设计思路。底层逻辑是相通的。8.3 一套低成本自测方法面试前两周我建议这样自测不看资料用文档或白纸默写“对象方法调用完整流程”包括缓存查找、继承链查找、消息转发三个阶段。口述“UITableView的Cell复用流程”以及“主线程卡顿的常见原因”。随机抽3道LeetCode简单题和1道中等题限定40分钟做完注意代码规范和边界条件。把简历里的项目按照“业务背景—技术方案—关键难点—最终成效”的结构写一遍讲给别人听。8.4 最后再分享一个小技巧比起死记硬背我建议在准备时给自己建立“为什么”的习惯。你每学一个知识点都追问一句为什么设计成这样如果不这样设计会出现什么问题举个例子GCD为什么要分串行和并发队列因为不区分的话线程资源就会失控每个任务都开线程会导致上下文切换开销巨大。理解了这一点面试官怎么换角度问你你都能抓住本质回答而不是背答案。2017年的这份欢聚时代笔试题教会我的不是某个具体API的用法而是一种考察思路一线互联网公司在挑选iOS工程师时从来不只看你会不会用几个框架而是看你有没有把底层原理吃透、有没有工程意识、有没有逻辑自洽能力。这些能力放到今天不管是Objective-C还是Swift不管是UIKit还是SwiftUI依然是最核心的竞争力。希望这篇拆解能帮你把iOS知识体系重新梳理一遍少走弯路。