从零散笔记到可复述的 iOS 知识模型
新版把原有底层知识、真实项目监控经验和网上高频面经重新归类:先看 30 秒答法,再看核心模型、易错点、项目落地和追问方向。目标不是背术语,而是能把原因、边界和取舍讲顺。
知识地图
修饰符与内存语义
属性修饰符解决“谁拥有对象、怎么暴露 API”,变量修饰符解决“闭包捕获、生命周期和置空行为”。面试里要把 property attribute 和 ownership qualifier 分开讲。
对象属性默认用 strong,delegate 和反向引用用 weak,NSString/集合/Block 属性优先 copy。基础类型用 assign。Block 里避免循环引用用 __weak,需要修改局部变量才用 __block。
| 修饰符 | 语义 | 适用场景 | 补充与坑点 |
|---|---|---|---|
strong / retain |
强引用,拥有对象。 | 模型、服务对象、普通对象属性。 | ARC 下常写 strong;retain 多见于 MRC 或历史代码。 |
weak |
弱引用,不增加引用计数,目标释放后自动置 nil。 |
delegate、dataSource、父子/回调反向引用。 | 只能用于对象类型;比 assign 安全,因为不会悬垂。 |
assign |
直接赋值,不做对象生命周期管理。 | BOOL、NSInteger、CGFloat 等标量。 |
不要修饰 Objective-C 对象,否则对象释放后会留下野指针。 |
copy |
保存副本,通常是不可变副本或堆上的 Block。 | NSString、NSArray、NSDictionary、Block。 |
集合 copy 是浅拷贝;元素本身不会递归复制。 |
atomic / nonatomic |
控制 getter/setter 是否具备原子访问语义。 | iOS 项目中通常写 nonatomic。 |
atomic 不等于业务线程安全,只保证单次 getter/setter 的同步。 |
readonly / readwrite |
控制是否对外生成 setter。 | 公开头文件只读,类扩展里改为可读写。 | readwrite 是默认值。 |
__block |
让局部变量以“可变盒子”的形式被 Block 捕获。 | Block 内需要修改外部局部变量。 | ARC 下 __block 对对象变量仍可能强持有,不能用它破循环引用。 |
__weak / __unsafe_unretained |
弱持有或不安全不持有。 | Block 捕获 self;兼容极旧代码。 | __unsafe_unretained 不会置空,释放后访问就是野指针。 |
@synthesize / @dynamic |
控制属性访问器的生成或承诺运行时提供。 | 自定义 ivar、Core Data、动态派发属性。 | @dynamic 只是让编译器别报警,运行时没有实现仍会崩。 |
修饰符选择演示
delegate
delegate 通常是反向引用,不应该被持有者强引用。
推荐:weak。如果写成 strong,很容易形成控制器和子对象之间的循环引用。
@property (nonatomic, weak) id<UITableViewDelegate> delegate;
推荐答法
“copy 不是因为 NSString 一定可变,而是为了隔离外部传入的 NSMutableString,保证属性语义稳定。”
容易误答
“atomic 是线程安全。”更准确的说法是:它只保护单次存取,复合操作仍需要锁、队列或其他同步方案。
Block
Block 的高频点是对象本质、三种存储位置、变量捕获方式、循环引用和 __block 底层结构。
Block 是带函数指针和捕获环境的对象。全局 Block 不捕获自动变量;栈 Block 在栈上;拷贝后是堆 Block。自动变量默认按值捕获,对象默认强捕获;需要修改局部变量用 __block,破循环引用用 __weak。
捕获规则
- 局部自动变量默认按值捕获,Block 内看到的是创建时的值。
static/ 全局变量不复制值,Block 直接访问存储位置。- 对象变量会被捕获为对象引用;在 ARC 下通常表现为强持有。
self.property会捕获self,不是只捕获那个属性。
__block 的 forwarding
__block 变量会被包装为结构体,结构体里有 forwarding 指针。Block 从栈复制到堆时,栈上的变量和堆上的变量都会通过 forwarding 指向唯一有效实例。
循环引用拆解
self -> block -> self
对象强持有 Block,Block 又强捕获 self,就形成环。
解决重点不是“让变量可修改”,而是打断所有权关系。
@property (nonatomic, copy) void (^completion)(void);
self.completion = ^{
[self reloadData];
};
__block 在 ARC 下不能当成破循环引用的工具。面试里如果说“用 __block 防止循环引用”,要补充这是 MRC 时代的历史语境,ARC 下应该使用 __weak。
Runtime
Runtime 的主线是“对象如何找到方法”:对象通过 isa 找类,类查 cache 和方法列表,找不到再解析和转发。
id 是对象指针,Class 是类对象指针。实例方法存在类对象中,类方法存在元类中。消息发送先查 cache,再查方法列表和父类;仍找不到就动态解析、快速转发、完整转发,最后才崩溃。
对象 / 类 / 元类
id = objc_object *,Class = objc_class *。对象的 isa 指向类;类的 isa 指向元类;元类保存类方法。
方法缓存
cache 是 Runtime 的快路径。方法第一次查找后会缓存 IMP,后续同一 selector 命中 cache 就直接调用。
分类
分类方法和主类同名时存在覆盖风险,加载顺序相关,不要依赖这种行为。分类的 +load 都会执行。
objc_msgSend 流程演示
1. isa 查找
接收者通过 isa 指针找到它的类对象。实例方法从类对象开始找;类方法从元类开始找。
这一步解释了为什么对象、类和元类是 Runtime 的入口。
转发链说法
完整顺序是动态方法解析、快速转发、方法签名、forwardInvocation:。快速转发只能换接收者,完整转发可以改参数、返回值,也能分发给多个对象。
respondsToSelector: 误区
它不会真正执行完整消息转发。若对象依赖转发链处理消息,需要同步重写 respondsToSelector:,否则外部判断可能不准确。
关联对象、Swizzling、字典转模型怎么串起来
关联对象通过 Runtime 的关联表给对象挂额外 key-value,常用于分类“加属性”。Swizzling 交换 selector 和 IMP,常用于埋点、修复、AOP,但要用 dispatch_once 控制时机和幂等。字典转模型则是读取属性列表、类型编码和 setter 信息,把动态结构映射到静态对象上。
RunLoop
RunLoop 是线程的事件循环:有事处理,没事休眠,被事件唤醒后继续。主线程默认开启,子线程要长期存活时需要手动配置 source/timer 并启动。
RunLoop 和线程一一对应。Mode 决定当前能处理哪些 source/timer;Common Modes 不是独立模式,而是一组模式标记。卡顿监控通常观察主线程 RunLoop 状态,线程保活则给子线程添加 source 并运行 RunLoop。
Mode
Default 处理普通事件,Tracking 用于滚动等追踪场景,Common 是模式集合标签,不是实际运行模式。
Source / Timer / Observer
Source 负责输入事件,Timer 负责定时,Observer 监听状态变化。卡顿监控通常基于 Observer 和独立线程信号。
Core Animation 提交
UIKit/CA 会在 RunLoop 的 BeforeWaiting 和 Exit 附近提交事务,因此布局和绘制问题会反映到主线程周期里。
RunLoop 阶段演示
kCFRunLoopEntry
RunLoop 即将进入一轮循环。Observer 可以在这个阶段记录状态。
主线程 RunLoop 不断重复这个过程,UI 事件、定时器、布局提交都被放进周期里处理。
while(1)。更合理的模型是:创建线程,给 RunLoop 添加 Source/Port 或 Timer,运行 RunLoop,任务通过 Source 或队列唤醒线程。
UIKit
UIKit 高频题集中在事件命中、响应链、UIView/CALayer 边界、AutoLayout 和滚动性能。
UIView 管交互和层级,CALayer 管显示和动画。事件传递从 window 向下 hit-test 找目标 view,响应链从目标 view 向上找 responder。卡顿主要来自主线程重任务、布局求解、图片处理和 GPU 渲染压力。
Hit-Test
- 从
UIWindow开始向子视图递归查找。 - 先判断
hidden、userInteractionEnabled、alpha、pointInside。 - 子视图通常按从前到后的顺序检查,命中最上层可响应视图。
Responder Chain
响应链从命中的 view 开始,沿 superview、viewController、window、application 向上传递。事件传递是“找谁”,响应链是“谁处理”。
| 问题 | 更准确的描述 | 优化建议 |
|---|---|---|
| AutoLayout 慢 | 不是“不能用”,而是复杂约束系统在频繁更新时求解成本高。 | 减少嵌套和约束数量,列表里缓存高度,必要时局部使用 frame。 |
| 离屏渲染 | mask、阴影、group opacity、复杂圆角等可能触发额外渲染路径。 | 设置 shadowPath,预渲染图片,避免滚动中动态绘制。 |
| UITableView vs UICollectionView | 单列标准列表下 UITableView 更专用;UICollectionView 更灵活但布局系统更通用。 | 按需求选型,不为简单单列列表引入复杂布局。 |
多线程
先分清 CPU、线程、RunLoop、队列的层级,再讨论同步/异步、串行/并行、锁和死锁。
线程是 CPU 调度单位,GCD 队列是任务调度抽象。串行队列一次执行一个任务,并行队列可同时执行多个任务。同步会等待任务完成,异步不等待。死锁常见于当前串行队列同步派发到自己。
| 组合 | 行为 | 面试提醒 |
|---|---|---|
| 串行 + 同步 | 当前线程等待任务执行完。 | 在当前串行队列里 sync 到自己会死锁。 |
| 串行 + 异步 | 任务按顺序执行,调用方不等待。 | 可能新开线程,也可能复用线程;队列不等于线程。 |
| 并行 + 同步 | 当前线程等待,任务可在当前线程或其他线程执行。 | 同步限制的是调用方,不是队列并发能力。 |
| 并行 + 异步 | 多个任务可并发执行,调用方立即返回。 | 并发数由系统调度,不是无限开线程。 |
死锁推演
主线程正在执行
当前代码已经运行在主线程,而主队列也是串行队列。
队列里的下一个任务必须等当前任务结束才有机会执行。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"不会执行到这里");
});
栅栏函数
dispatch_barrier 只对自建并发队列有意义。它等待前面的任务完成,再独占执行,然后放行后续任务。
线程安全
互斥锁、信号量、串行队列、读写锁、NSCondition 都是工具。关键是保护共享可变状态。
单例
dispatch_once 保证初始化只执行一次,适合全局唯一实例。不要把单例当成全局状态垃圾桶。
网络与缓存
把 HTTP 流程、现代 TLS、NSURLSession 结构、缓存协商和断点续传串起来,答案会比单背步骤更稳。
HTTP 请求通常经过 DNS、TCP、TLS、请求响应和连接复用。HTTPS 用证书验证身份,用密钥协商生成对称密钥。缓存由 Cache-Control、ETag、Last-Modified 等共同决定;断点续传依赖 Range 和服务端支持。
现代 TLS 说法
TLS 1.2/1.3 主流使用 ECDHE 做密钥协商,证书用于身份验证和签名校验,后续数据用对称加密保护。
旧式简化说法
“客户端生成随机密钥,用服务器公钥加密发过去”更像旧 RSA key exchange 的简化描述。面试中最好补一句现代 TLS 的 ECDHE。
NSURLSession
管理会话、连接复用、任务创建、Cookie、缓存和协议栈行为。
Configuration
控制超时、缓存、蜂窝网络、后台传输、Cookie、协议类等。
Task / Delegate
DataTask、DownloadTask、UploadTask 执行具体请求;Delegate 接收进度、重定向、证书验证和完成回调。
| 缓存头 / 策略 | 含义 | 常见结论 |
|---|---|---|
Cache-Control: max-age |
强缓存有效期。 | 未过期可直接使用缓存,通常不发请求。 |
ETag / If-None-Match |
基于资源标识的协商缓存。 | 服务端返回 304 表示缓存仍可用。 |
Last-Modified / If-Modified-Since |
基于修改时间的协商缓存。 | 精度和可靠性弱于 ETag,但仍常见。 |
Range |
请求资源的部分字节区间。 | 断点续传依赖它,服务端文件变化会导致 resumeData 失效。 |
启动优化与稳定性
启动优化要拆成 pre-main 和 main 后;稳定性要按崩溃类型定位:野指针、越界、KVC/KVO、线程、生命周期和资源异常。
pre-main 主要受 Mach-O 加载、动态库、符号绑定、+load、静态初始化影响;main 后关注首屏路径。优化方向是减少动态库和启动期工作、懒加载、二进制重排、用 Instruments/MetricKit 度量。
pre-main
- 加载 Mach-O 和 dyld。
- 加载依赖库,做 Rebase / Bind。
- 执行
+load和 C++ 静态构造。 - 现代系统有 dyld closure,但动态库数量和启动期代码仍重要。
main 后
UIApplicationMain创建应用对象和 delegate。- 开启主线程 RunLoop。
- 创建 window、rootViewController、加载首屏。
- 首屏网络、图片、布局和同步 IO 都会影响体感。
二进制重排
把启动路径热函数放得更连续,减少 Page Fault。常配合 order file、启动采样和实际设备验证。
KVC
给标量属性设置 nil 会触发 setNilValueForKey:,未知 key 会触发 setValue:forUndefinedKey:。
KVO
旧式 KVO 要管理 add/remove、context 和回调线程。现代系统减少了部分 dealloc 崩溃,但工程上仍应清晰管理观察生命周期。
| 稳定性问题 | 典型原因 | 治理动作 |
|---|---|---|
| 数组 / 字典越界 | 服务端数据异常、异步状态变化、索引未校验。 | 输入校验、边界判断、NSNull 处理、数据快照。 |
| 野指针 | assign 对象、unsafe 引用、delegate 生命周期错误。 |
改用 weak,明确持有关系,打开 Zombie 辅助定位。 |
| KVO 崩溃 | 重复添加/移除、context 混乱、回调线程更新 UI。 | 用唯一 context、封装 token、主线程更新 UI。 |
| 启动慢 | 动态库多、+load 重、首屏同步任务多。 |
延迟加载、懒初始化、减少启动链路 IO 和计算。 |
面经高频地图
把网上常见 iOS 面经和项目面试问题合并后,高频区可以分成“语言底层、系统机制、工程项目、线上治理、基础能力”五类。准备时不要只背答案,要给每类都准备一个能讲清楚的项目例子。
初中级重点是 OC/Swift 基础、Runtime、RunLoop、内存、Block、GCD、UIKit、网络;高级岗位会继续追问启动优化、线上监控、架构拆分、组件化、CI/CD、稳定性治理和复杂业务设计。
| 分类 | 面经常见问法 | 考察点 | 准备方式 |
|---|---|---|---|
| Objective-C 底层 | Runtime 消息发送、分类加载、关联对象、KVO 实现、+load 和 +initialize 区别。 |
是否理解动态派发和类加载,而不是只会调用 API。 | 画出对象、类、元类、cache、转发链;准备一次线上 crash 或 swizzle 案例。 |
| Swift | 值类型和引用类型、协议泛型、ARC、闭包捕获、actor、MainActor、async/await。 | 类型系统、并发模型、和 Objective-C 混编边界。 | 准备一个 Swift 业务模块迁移或并发改造例子。 |
| UIKit / 渲染 | 事件传递、响应链、UIView/CALayer、AutoLayout、列表卡顿、离屏渲染。 | 是否能从主线程、布局、图片、GPU 四条线分析卡顿。 | 准备一套 Feed/评论/直播列表优化方案。 |
| 网络与数据 | HTTP/TLS、缓存、断点续传、DNS、弱网、Core Data/SQLite、数据迁移。 | 端到端链路意识和异常恢复能力。 | 用“请求链路 + 缓存策略 + 失败降级 + 埋点”组织答案。 |
| 性能稳定性 | 启动优化、OOM、卡顿、Crash 率、MetricKit、线上监控、日志链路。 | 是否具备线上闭环:发现、定位、止血、修复、验证。 | 准备一次真实性能优化,带指标前后对比。 |
| 架构工程化 | 组件化、路由、依赖治理、模块边界、CI、灰度、AB、热修复、测试。 | 大型 App 协作能力和长期维护能力。 | 讲清楚“为什么拆、按什么边界拆、拆完如何防止依赖倒挂”。 |
| 计算机基础 | 线程进程、锁、内存、TCP、数据库索引、常见算法、设计模式。 | 能否把 iOS 问题落回通用 CS 原理。 | 每天刷小题,但面试重点是复杂度、边界和代码质量。 |
回答结构
先给结论,再讲原理,最后讲项目落地。比如问启动优化:先拆 pre-main/main 后,再讲如何量化,最后给你做过的削减动作和收益。
高级追问
高级面试更关心“取舍”:为什么不用某方案、灰度怎么控、出了问题怎么回滚、指标怎么证明有效。
项目表达
每个项目都准备四件事:背景、技术难点、关键决策、结果指标。没有指标的优化,很容易被追问到虚。
Swift 与 SwiftUI
Swift 面试不只问语法,重点是值语义、协议泛型、错误处理、闭包捕获、ARC、并发安全,以及和 Objective-C/ UIKit 混编时的边界。
Swift 用强类型、值语义、协议和泛型提升安全性;类仍由 ARC 管理,闭包也可能循环引用。现代 Swift 并发用 async/await 表达异步流程,用 actor 隔离可变状态,用 @MainActor 约束 UI 更新。
值类型 vs 引用类型
struct / enum 默认值语义,适合不可共享状态;class 是引用语义,适合身份、继承和共享生命周期。数组、字典、字符串有 Copy-on-Write。
协议与泛型
协议描述能力,泛型保留静态类型信息。some Protocol 表示不透明返回类型,any Protocol 表示 existential 容器。
Optional
Optional 把“可能为空”放进类型系统。面试重点不是会不会解包,而是 API 设计时是否能表达缺失、失败和默认值。
| 主题 | 要点 | 常见坑 |
|---|---|---|
| 闭包捕获 | { [weak self] in } 用于打断循环引用,必要时在闭包内转强引用。 |
所有异步闭包都无脑 weak 会导致关键流程中途 self 释放;要结合业务生命周期判断。 |
| 错误处理 | throw 表达可恢复错误,Result 适合回调/状态流,fatalError 只适合不可恢复编程错误。 |
把所有错误都吞成 nil,会丢失排障信息。 |
| 并发 | Task 表示并发任务,async let 适合结构化并发,actor 保护内部可变状态。 |
异步函数不等于后台线程;UI 更新仍应回到主 actor。 |
| SwiftUI | View 是状态到界面的声明式映射,常见状态源有 @State、@Binding、@ObservedObject、@StateObject。 |
复杂业务仍要分层,不能把网络、路由和副作用全部塞进 View。 |
| 混编 | Swift 调 Objective-C 依赖 bridging header;Objective-C 调 Swift 依赖自动生成的 -Swift.h。 |
泛型、Swift-only enum、actor 等能力暴露给 OC 时受限,需要设计桥接 API。 |
生命周期与页面管理
生命周期题要拆开:App 进程生命周期、Scene 生命周期、ViewController 生命周期、类加载生命周期。它们发生在不同层级,混在一起讲会显得不稳。
App 会在 foreground、active、background、suspended 等状态间切换;页面从 loadView、viewDidLoad、viewWillAppear 到 viewDidAppear。Objective-C 类在镜像加载时可能执行 +load,首次收到消息前可能触发 +initialize。
main。+load
类或分类被 Runtime 加载到进程时调用,早于 main,不依赖是否使用这个类。多个分类的 +load 都会执行,启动期做重活会拖慢 pre-main。
+initialize
类第一次收到消息前由 Runtime 懒触发,父类通常先于子类。它可能被多线程触发,现代代码更推荐用 dispatch_once 或显式初始化表达依赖。
页面生命周期
viewDidLoad 做一次性视图准备;viewWillAppear 处理每次展示前刷新;viewDidAppear 适合曝光、动画、首帧后任务。
路由与回退
大项目通常用统一路由描述页面入口、参数校验、登录拦截、降级兜底和跨模块解耦。
状态恢复
前后台切换、进程被杀、低内存回收都要求页面状态可重建,不应依赖单个控制器长期存活。
数据持久化
持久化题的核心是“按数据类型选存储”:配置、隐私凭证、结构化数据、大文件缓存、离线队列、日志,各自对一致性、性能和安全性的要求不同。
小配置用 UserDefaults,敏感凭证用 Keychain,文件和图片走沙盒文件系统,结构化关系数据用 SQLite/Core Data,缓存要有容量、过期和清理策略。涉及版本升级时必须考虑数据迁移和回滚。
| 方案 | 适合数据 | 面试要点 |
|---|---|---|
| UserDefaults | 少量偏好配置、开关、轻量状态。 | 不是数据库,不适合大对象和频繁写入;写入时机和默认值要清楚。 |
| Keychain | token、账号凭证、设备级敏感标识。 | 关注访问控制、迁移、卸载后残留、共享访问组。 |
| File Cache | 图片、视频、下载文件、日志、临时数据。 | 区分 Documents、Library/Caches、tmp;缓存可被系统清理,重要数据不能只放 Caches。 |
| SQLite / FMDB | 结构化数据、本地索引、离线队列。 | 事务、索引、分页、迁移、并发读写是重点。 |
| Core Data | 对象图、关系复杂、本地持久层。 | 理解 context、persistent store、merge policy、批量更新和线程 confinement。 |
| 内存缓存 | 短生命周期热点数据。 | 配合内存警告清理;NSCache 会自动响应内存压力但不能依赖精确淘汰。 |
架构与工程化
架构题不是背 MVC/MVVM 名词,而是解释复杂业务如何拆分、依赖如何收敛、模块如何协作、变更如何可测可回滚。
好的 iOS 架构要降低耦合、提高可测性和交付效率。页面层处理 UI,业务层沉淀用例,数据层封装网络和缓存,基础层提供日志、路由、配置、监控等能力。组件化时要控制依赖方向,避免模块互相 import。
| 模式/手段 | 适用场景 | 风险 |
|---|---|---|
| MVC | 简单页面、系统原生协作成本低。 | Controller 容易膨胀,需要把网络、格式化、业务规则拆出去。 |
| MVVM | 展示逻辑多、状态转换复杂、需要单测。 | ViewModel 也可能变胖;输入输出和生命周期要清晰。 |
| Clean / VIPER | 多人协作、业务规则复杂、模块边界明确。 | 文件和协议数量多,小页面会显得过重。 |
| 组件化 | 大型 App、多团队并行、独立编译和发布。 | 公共层膨胀、循环依赖、路由参数失控。 |
| 插件化/动态化 | 配置化页面、跨端容器、快速实验。 | 调试、性能、稳定性和审核边界都要额外治理。 |
依赖治理
依赖方向一般从业务模块指向基础能力,跨业务通信走协议、路由或服务注册。禁止 A 模块直接依赖 B 的内部实现。
CI/CD
常见流水线包括 lint、单测、构建、包体检查、符号表上传、灰度配置、崩溃监控回归和自动化冒烟。
测试金字塔
工具函数和业务规则用单测,模块交互用集成测试,关键链路用 UI 自动化,线上再用监控兜底。
线上监控与排障
结合你本地 `ios-client` 项目来看,大型 iOS App 的监控不是单点 SDK,而是稳定性、性能、资源、网络、视觉体验、业务埋点、日志追踪、灰度告警组成的闭环。
线上监控要覆盖 Crash/OOM/Watchdog、启动和卡顿、CPU/内存/磁盘/电量、网络错误、页面黑白屏、业务漏斗和日志 trace。指标异常后要能定位版本、设备、系统、页面、用户路径和最近操作。
| 监控层 | 核心指标 | 排障动作 |
|---|---|---|
| 稳定性 | Crash 率、OOM/FOOM、Watchdog、CPU Block、异常类型、崩溃堆栈、版本分布。 | 符号化、聚类、回溯发布变更、灰度止血、补充上下文日志。 |
| 性能 | 冷启动、热启动、TTI、首帧、FPS、卡顿率、长卡顿、主线程耗时。 | Instruments、MetricKit、火焰图、主线程采样、启动任务拆解。 |
| 资源 | footprint、resident memory、CPU、线程数、wakeups、磁盘占用、温度、电量。 | 按页面/场景采集水位,结合内存图、泄漏、缓存和图片解码分析。 |
| 网络 | 请求成功率、状态码、错误码、耗时、DNS/TLS/TCP 阶段、重试、上下行字节。 | 弱网复现、traceId 串联服务端、缓存命中分析、降级和重试策略检查。 |
| 体验 | 黑屏白屏、内容错位、文本截断、点击无响应、页面加载失败。 | 截图/视图树/日志打包,按页面和实验维度归因。 |
| 业务 | 曝光、点击、转化、支付成功率、漏斗断点、埋点丢失率。 | 端上埋点校验、服务端对账、AB 分组和版本维度分析。 |
发现
通过告警、看板、Crash 聚类、用户反馈和客服工单发现问题。好的告警要有阈值、同比/环比和影响面。
定位
把问题收敛到版本、系统、机型、页面、实验、网络环境和用户路径,再结合日志、堆栈、trace 和上下文。
止血
优先灰度回滚、配置降级、关闭实验、熔断高风险功能,再安排代码修复和版本验证。
安全、发布与合规
客户端安全题常常和发布流程绑在一起问:证书、签名、权限、隐私、网络安全、数据安全、App Store 审核、灰度和回滚。
iOS 安全从代码签名和沙盒开始,网络侧依赖 ATS/TLS,敏感数据放 Keychain,权限访问要最小化并说明用途。发布上要管理证书、描述文件、符号表、灰度、崩溃监控和审核合规。
代码签名
签名保证 App 来自可信开发者且未被篡改。证书、App ID、Provisioning Profile 和 entitlements 必须匹配。
网络安全
默认遵守 ATS,使用 TLS,必要时做证书锁定。证书锁定要设计轮换和兜底,避免证书更新导致全量不可用。
隐私合规
相册、定位、相机、麦克风、通讯录等权限要有明确用途说明,采集数据遵循最小化和可解释原则。
| 发布环节 | 检查项 | 线上风险 |
|---|---|---|
| 构建 | 版本号、环境配置、证书、Profile、编译参数、Bitcode/符号表策略。 | 环境错、签名失败、符号表缺失导致 crash 无法定位。 |
| 提审 | 权限描述、隐私清单、支付规则、后台能力、第三方 SDK 合规。 | 审核被拒、隐私投诉、能力滥用。 |
| 灰度 | 按版本、人群、实验、地区、设备逐步放量。 | 问题全量扩散,无法快速回滚。 |
| 发布后 | Crash、启动、核心转化、网络错误、用户反馈。 | 新版本质量劣化但未及时发现。 |
计算机基础与算法
iOS 面试的算法通常不会像纯后端那样深,但基础题很常见。准备方向是常用数据结构、复杂度、线程同步、网络协议和数据库索引。
算法重点刷数组、字符串、链表、栈队列、哈希表、树、二分、滑动窗口、动态规划入门。计算机基础重点看进程线程、锁、内存、TCP/HTTP、数据库索引和事务,并能映射到 iOS 场景。
| 基础主题 | iOS 场景 | 面试连接方式 |
|---|---|---|
| 进程与线程 | App 进程、Extension 进程、主线程 UI、后台队列。 | 解释为什么 UI 必须主线程,以及线程过多为什么会增加调度和内存成本。 |
| 锁与同步 | 缓存字典、数据库队列、单例初始化、读写状态。 | 说明互斥锁、串行队列、读写锁、信号量的取舍。 |
| TCP/HTTP | 接口请求、图片加载、长连接、直播/IM。 | 把 DNS、连接、TLS、请求、响应、缓存、重试串成链路。 |
| 数据库 | 本地消息、搜索历史、草稿箱、离线缓存。 | 讲清索引、事务、分页、迁移和并发写入。 |
| 设计模式 | 代理、观察者、工厂、策略、责任链、适配器。 | 不要硬背定义,要说它解决了什么变化点。 |