小米iOS开发面试4轮全记录:Swift与iOS原理深度考察

技术面试作者: 美历团队

3年iOS开发小米面试全流程复盘,包含技术一面二面三面、HR面真题,涵盖Swift高级特性、Runtime、RunLoop、内存管理、UI优化等考点,附面试真题汇总和备考建议

背景介绍

先说下我的情况:3年iOS开发经验,目前在中型App公司做社交类产品,日活大概200万左右。日常工作主要用Swift写业务,也维护一些OC的老模块。说实话,在现在的公司做了两年多,技术上到了一个瓶颈期——业务迭代很快,但底层的东西接触得越来越少,感觉自己越来越像"API调用工程师"。

今年3月份开始看机会,目标很明确:想去大厂的系统级团队,能接触到更底层的iOS开发。小米的MIUI/iOS团队一直在我的目标列表上,一方面是因为小米生态的广度,另一方面是听说他们团队对iOS原理的考察很深入,正好可以逼自己一把。

投递过程比较顺利,在Boss直聘上和HR聊了之后,很快就安排了面试。整个流程从一面到拿到offer,大概用了两周半的时间。下面我把4轮面试的完整过程复盘出来,希望对正在准备小米iOS面试或者iOS开发面试的同学有所帮助。

第1轮 技术一面(视频面,约60分钟)

一面安排在周一下午2点,面试官是一个看起来很年轻的工程师,自我介绍说是小米iOS基础架构组的。整体节奏比较紧凑,问题覆盖面广,从Swift基础到iOS原理都有涉及。

1. Swift中struct和class的区别?为什么Swift推荐优先使用struct?

这个我答得比较流畅:struct是值类型,class是引用类型;struct不支持继承,class支持;struct有memberwise initializer。推荐优先使用struct的原因主要是值语义更安全、不存在共享状态导致的副作用、在栈上分配性能更好、天然线程安全。面试官追问了copy-on-write机制,我也解释了Swift标准库中Array、Dictionary等类型如何通过COW优化性能。

2. Swift泛型中where关键字的作用?能举一个实际使用的例子吗?

我说where用于对泛型类型添加约束,比如限制泛型参数必须遵循某个协议或继承某个类。举了一个实际例子:写一个函数要求元素同时遵循Hashable和Comparable协议。面试官点了点头,没有深入追问。

3. Swift的protocol和Objective-C的protocol有什么区别?

我提到了Swift protocol支持默认实现(通过extension)、支持作为泛型约束、支持associatedtype关联类型。OC的protocol不支持默认实现,也不支持关联类型。面试官追问了protocol中能不能声明存储属性,我回答不能,只能声明计算属性的要求,存储属性需要通过关联对象或者具体类型来实现。

4. Swift的async/await和传统的GCD/Completion Handler有什么区别?

我从代码可读性、错误处理、任务取消三个维度来对比。async/await让异步代码看起来像同步代码,避免了回调地狱;配合try-catch处理错误更自然;可以通过Task和TaskGroup实现结构化并发。面试官追问了actor是什么,我解释了actor是Swift并发模型中的隔离机制,保证内部状态的线程安全访问,通过actor隔离避免数据竞争。说实话actor这块我讲得不够深入,只说了基本概念,面试官没有继续追问,但我感觉这块应该再准备得更充分一些。

5. iOS中weak和unowned的区别?分别在什么场景下使用?

weak是可选类型,对象释放后自动置为nil;unowned是非可选类型,假设对象始终存在,如果对象释放后访问会崩溃。使用场景:weak用在闭包中捕获self时最常见;unowned用在确认对象生命周期比闭包长的场景,比如lazy属性中。面试官追问了闭包中[weak self]和[unowned self]的选择,我回答一般优先用weak更安全。

6. SwiftUI和UIKit的区别?你怎么看SwiftUI的未来?

我说SwiftUI是声明式UI框架,UIKit是命令式;SwiftUI通过状态驱动视图更新,UIKit需要手动管理视图层级。SwiftUI的优势是代码简洁、实时预览、跨平台;劣势是生态不成熟、复杂自定义困难、性能调试手段少。关于未来,我认为SwiftUI会逐步替代UIKit,但短期内UIKit仍然是主力,尤其是在复杂业务场景下。面试官似乎对这个回答比较满意。

7. iOS事件响应链(Responder Chain)的工作原理?

我解释了hit-testing确定第一响应者,然后事件沿响应链从子视图向父视图传递,直到被处理。如果最终没有被处理,事件会传递到UIWindow和UIApplication。面试官追问了如何扩大点击区域,我说可以重写point(inside:with:)方法或者使用pointInside的扩展。

8. 你了解iOS中的Method Swizzling吗?有什么风险?

我解释了Method Swizzling是通过Runtime交换两个方法的实现,常用于AOP编程,比如埋点统计。风险包括:交换顺序依赖导致的问题、多次交换导致死循环、线程安全问题、影响系统行为的不可预测性。建议使用时确保在+load中执行、使用dispatch_once保证只交换一次、做好日志记录。面试官对这个回答点了点头。

9. 简述iOS的内存管理机制ARC的工作原理?

ARC通过编译器在编译期自动插入retain/release/autorelease调用,基于引用计数管理内存。强引用增加引用计数,弱引用不影响。当引用计数降为0时,对象被释放。我提到了autoreleasepool的作用——在自动释放池结束时统一发送release消息,避免内存峰值。面试官追问了autoreleasepool在RunLoop中的关系,我简单说了每次RunLoop迭代会创建一个autoreleasepool,迭代结束时drain。这块和二面的RunLoop问题有呼应。

第2轮 技术二面(视频面,约65分钟)

二面安排在周四上午10点,面试官是团队的技术负责人,风格明显比一面更深入,追问很多,会一直挖到你说不出来为止。这轮面试我压力比较大,有两个问题答得不太好。

1. 详细讲讲Objective-C Runtime的消息派发机制?

我从objc_msgSend开始讲:方法调用在编译期会被转化为objc_msgSend(receiver, selector, ...),然后依次经历:在缓存中查找方法→在类的方法列表中查找→沿着继承链向上查找。如果找不到,进入动态方法解析流程:resolveInstanceMethod → forwardingTargetForSelector → forwardInvocation。面试官追问了methodSignatureForSelector和forwardInvocation的关系,我说methodSignatureForSelector返回方法签名,forwardInvocation根据签名执行转发。然后面试官又问了"如果所有转发都失败了会怎样",我回答会抛出doesNotRecognizeSelector异常导致崩溃。这块我整体答得还行,但动态方法解析的细节讲得不够清楚,尤其是resolveInstanceMethod返回YES后为什么还要再查一遍缓存,我有点含糊。

2. RunLoop的底层机制是什么?它在iOS中有哪些实际应用?

我说RunLoop本质上是一个事件循环,通过mach_port接收事件,保持线程不被销毁。内部维护了多个Mode,每个Mode包含Source0、Source1、Timer和Observer。实际应用包括:NSTimer的实现、AutoreleasePool的管理、事件响应、GCD回调的执行。面试官追问了RunLoop和线程的关系,我说每个线程都有对应的RunLoop,但只有主线程的RunLoop默认启动,子线程需要手动获取并运行。然后面试官问了一个让我卡住的问题:"RunLoop在休眠时,线程处于什么状态?是怎么被唤醒的?" 我知道线程会被挂起,但具体说出了"通过mach_msg让线程休眠,当有端口消息时唤醒"这个细节,但关于内核态和用户态的切换细节我确实不太清楚,只能坦诚说"这块我了解得不够深入"。面试官说没关系,继续下一个问题。

3. iOS中如何检测和解决循环引用(Retain Cycle)?

我列举了常见场景:闭包捕获self、delegate声明为strong、Timer未invalidate。检测方法:Instruments的Leaks和Allocations工具、Xcode Memory Graph Debugger、FBRetainCycleDetector。解决方法:闭包中使用[weak self]、delegate声明为weak、Timer在deinit中invalidate。面试官追问了一个我没想到的问题:"NSTimer的循环引用具体是怎么形成的?为什么用weak修饰target不能解决?" 我想了想说,因为NSTimer的target是被RunLoop强持有的,即使target是weak,RunLoop仍然持有timer,timer持有target,形成RunLoop→Timer→Target的循环。weak只能让引用不增加计数,但RunLoop的持有是strong的。正确的做法是用GCD Timer或者基于Block的Timer API。面试官说"基本正确",但我感觉解释得不够清晰。

4. GCD和OperationQueue的区别和使用场景?

GCD是C语言API,基于队列和闭包,轻量高效;OperationQueue是面向对象的,支持取消、依赖、优先级、最大并发数控制。使用场景:简单异步任务用GCD,复杂任务编排用OperationQueue。面试官追问了GCD的队列类型和QoS等级,我列举了main queue、global queue、自定义串行队列、自定义并发队列,以及userInteractive、userInitiated、default、utility、background五个QoS等级。

5. 讲讲iOS中的Category和Extension的区别?Category能添加存储属性吗?

Category在运行时生效,可以为已有类添加方法,不能直接添加存储属性;Extension在编译时生效,可以添加属性和方法,但必须在主类的实现文件中。Category添加存储属性需要通过关联对象(objc_setAssociatedObject/objc_getAssociatedObject)。面试官追问了关联对象的底层实现,我说关联对象存储在一个全局的AssociationsManager哈希表中,以对象地址和key为索引。对象释放时通过objc_destructInstance清理关联对象。

6. iOS中KVO的底层实现原理?

我说KVO通过Runtime动态创建一个被观察对象的子类(NSKVONotifying_XXX),并重写setter方法,在setter中调用willChangeValueForKey和didChangeValueForKey通知观察者。面试官追问了如何证明这个机制,我说可以通过object_getClass打印对象的实际类来验证。还提到了KVO的一个坑:如果在setter外直接修改实例变量,KVO不会触发,需要手动调用willChange/didChange。

第3轮 技术三面(视频面,约55分钟)

三面安排在下周二下午3点,面试官是一位资深架构师,问的问题更偏向架构设计和场景分析。这轮面试的节奏比较舒服,面试官更像是在和你讨论问题,而不是单纯考你。

1. 你在项目中遇到过哪些性能问题?怎么优化的?

我举了两个实际案例。第一个是TableView滑动卡顿:原因是cellForRow中做了图片解码和圆角裁剪,优化方案是把图片解码放到后台线程、用预渲染圆角替代实时裁剪、预计算行高。第二个是启动时间过长:通过添加环境变量DYLD_PRINT_STATISTICS定位到pre-main阶段耗时,发现是动态库太多,合并了几个内部库,启动时间从2.3秒降到1.5秒。面试官追问了还有哪些启动优化手段,我补充了二进制重排、懒加载非首屏模块、减少+load中的逻辑。

2. 设计一个图片缓存框架,你会怎么设计?

我从三层缓存结构来设计:内存缓存(NSCache,自动淘汰策略)→磁盘缓存(文件系统,按时间和大小清理)→网络请求(URLSession,支持并发和优先级)。关键设计点:内存警告时清理内存缓存、磁盘缓存用LRU策略、图片解码在后台线程、支持渐进式JPEG加载。面试官追问了NSCache和NSDictionary的区别,我说NSCache是线程安全的、自动释放内存、不拷贝key。面试官又问了磁盘缓存如何高效查找,我说用文件路径的MD5作为文件名,通过文件系统直接读取。

3. 如果App出现OOM,你会怎么排查?

我说首先区分是内存泄漏还是内存峰值。内存泄漏用Instruments Leaks和Memory Graph排查;内存峰值需要分析大对象的生命周期。常见原因:大图片未压缩直接加载、列表缓存未限制大小、循环引用。排查步骤:先用Memory Graph看当前内存中的对象分布,再用Allocations追踪内存增长趋势,最后用Leaks检测泄漏。面试官追问了Jetsam机制,我说iOS通过Jetsam优先杀掉内存占用高且优先级低的进程,可以在日志中查看Jetsam event。

4. 你对组件化有什么理解?你们项目是怎么做的?

我说组件化的核心是解耦,让各模块独立开发、独立测试。我们项目用的是基于协议注册的方案(类似BeeHive),通过Protocol-Class注册实现服务发现,模块间通过协议通信而不是直接依赖。面试官问了组件化过程中遇到的困难,我说最大的挑战是历史代码的解耦——很多跨模块调用散落在各处,需要逐步抽离。另一个问题是组件粒度的把握,太细维护成本高,太粗解耦不彻底。

5. 场景题:设计一个可配置的首页信息流,支持A/B测试,怎么设计架构?

我设计了分层架构:配置层(从服务端拉取AB实验配置,决定展示哪些卡片和顺序)→数据层(根据配置请求对应的数据源)→展示层(根据卡片类型动态创建对应的Cell,用工厂模式)。关键点:配置下发格式用JSON,客户端解析后生成卡片模型数组;Cell注册用ReuseIdentifier映射;数据源通过协议抽象,不同卡片实现自己的数据协议。面试官追问了如果新增卡片类型需要发版怎么办,我说可以预留通用卡片类型,通过服务端配置动态渲染,类似WebView或模板渲染的思路。

第4轮 HR面(约35分钟)

HR面安排在三面后的第三天,面试官是一位很亲切的HR姐姐。整体氛围比较轻松,但有些问题还是需要认真思考后回答。

1. 自我介绍

我简单介绍了3年iOS开发经验、目前在做的产品和技术栈、为什么想加入小米。

2. 为什么选择小米?

我说了三点:小米的生态链很广,从手机到IoT到汽车,iOS开发在这里不只是做App,还有系统级的工作;小米的技术氛围好,开源文化浓厚;MIUI团队对技术的追求和我的职业规划一致。

3. 你在当前公司最大的成就是什么?

我说了主导App从OC迁移到Swift的项目,历时半年,迁移了80%的代码,同时保证了线上零故障。这个过程中我积累了混合语言项目的经验,也深入理解了OC和Swift的互操作机制。

4. 你期望的薪资是多少?

我说了目前的薪资和期望涨幅,HR没有当场回复,说需要综合评估后给方案。

5. 你有什么想问我的?

我问了团队的技术栈和未来的方向,HR说团队目前在做跨平台方案的研究,也在参与小米汽车的车机iOS适配。这个回答让我更加期待了。

面试真题汇总

以下是4轮面试的所有题目,按轮次整理:

技术一面(9题):

1. struct和class的区别,为什么优先用struct → 考察Swift基础 → ⭐⭐
2. 泛型where关键字的作用和实际例子 → 考察Swift泛型 → ⭐⭐
3. Swift protocol和OC protocol的区别 → 考察语言对比 → ⭐⭐⭐
4. async/await和GCD/Completion Handler的区别 → 考察Swift并发 → ⭐⭐⭐
5. weak和unowned的区别和使用场景 → 考察内存管理 → ⭐⭐
6. SwiftUI和UIKit的区别 → 考察技术视野 → ⭐⭐
7. 事件响应链的工作原理 → 考察UI原理 → ⭐⭐⭐
8. Method Swizzling的原理和风险 → 考察Runtime → ⭐⭐⭐
9. ARC的工作原理 → 考察内存管理 → ⭐⭐

技术二面(6题):

1. Runtime消息派发机制 → 考察Runtime底层 → ⭐⭐⭐⭐
2. RunLoop的底层机制和实际应用 → 考察RunLoop → ⭐⭐⭐⭐
3. 循环引用的检测和解决 → 考察内存管理 → ⭐⭐⭐
4. GCD和OperationQueue的区别 → 考察多线程 → ⭐⭐⭐
5. Category和Extension的区别 → 考察OC特性 → ⭐⭐⭐
6. KVO的底层实现原理 → 考察Runtime → ⭐⭐⭐⭐

技术三面(5题):

1. 项目中的性能优化经验 → 考察实战能力 → ⭐⭐⭐
2. 设计图片缓存框架 → 考察架构设计 → ⭐⭐⭐⭐
3. OOM排查思路 → 考察性能调优 → ⭐⭐⭐⭐
4. 组件化的理解和实践 → 考察架构能力 → ⭐⭐⭐
5. 可配置首页信息流架构设计 → 考察系统设计 → ⭐⭐⭐⭐⭐

HR面(5题):

1. 自我介绍 → 考察表达能力 → ⭐
2. 为什么选择小米 → 考察求职动机 → ⭐⭐
3. 最大成就 → 考察自我认知 → ⭐⭐
4. 薪资期望 → 考察市场认知 → ⭐⭐
5. 反问环节 → 考察主动性 → ⭐

心得体会与建议

最终,我在面试后第5天收到了小米的offer,薪资涨幅约30%,整体比较满意。回顾整个面试过程,有几点建议分享给大家:

1. iOS原理一定要深入,不能只停留在表面。小米对Runtime、RunLoop、内存管理的考察非常深入,不是背几个概念就能应付的。比如RunLoop的休眠和唤醒机制、Runtime消息转发的完整流程,这些都需要你真正理解底层实现,而不是只记住结论。建议看《Objective-C高级编程》和Apple的官方文档,配合源码阅读。

2. Swift高级特性是加分项,async/await和actor一定要准备。一面中async/await和actor的考察让我意识到,Swift并发模型已经是面试的高频考点了。如果你还在只用GCD,建议尽快学习Swift Concurrency,这不仅是面试需要,也是未来iOS开发的方向。

3. 架构设计和场景题需要平时积累,临时抱佛脚效果有限。三面的图片缓存框架设计和首页信息流架构设计,都需要你有实际的项目经验和设计思维。建议在日常工作中多思考"为什么这样设计",积累设计模式的应用场景,而不是只关注"怎么实现"。

4. 面试中不会的问题要坦诚,不要硬编。二面中RunLoop的内核态切换我确实不了解,坦诚说了"这块不够深入",面试官并没有因此扣分。相反,如果你编造答案被追问,反而会严重影响评价。不会就说不会,然后表达学习的意愿,这是更好的策略。

FAQ

Q1:小米iOS面试对OC基础要求高吗?
A:很高。虽然日常开发用Swift,但Runtime、RunLoop、KVO等都是OC层面的底层机制,面试必考。建议即使日常用Swift,也要系统学习OC的Runtime和内存管理。

Q2:小米iOS面试会考算法吗?
A:我这4轮面试没有单独的算法题,但三面的场景设计题需要你有数据结构和算法的基础。建议至少刷完LeetCode Hot 100,重点看链表、树、动态规划。

Q3:SwiftUI在面试中的比重有多大?
A:一面问了一道SwiftUI和UIKit的对比题,不算深。但如果你简历上写了SwiftUI项目,面试官肯定会深入问。建议没有实际项目经验的话,简历上不要过度强调SwiftUI。

Q4:小米面试的难度和其他大厂比怎么样?
A:个人感觉小米iOS面试的难度中等偏上,比字节跳动的iOS面试略简单,和百度差不多。特点是原理考察很深,但不会故意刁难,面试官的态度都很好。

Q5:面试前需要准备什么项目介绍?
A:建议准备1-2个能深入讲的项目,重点突出你解决的技术难题和优化效果。用STAR法则组织:Situation(背景)→Task(任务)→Action(行动)→Result(结果),尤其是Result要能量化,比如"启动时间从2.3秒降到1.5秒"比"优化了启动速度"更有说服力。

相关模板

#小米#iOS面试#Swift#Runtime#面试真题