iOS Interview Playbook

从零散笔记到可复述的 iOS 知识模型

新版把原有底层知识、真实项目监控经验和网上高频面经重新归类:先看 30 秒答法,再看核心模型、易错点、项目落地和追问方向。目标不是背术语,而是能把原因、边界和取舍讲顺。

知识地图

内存语义strong / weak / copy / atomic
Block捕获、堆栈、循环引用
Runtimeisa、cache、转发链
RunLoopMode、Observer、休眠唤醒
UIKit事件、渲染、布局性能
稳定性启动、KVC/KVO、崩溃治理
工程化架构、组件化、CI、发布
面经补全Swift、算法、OS、数据库
01 / Ownership

修饰符与内存语义

属性修饰符解决“谁拥有对象、怎么暴露 API”,变量修饰符解决“闭包捕获、生命周期和置空行为”。面试里要把 property attribute 和 ownership qualifier 分开讲。

30 秒答法

对象属性默认用 strong,delegate 和反向引用用 weak,NSString/集合/Block 属性优先 copy。基础类型用 assign。Block 里避免循环引用用 __weak,需要修改局部变量才用 __block

修饰符 语义 适用场景 补充与坑点
strong / retain 强引用,拥有对象。 模型、服务对象、普通对象属性。 ARC 下常写 strongretain 多见于 MRC 或历史代码。
weak 弱引用,不增加引用计数,目标释放后自动置 nil delegate、dataSource、父子/回调反向引用。 只能用于对象类型;比 assign 安全,因为不会悬垂。
assign 直接赋值,不做对象生命周期管理。 BOOLNSIntegerCGFloat 等标量。 不要修饰 Objective-C 对象,否则对象释放后会留下野指针。
copy 保存副本,通常是不可变副本或堆上的 Block。 NSStringNSArrayNSDictionary、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 是线程安全。”更准确的说法是:它只保护单次存取,复合操作仍需要锁、队列或其他同步方案。

02 / Closure Object

Block

Block 的高频点是对象本质、三种存储位置、变量捕获方式、循环引用和 __block 底层结构。

30 秒答法

Block 是带函数指针和捕获环境的对象。全局 Block 不捕获自动变量;栈 Block 在栈上;拷贝后是堆 Block。自动变量默认按值捕获,对象默认强捕获;需要修改局部变量用 __block,破循环引用用 __weak

NSGlobalBlock 不捕获自动变量,生命周期全局存在。
NSStackBlock 捕获自动变量且未逃逸,位于栈上。
NSMallocBlock 被 copy 到堆上,能安全逃逸出当前栈帧。

捕获规则

  • 局部自动变量默认按值捕获,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
03 / Message Dispatch

Runtime

Runtime 的主线是“对象如何找到方法”:对象通过 isa 找类,类查 cache 和方法列表,找不到再解析和转发。

30 秒答法

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 信息,把动态结构映射到静态对象上。

04 / Event Loop

RunLoop

RunLoop 是线程的事件循环:有事处理,没事休眠,被事件唤醒后继续。主线程默认开启,子线程要长期存活时需要手动配置 source/timer 并启动。

30 秒答法

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 的 BeforeWaitingExit 附近提交事务,因此布局和绘制问题会反映到主线程周期里。

RunLoop 阶段演示

kCFRunLoopEntry

RunLoop 即将进入一轮循环。Observer 可以在这个阶段记录状态。

主线程 RunLoop 不断重复这个过程,UI 事件、定时器、布局提交都被放进周期里处理。

子线程保活不能只写 while(1)。更合理的模型是:创建线程,给 RunLoop 添加 Source/Port 或 Timer,运行 RunLoop,任务通过 Source 或队列唤醒线程。
主线程 Timer 加到 Default Mode 时,滚动进入 Tracking Mode 后可能不触发。需要滚动中继续触发时,把 Timer 加到 Common Modes。
05 / UI Pipeline

UIKit

UIKit 高频题集中在事件命中、响应链、UIView/CALayer 边界、AutoLayout 和滚动性能。

30 秒答法

UIView 管交互和层级,CALayer 管显示和动画。事件传递从 window 向下 hit-test 找目标 view,响应链从目标 view 向上找 responder。卡顿主要来自主线程重任务、布局求解、图片处理和 GPU 渲染压力。

Hit-Test

  • UIWindow 开始向子视图递归查找。
  • 先判断 hiddenuserInteractionEnabledalphapointInside
  • 子视图通常按从前到后的顺序检查,命中最上层可响应视图。

Responder Chain

响应链从命中的 view 开始,沿 superview、viewController、window、application 向上传递。事件传递是“找谁”,响应链是“谁处理”。

主线程JSON 解析、归档、文件 IO、图片解码都要避免挤在滚动时执行。
布局约束越多、层级越深,AutoLayout 求解成本越高。
渲染阴影、mask、复杂圆角和过大图片会增加 GPU/离屏压力。
列表复用、预取、异步解码、缓存高度、滚动时降级非关键任务。
问题 更准确的描述 优化建议
AutoLayout 慢 不是“不能用”,而是复杂约束系统在频繁更新时求解成本高。 减少嵌套和约束数量,列表里缓存高度,必要时局部使用 frame。
离屏渲染 mask、阴影、group opacity、复杂圆角等可能触发额外渲染路径。 设置 shadowPath,预渲染图片,避免滚动中动态绘制。
UITableView vs UICollectionView 单列标准列表下 UITableView 更专用;UICollectionView 更灵活但布局系统更通用。 按需求选型,不为简单单列列表引入复杂布局。
06 / Concurrency

多线程

先分清 CPU、线程、RunLoop、队列的层级,再讨论同步/异步、串行/并行、锁和死锁。

30 秒答法

线程是 CPU 调度单位,GCD 队列是任务调度抽象。串行队列一次执行一个任务,并行队列可同时执行多个任务。同步会等待任务完成,异步不等待。死锁常见于当前串行队列同步派发到自己。

组合 行为 面试提醒
串行 + 同步 当前线程等待任务执行完。 在当前串行队列里 sync 到自己会死锁。
串行 + 异步 任务按顺序执行,调用方不等待。 可能新开线程,也可能复用线程;队列不等于线程。
并行 + 同步 当前线程等待,任务可在当前线程或其他线程执行。 同步限制的是调用方,不是队列并发能力。
并行 + 异步 多个任务可并发执行,调用方立即返回。 并发数由系统调度,不是无限开线程。

死锁推演

主线程正在执行

当前代码已经运行在主线程,而主队列也是串行队列。

队列里的下一个任务必须等当前任务结束才有机会执行。

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"不会执行到这里");
});

栅栏函数

dispatch_barrier 只对自建并发队列有意义。它等待前面的任务完成,再独占执行,然后放行后续任务。

线程安全

互斥锁、信号量、串行队列、读写锁、NSCondition 都是工具。关键是保护共享可变状态。

单例

dispatch_once 保证初始化只执行一次,适合全局唯一实例。不要把单例当成全局状态垃圾桶。

07 / Network

网络与缓存

把 HTTP 流程、现代 TLS、NSURLSession 结构、缓存协商和断点续传串起来,答案会比单背步骤更稳。

30 秒答法

HTTP 请求通常经过 DNS、TCP、TLS、请求响应和连接复用。HTTPS 用证书验证身份,用密钥协商生成对称密钥。缓存由 Cache-Control、ETag、Last-Modified 等共同决定;断点续传依赖 Range 和服务端支持。

现代 TLS 说法

TLS 1.2/1.3 主流使用 ECDHE 做密钥协商,证书用于身份验证和签名校验,后续数据用对称加密保护。

旧式简化说法

“客户端生成随机密钥,用服务器公钥加密发过去”更像旧 RSA key exchange 的简化描述。面试中最好补一句现代 TLS 的 ECDHE。

DNS把域名解析成 IP,可能受本地、运营商、公共 DNS 缓存影响。
TCP / QUICHTTP/1.1 和 HTTP/2 常基于 TCP;HTTP/3 基于 QUIC。
TLS证书校验、密钥协商、生成会话密钥。
Request发送请求行、Header、Body,服务端处理后返回响应。
Cache根据缓存策略决定直接用缓存、协商缓存或重新拉取。

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 失效。
08 / Launch & Stability

启动优化与稳定性

启动优化要拆成 pre-main 和 main 后;稳定性要按崩溃类型定位:野指针、越界、KVC/KVO、线程、生命周期和资源异常。

30 秒答法

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 和计算。
09 / Interview Radar

面经高频地图

把网上常见 iOS 面经和项目面试问题合并后,高频区可以分成“语言底层、系统机制、工程项目、线上治理、基础能力”五类。准备时不要只背答案,要给每类都准备一个能讲清楚的项目例子。

30 秒答法

初中级重点是 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 后,再讲如何量化,最后给你做过的削减动作和收益。

高级追问

高级面试更关心“取舍”:为什么不用某方案、灰度怎么控、出了问题怎么回滚、指标怎么证明有效。

项目表达

每个项目都准备四件事:背景、技术难点、关键决策、结果指标。没有指标的优化,很容易被追问到虚。

10 / Swift

Swift 与 SwiftUI

Swift 面试不只问语法,重点是值语义、协议泛型、错误处理、闭包捕获、ARC、并发安全,以及和 Objective-C/ UIKit 混编时的边界。

30 秒答法

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。
面试里讲 Swift 并发时,不要只说“语法更简单”。更有力的说法是:结构化并发把任务生命周期放回调用结构里,actor 把共享可变状态的访问串行化,减少数据竞争。
11 / Lifecycle

生命周期与页面管理

生命周期题要拆开:App 进程生命周期、Scene 生命周期、ViewController 生命周期、类加载生命周期。它们发生在不同层级,混在一起讲会显得不稳。

30 秒答法

App 会在 foreground、active、background、suspended 等状态间切换;页面从 loadViewviewDidLoadviewWillAppearviewDidAppear。Objective-C 类在镜像加载时可能执行 +load,首次收到消息前可能触发 +initialize

进程启动dyld 加载 Mach-O 和动态库,执行必要的初始化,进入 main
UIApplicationMain创建 application、delegate,建立事件循环和主 RunLoop。
Scene / Window多 Scene 项目由 SceneDelegate 管理窗口和连接生命周期。
Root 页面创建 rootViewController,触发首屏页面加载、布局、渲染。
前后台切换进入后台要保存状态、暂停任务、释放可重建资源,回前台恢复 UI 和业务状态。

+load

类或分类被 Runtime 加载到进程时调用,早于 main,不依赖是否使用这个类。多个分类的 +load 都会执行,启动期做重活会拖慢 pre-main。

+initialize

类第一次收到消息前由 Runtime 懒触发,父类通常先于子类。它可能被多线程触发,现代代码更推荐用 dispatch_once 或显式初始化表达依赖。

页面生命周期

viewDidLoad 做一次性视图准备;viewWillAppear 处理每次展示前刷新;viewDidAppear 适合曝光、动画、首帧后任务。

路由与回退

大项目通常用统一路由描述页面入口、参数校验、登录拦截、降级兜底和跨模块解耦。

状态恢复

前后台切换、进程被杀、低内存回收都要求页面状态可重建,不应依赖单个控制器长期存活。

12 / Persistence

数据持久化

持久化题的核心是“按数据类型选存储”:配置、隐私凭证、结构化数据、大文件缓存、离线队列、日志,各自对一致性、性能和安全性的要求不同。

30 秒答法

小配置用 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 会自动响应内存压力但不能依赖精确淘汰。
一致性写入是否原子,崩溃中断后能否恢复。
性能大文件 IO、数据库迁移和 JSON 编解码不要卡主线程。
安全敏感数据加密、最小化存储、避免日志泄露。
迁移版本升级、降级、灰度回滚都要有兼容策略。
13 / Architecture

架构与工程化

架构题不是背 MVC/MVVM 名词,而是解释复杂业务如何拆分、依赖如何收敛、模块如何协作、变更如何可测可回滚。

30 秒答法

好的 iOS 架构要降低耦合、提高可测性和交付效率。页面层处理 UI,业务层沉淀用例,数据层封装网络和缓存,基础层提供日志、路由、配置、监控等能力。组件化时要控制依赖方向,避免模块互相 import。

模式/手段 适用场景 风险
MVC 简单页面、系统原生协作成本低。 Controller 容易膨胀,需要把网络、格式化、业务规则拆出去。
MVVM 展示逻辑多、状态转换复杂、需要单测。 ViewModel 也可能变胖;输入输出和生命周期要清晰。
Clean / VIPER 多人协作、业务规则复杂、模块边界明确。 文件和协议数量多,小页面会显得过重。
组件化 大型 App、多团队并行、独立编译和发布。 公共层膨胀、循环依赖、路由参数失控。
插件化/动态化 配置化页面、跨端容器、快速实验。 调试、性能、稳定性和审核边界都要额外治理。

依赖治理

依赖方向一般从业务模块指向基础能力,跨业务通信走协议、路由或服务注册。禁止 A 模块直接依赖 B 的内部实现。

CI/CD

常见流水线包括 lint、单测、构建、包体检查、符号表上传、灰度配置、崩溃监控回归和自动化冒烟。

测试金字塔

工具函数和业务规则用单测,模块交互用集成测试,关键链路用 UI 自动化,线上再用监控兜底。

面试中讲组件化,要避免只说“拆 Pod”。更完整的答案应包含:模块边界、依赖方向、资源隔离、路由协议、编译耗时、版本管理、灰度发布和故障回滚。
14 / Observability

线上监控与排障

结合你本地 `ios-client` 项目来看,大型 iOS App 的监控不是单点 SDK,而是稳定性、性能、资源、网络、视觉体验、业务埋点、日志追踪、灰度告警组成的闭环。

30 秒答法

线上监控要覆盖 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 和上下文。

止血

优先灰度回滚、配置降级、关闭实验、熔断高风险功能,再安排代码修复和版本验证。

对线上项目,最有价值的不是“接了多少 SDK”,而是能不能形成闭环:指标可观测、问题可归因、配置可止血、修复可验证、复盘能沉淀规则。
15 / Security & Release

安全、发布与合规

客户端安全题常常和发布流程绑在一起问:证书、签名、权限、隐私、网络安全、数据安全、App Store 审核、灰度和回滚。

30 秒答法

iOS 安全从代码签名和沙盒开始,网络侧依赖 ATS/TLS,敏感数据放 Keychain,权限访问要最小化并说明用途。发布上要管理证书、描述文件、符号表、灰度、崩溃监控和审核合规。

代码签名

签名保证 App 来自可信开发者且未被篡改。证书、App ID、Provisioning Profile 和 entitlements 必须匹配。

网络安全

默认遵守 ATS,使用 TLS,必要时做证书锁定。证书锁定要设计轮换和兜底,避免证书更新导致全量不可用。

隐私合规

相册、定位、相机、麦克风、通讯录等权限要有明确用途说明,采集数据遵循最小化和可解释原则。

发布环节 检查项 线上风险
构建 版本号、环境配置、证书、Profile、编译参数、Bitcode/符号表策略。 环境错、签名失败、符号表缺失导致 crash 无法定位。
提审 权限描述、隐私清单、支付规则、后台能力、第三方 SDK 合规。 审核被拒、隐私投诉、能力滥用。
灰度 按版本、人群、实验、地区、设备逐步放量。 问题全量扩散,无法快速回滚。
发布后 Crash、启动、核心转化、网络错误、用户反馈。 新版本质量劣化但未及时发现。
16 / CS Basics

计算机基础与算法

iOS 面试的算法通常不会像纯后端那样深,但基础题很常见。准备方向是常用数据结构、复杂度、线程同步、网络协议和数据库索引。

30 秒答法

算法重点刷数组、字符串、链表、栈队列、哈希表、树、二分、滑动窗口、动态规划入门。计算机基础重点看进程线程、锁、内存、TCP/HTTP、数据库索引和事务,并能映射到 iOS 场景。

数组字符串双指针、滑动窗口、前缀和、原地修改。
链表快慢指针、反转、合并、环检测。
递归、层序遍历、二叉搜索树、最近公共祖先。
动态规划状态定义、转移方程、初始化、空间压缩。
基础主题 iOS 场景 面试连接方式
进程与线程 App 进程、Extension 进程、主线程 UI、后台队列。 解释为什么 UI 必须主线程,以及线程过多为什么会增加调度和内存成本。
锁与同步 缓存字典、数据库队列、单例初始化、读写状态。 说明互斥锁、串行队列、读写锁、信号量的取舍。
TCP/HTTP 接口请求、图片加载、长连接、直播/IM。 把 DNS、连接、TLS、请求、响应、缓存、重试串成链路。
数据库 本地消息、搜索历史、草稿箱、离线缓存。 讲清索引、事务、分页、迁移和并发写入。
设计模式 代理、观察者、工厂、策略、责任链、适配器。 不要硬背定义,要说它解决了什么变化点。
刷题时别只追求“会过”。iOS 面试写算法更看重边界条件、复杂度说明、命名清楚、空输入处理和能否边写边解释思路。