复习路径
先建立主干,再补细节。iOS 面试不是把每个 API 背出来,而是能把问题放到“语言、系统、UI、并发、网络、性能、架构、线上治理”这些层级里分析。
第一层:会说清原理
Runtime、RunLoop、ARC、Block、GCD、UIKit 渲染、HTTP/TLS 是最容易被追问的底层主干。
第二层:能结合项目
每个知识点都准备一个项目场景:比如卡顿怎么定位、启动怎么拆、OOM 如何加上下文。
第三层:能讲取舍
高级面试更关心为什么这么做、风险是什么、怎么灰度、如何验证收益。
第四层:有线上闭环
发现、定位、止血、修复、验证、复盘,能讲闭环比单点知识更有说服力。
面经高频主题
| 知识域 | 高频问题 | 考察重点 |
|---|---|---|
| 语言底层 | 属性修饰符、Block、Runtime、KVO、分类、+load、+initialize |
是否理解对象模型、内存语义和动态派发。 |
| 系统机制 | RunLoop、事件响应、主线程渲染、GCD、锁、进程线程 | 是否能解释 UI 卡顿、线程安全和死锁。 |
| 工程项目 | 启动优化、列表优化、网络缓存、组件化、CI/CD、灰度 | 是否能把技术方案放进真实业务环境。 |
| 线上治理 | Crash、OOM、Watchdog、卡顿、MetricKit、日志、trace、告警 | 是否具备线上问题闭环能力。 |
Objective-C 与内存
这一章解决“对象怎么被持有、什么时候释放、Block 捕获了什么、属性语义该怎么选”。
对象属性默认用 strong,反向引用和 delegate 用 weak,字符串、集合和 Block 属性优先 copy,基础类型用 assign。Block 里避免循环引用用 __weak,需要修改局部变量才用 __block。
属性修饰符
| 修饰符 | 语义 | 使用场景 | 常见误区 |
|---|---|---|---|
strong / retain |
强引用,持有对象。 | 模型、服务、普通对象属性。 | ARC 下常用 strong,retain 多见于历史代码。 |
weak |
弱引用,不增加引用计数,释放后自动置空。 | delegate、dataSource、父子对象的反向引用。 | 不要用 assign 修饰对象来模拟弱引用,会产生野指针。 |
copy |
保存副本。 | NSString、集合、Block。 |
集合 copy 是浅拷贝,元素不会递归复制。 |
atomic |
getter/setter 单次访问具备原子性。 | iOS 项目中通常不用,默认写 nonatomic。 |
atomic 不等于业务线程安全。 |
Block 的知识框架
存储位置
全局 Block 不捕获自动变量;栈 Block 不能逃逸;copy 后变成堆 Block,能安全保存。
捕获规则
局部自动变量默认按值捕获;对象变量通常被强捕获;self.property 捕获的是 self。
__block
__block 让变量被包装成可转移的结构体,通过 forwarding 指向有效存储位置。
常见追问:为什么 NSString 用 copy?
不是因为所有字符串都会变,而是为了隔离外部传入的 NSMutableString。属性声明为 copy 后,内部状态不会被外部可变对象后续修改影响,这体现的是 API 语义稳定。
Runtime 与类加载
Runtime 的核心问题是“对象如何找到方法”。围绕对象、类、元类、cache、方法列表、消息转发、类加载时机展开。
实例方法存在类对象中,类方法存在元类中。消息发送先通过 isa 找到类,再查 cache、方法列表和父类;找不到时进入动态方法解析、快速转发、完整转发,最终才是 unrecognized selector 崩溃。
消息发送流程
- 接收者通过
isa找到类对象。 - Runtime 先查方法缓存,命中后直接调用 IMP。
- 缓存未命中时查当前类方法列表,再沿父类向上找。
- 仍找不到,调用
resolveInstanceMethod:或resolveClassMethod:动态添加方法。 - 快速转发
forwardingTargetForSelector:可以换接收者。 - 完整转发通过
methodSignatureForSelector:和forwardInvocation:处理。
类加载与首次使用
| 方法 | 触发时机 | 面试重点 |
|---|---|---|
+load |
类或分类被 Runtime 加载进进程时调用,通常早于 main。 |
不依赖是否使用这个类;分类的 +load 都会执行;启动期不要做重活。 |
| 静态初始化 | C/C++ 静态对象、全局变量构造等发生在进入业务代码前。 | 也会影响 pre-main,复杂初始化应尽量延迟。 |
+initialize |
类第一次收到消息前由 Runtime 懒触发。 | 父类通常先于子类;可能被多线程触发;现代代码更推荐显式初始化或 dispatch_once。 |
关联对象、分类、Swizzling
- 关联对象:给已有对象挂额外 key-value,常用于分类“加属性”。
- 分类:方法会合并到类方法列表,同名方法存在覆盖风险,不能依赖加载顺序做业务逻辑。
- Swizzling:交换 selector 和 IMP,适合埋点、修复、AOP,但必须控制时机、幂等和作用范围。
RunLoop 与 UIKit
RunLoop 是线程事件循环,UIKit 则把事件响应、布局、渲染和动画放在主线程周期里完成。
RunLoop 和线程一一对应。主线程 RunLoop 默认开启,子线程要保活需要添加 Source/Timer 后运行。Mode 决定当前处理哪些事件;卡顿监控常通过观察主线程 RunLoop 状态和独立线程计时来判断。
RunLoop 模型
Source
输入源,处理端口、事件、手动唤醒等。
Timer
定时器,受 Mode 影响。滚动时 Default Mode 的 Timer 可能暂停。
Observer
观察 RunLoop 状态变化,卡顿监控常用它记录主线程是否长时间停留。
UIKit 高频点
| 主题 | 要点 | 项目表达 |
|---|---|---|
| 事件传递 | Window 从上到下 hit-test 找目标 view,响应链从目标 view 向上传递。 | 区分“谁被命中”和“谁处理事件”。 |
| UIView / CALayer | UIView 管事件和层级,CALayer 管显示和动画。 | 渲染问题常从 layer 属性、离屏渲染、图片解码分析。 |
| AutoLayout | 复杂约束和频繁更新会增加求解成本。 | 列表中可缓存高度,减少嵌套和动态约束。 |
| 列表性能 | 复用、预取、异步解码、缓存尺寸、滚动时降级非关键任务。 | 能结合 Feed/评论/直播列表讲优化路径。 |
并发与线程安全
并发题要分清线程、队列、任务、锁、共享状态。队列不是线程,同步/异步也不是串行/并行。
线程是系统调度单位,GCD 队列是任务调度抽象。串行队列一次执行一个任务,并发队列可同时执行多个任务;同步会等待任务完成,异步不会等待。死锁常见于当前串行队列同步派发到自己。
GCD 组合
| 组合 | 行为 | 面试提醒 |
|---|---|---|
| 串行 + 同步 | 当前线程等待任务执行完。 | 在当前串行队列里 sync 到自己会死锁。 |
| 串行 + 异步 | 任务按顺序执行,调用方不等待。 | 可能新开线程,也可能复用线程。 |
| 并发 + 同步 | 调用方等待,任务可由当前线程或其他线程执行。 | 同步限制的是调用方,不是队列的并发能力。 |
| 并发 + 异步 | 多个任务可并发执行。 | 并发数由系统调度,不是无限开线程。 |
线程安全怎么答
- 先指出共享可变状态在哪里。
- 再说明用什么同步手段保护:锁、串行队列、读写锁、信号量、actor。
- 最后补充风险:死锁、优先级反转、锁粒度过大、主线程阻塞。
Swift 与 SwiftUI
Swift 面试重点在值语义、协议泛型、ARC、闭包捕获、并发模型和与 Objective-C 混编边界。
Swift 用强类型、值语义、协议和泛型提升安全性;类仍由 ARC 管理,闭包仍可能循环引用。现代并发用 async/await 表达异步流程,用 actor 隔离可变状态,用 @MainActor 约束 UI 更新。
Swift 高频点
值类型和引用类型
struct / enum 默认值语义,适合不可共享状态;class 适合身份、继承和共享生命周期。
协议和泛型
协议描述能力,泛型保留静态类型。some Protocol 是不透明类型,any Protocol 是 existential 容器。
闭包捕获
[weak self] 用于打断循环引用,但不要所有异步闭包都无脑 weak,要结合业务生命周期。
SwiftUI 状态
@State、@Binding、@ObservedObject、@StateObject 表达不同状态来源和所有权。
网络与数据
网络题要能讲清端到端链路,数据题要按数据类型选存储,并说明一致性、性能、安全和迁移。
一次 HTTPS 请求通常经过 DNS、连接建立、TLS、请求响应、缓存处理。现代 TLS 用证书验证身份,用 ECDHE 协商密钥,后续数据用对称加密。持久化上,小配置用 UserDefaults,敏感凭证用 Keychain,结构化数据用 SQLite/Core Data,大文件走文件系统缓存。
网络链路
- DNS 解析域名,可能受到本地、运营商、公共 DNS 缓存影响。
- TCP 或 QUIC 建立传输通道。
- TLS 完成证书校验和密钥协商。
- 发送请求,服务端处理并返回响应。
- 客户端根据缓存策略、状态码和业务错误做处理。
缓存与持久化
| 主题 | 要点 | 风险 |
|---|---|---|
| HTTP 缓存 | Cache-Control 是强缓存,ETag / Last-Modified 是协商缓存。 |
缓存策略错误会导致数据陈旧或请求量过大。 |
| 断点续传 | 依赖 Range 和服务端支持。 |
文件变化、校验不一致、resumeData 失效都要处理。 |
| Keychain | 适合 token、账号凭证、敏感标识。 | 注意访问组、卸载残留、迁移和权限。 |
| SQLite/Core Data | 适合结构化数据、本地索引、对象图。 | 重点是事务、索引、分页、迁移和并发访问。 |
启动、性能与稳定性
性能优化不是“感觉快”,而是分阶段度量、定位瓶颈、做最小改动并验证收益。稳定性则要按崩溃类型和资源异常分类治理。
启动分为 pre-main 和 main 后。pre-main 受 Mach-O 加载、动态库、符号绑定、+load、静态初始化影响;main 后关注首屏路径。优化方向是减少启动期工作、懒加载、任务拆分、二进制重排,并用 Instruments、MetricKit 和线上指标验证。
性能问题分类
| 问题 | 常见原因 | 定位手段 |
|---|---|---|
| 启动慢 | 动态库多、+load 重、首屏同步 IO、首屏网络阻塞。 |
启动耗时拆解、dyld 统计、Instruments、线上启动指标。 |
| 卡顿 | 主线程计算、图片解码、布局求解、锁等待、同步 IO。 | 主线程采样、RunLoop 监控、火焰图、卡顿堆栈。 |
| OOM | 图片/视频内存、缓存失控、泄漏、峰值过高。 | 内存水位、Memory Graph、Jetsam/OOM 上下文。 |
| Crash | 越界、野指针、KVC/KVO、线程、生命周期、数据异常。 | 符号化、聚类、日志、用户路径、版本/机型/系统分布。 |
稳定性治理动作
- 输入数据做边界校验,处理
nil、NSNull、数组越界和类型不匹配。 - KVO 使用唯一 context 或 token 封装,避免重复添加/移除。
- Crash/OOM/Watchdog 上报时补充页面、场景、版本、设备、内存、CPU、磁盘和最近操作。
- 对高风险功能准备灰度、降级和配置止血能力。
架构与工程化
架构面试的重点不是背 MVC/MVVM 名词,而是解释复杂业务如何拆分、依赖如何收敛、变更如何可测可回滚。
好的 iOS 架构要降低耦合、提高可测性和交付效率。页面层处理 UI,业务层沉淀用例,数据层封装网络和缓存,基础层提供日志、路由、配置、监控等能力。组件化时要控制依赖方向,避免模块互相 import。
架构表达模板
- 先讲业务背景:为什么现有结构不够用了。
- 再讲边界:按业务、能力、页面还是基础设施拆。
- 说明依赖方向:谁能依赖谁,跨模块怎么通信。
- 说明工程配套:CI、测试、路由、资源隔离、版本管理。
- 最后讲收益和风险:编译时间、协作效率、问题定位、灰度回滚。
常见模式
MVC
系统协作成本低,适合简单页面;风险是 Controller 膨胀。
MVVM
适合展示逻辑多、状态转换复杂的页面;要避免 ViewModel 变成另一个“大控制器”。
Clean / VIPER
适合多人协作和复杂业务规则;风险是文件和协议数量较多。
组件化
适合大型 App 和多团队并行;关键是模块边界、依赖治理和路由协议。
线上监控与排障
线上监控不是接一个 SDK,而是形成从指标到定位、止血、修复、验证的闭环。你本地的 `ios-client` 项目就体现了这种完整监控栈。
线上监控要覆盖 Crash/OOM/Watchdog、启动和卡顿、CPU/内存/磁盘/电量、网络错误、页面黑白屏、业务漏斗和日志 trace。指标异常后要能定位版本、设备、系统、页面、实验、用户路径和最近操作。
监控指标
| 层级 | 核心指标 | 排障动作 |
|---|---|---|
| 稳定性 | Crash 率、OOM/FOOM、Watchdog、CPU Block、异常类型。 | 符号化、聚类、回溯变更、灰度止血、补上下文。 |
| 性能 | 冷启动、热启动、TTI、首帧、FPS、卡顿率、长卡顿。 | Instruments、MetricKit、火焰图、启动任务拆解。 |
| 资源 | footprint、resident、CPU、线程数、wakeups、磁盘、电量。 | 按页面和场景采集水位,结合内存图、缓存、图片解码分析。 |
| 网络 | 成功率、状态码、错误码、耗时、DNS/TLS/TCP、重试、流量。 | 弱网复现、traceId 串联服务端、降级和重试策略检查。 |
| 体验和业务 | 黑白屏、内容错位、点击无响应、曝光、点击、转化、支付成功率。 | 截图/视图树/日志打包,按页面、版本、实验、人群归因。 |
线上问题闭环
- 发现:告警、看板、Crash 聚类、用户反馈。
- 定位:版本、系统、机型、页面、实验、网络环境、用户路径。
- 止血:配置降级、关闭实验、回滚灰度、熔断高风险功能。
- 修复:补代码、补校验、补监控、补测试。
- 验证:指标回落、灰度观察、复盘沉淀。
安全、发布与基础能力
这部分常作为综合能力追问:证书签名、隐私权限、发布灰度、算法、操作系统、网络和数据库基础。
iOS 安全从代码签名和沙盒开始,网络侧依赖 ATS/TLS,敏感数据放 Keychain,权限访问要最小化并说明用途。发布上要管理证书、描述文件、符号表、灰度、崩溃监控和审核合规。
发布检查
- 构建:版本号、环境、证书、Profile、编译参数、符号表上传。
- 提审:权限描述、隐私清单、支付规则、后台能力、第三方 SDK 合规。
- 灰度:按版本、人群、地区、设备、实验逐步放量。
- 发布后:重点看 Crash、启动、核心转化、网络错误和用户反馈。
算法和基础
算法
数组、字符串、链表、栈队列、哈希表、树、二分、滑动窗口、动态规划入门。
操作系统
进程线程、锁、内存、调度、上下文切换、虚拟内存。
网络基础
TCP、HTTP、TLS、DNS、缓存、弱网、重试和幂等。
数据库
索引、事务、分页、迁移、并发读写、本地缓存一致性。