(2026年)iOS OC面试题,Swift面试题,Flutter面试及答案
# 📱 iOS 开发面试要点总结
> 💡 **这是一份全面的 iOS 开发面试要点总结,涵盖从基础到高级的各个技术领域**
---
## 📌 Objective-C 常见面试要点
### 1️⃣ 内存管理在实际开发中的应用
- 基本原则:MRC 下遵循“谁创建谁释放”与引用计数对称;ARC 由编译器在合适位置插入 retain/release/autorelease,仍需避免循环引用。
- 强/弱引用:UI 与数据模型之间常用 weak 防止循环;定时器、闭包(block)中使用 weak/ unowned (Swift) 或 __weak (OC) 捕获 self。
- 容器与跨线程:集合类持有强引用;跨线程传递对象需确保线程安全与生命周期,例如在后台构建数据、回主线程使用时保证对象未被释放。
- autoreleasepool:在循环中创建大量临时对象(图片解码、文本处理)时使用 @autoreleasepool 或 @autoreleasepool{} 控制峰值内存。
- Core Foundation 桥接:CF 类型与 Objective-C 对象互转需遵循 Create Rule(含 Create/Copy/Retain 的函数需要手动 Release);使用 ARC 时用 __bridge_transfer / __bridge_retained。
- 诊断与工具:Instruments/Leaks/Allocations 检查泄漏与过度保留;Xcode Memory Graph 找循环引用。
### 🔍 【深入问题】
- 如何通过引用计数优化内存使用?引用计数为 0 时对象何时真正释放?dealloc 调用时机与线程安全?
- ARC 下 retain/release 的插入规则是什么?编译器如何优化不必要的 retain/release?
- 弱引用表的实现原理?为什么 weak 访问需要加锁?weak 指针置 nil 的时机?
- autoreleasepool 的嵌套机制?RunLoop 如何与 autoreleasepool 协作?哨兵对象的作用?
- 如何诊断和解决循环引用?Memory Graph 的使用技巧?如何避免过度保留(over-retain)?
- CF 对象的内存管理规则?__bridge 系列的使用场景和注意事项?CF 与 OC 对象混用的最佳实践?
- 内存警告(didReceiveMemoryWarning)的处理策略?如何实现内存缓存和清理机制?
### ✅ 【答案与解释】
**1. 引用计数优化与对象释放** 💡
- 引用计数为 0 时,对象立即进入 dealloc 流程,但实际内存释放由系统决定(可能延迟)
- dealloc 在对象所属线程调用,此时对象已从引用计数表移除,发送消息会崩溃
- ⚡ **优化策略**:使用 weak 避免不必要强引用,及时释放不需要对象,用 @autoreleasepool 控制临时对象生命周期
**2. ARC 的 retain/release 插入规则** 💡
- 赋值时:新值 retain,旧值 release(如 `self.property = newValue`)
- 函数返回:返回值自动 retain,调用者负责 release
- 局部变量:作用域结束时自动 release
- 编译器优化:识别不可变对象、局部作用域等,消除不必要的 retain/release
**3. 弱引用表实现** 💡
- Runtime 维护全局弱引用表(SideTable),以对象地址为 key,存储所有指向该对象的 weak 指针
- 对象释放时,遍历弱引用表,将所有 weak 指针置为 nil
- 访问 weak 需要加锁:多线程环境下,weak 指针可能正在被置 nil,需要同步保护
**4. autoreleasepool 机制** 💡
- 嵌套结构:每个 @autoreleasepool 创建新 pool,形成栈结构
- RunLoop 协作:主线程 RunLoop 在进入时创建 pool,退出时释放;子线程需手动管理
- 哨兵对象(POOL_BOUNDARY):标记 pool 边界,drain 时从栈顶回溯到哨兵,依次 release
**5. 循环引用诊断** 💡
- Memory Graph:可视化对象引用关系,快速定位循环引用
- 技巧:关注 retain cycle 警告,检查 block、delegate、timer 的持有关系
- 避免过度保留:使用 weak 引用,及时释放不需要对象,避免容器强持有大量对象
**6. CF 对象管理** 💡
- Create Rule:CFCreate/CFCopy/CFRetain 返回的对象需要 CFRelease
- __bridge:仅类型转换,不改变引用计数
- __bridge_retained:OC→CF,ARC 不再管理,需要手动 CFRelease
- __bridge_transfer:CF→OC,转移所有权给 ARC,自动管理
**7. 内存警告处理** 💡
- 清理缓存:图片缓存、数据缓存、临时对象
- 释放资源:停止不必要动画、取消网络请求、释放大对象
- 实现策略:建立内存优先级,优先释放低优先级资源,保留关键数据
---
### 2️⃣ 多线程的实际应用场景与回主线程方法
**核心要点:**
- 🎯 **应用场景**:耗时 I/O(网络、文件)、图片解码/压缩、数据库读写、批量计算、日志写入、防止主线程卡顿。
- 🔄 **回主线程方法**:`dispatch_async(dispatch_get_main_queue(), ^{ ... })`;NSOperationQueue 的 mainQueue;Swift 的 DispatchQueue.main.async。
### 🔍 【深入问题】
- 为什么 UI 操作必须在主线程?UIKit 的线程安全机制?后台线程操作 UI 会发生什么?
- 如何设计线程安全的单例?dispatch_once 的实现原理?如何避免线程安全问题?
- 多线程环境下的数据同步策略?如何避免竞态条件?读写锁的使用场景?
- 线程池的设计与实现?如何控制并发数量?如何实现任务优先级和依赖关系?
- 后台任务的生命周期管理?如何确保后台任务完成?后台任务超时处理?
- 线程间通信的最佳实践?如何安全地传递对象?如何避免线程间数据竞争?
### ✅ 【答案与解释】
**1. UI 操作必须在主线程** 💡
- UIKit 不是线程安全的,所有 UI 操作必须在主线程执行
- 后台线程操作 UI 会导致:界面更新延迟、崩溃、数据不一致
- 机制:UIKit 内部使用主线程 RunLoop 处理所有 UI 事件,非主线程操作会被忽略或崩溃
**2. 线程安全的单例** 💡
```objc
+ (instancetype)sharedInstance {
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
```
> 📝 **代码说明**:使用 `dispatch_once` 确保线程安全且只执行一次初始化
- dispatch_once 原理:使用原子操作和内存屏障,确保只执行一次,线程安全且性能高
- 避免线程安全问题:使用 dispatch_once、@synchronized、pthread_mutex 等同步机制
3. **数据同步策略**:
- 串行队列:将共享数据访问封装到串行队列,保证顺序访问
- 读写锁:读操作并发,写操作互斥,适合读多写少场景
- 不可变对象:使用不可变数据结构,避免共享可变状态
- 避免竞态:使用锁、原子操作、队列隔离等方式保护共享资源
4. **线程池设计**:
- 使用 NSOperationQueue 的 maxConcurrentOperationCount 控制并发数
- 任务优先级:通过 NSOperation 的 queuePriority 设置
- 依赖关系:使用 addDependency 建立任务依赖
- 自定义线程池:使用 dispatch_queue 和信号量实现
5. **后台任务管理**:
- 使用 UIApplication 的 beginBackgroundTaskWithExpirationHandler 申请后台时间
- 确保完成:在 expirationHandler 中保存状态,在任务完成时调用 endBackgroundTask
- 超时处理:设置超时时间,超时后保存进度,下次启动继续
6. **线程间通信**:
- 主线程回调:使用 dispatch_async(dispatch_get_main_queue()) 切回主线程
- 安全传递:传递不可变对象,或使用深拷贝避免共享可变状态
- 避免竞争:使用队列、锁、原子操作等同步机制保护共享数据
---
### 3️⃣ GCD 深入解析
- 队列类型:串行队列(顺序执行,常用于同步资源访问),并发队列(提高吞吐),主队列(UI 更新)。
- 同步/异步:sync 阻塞当前线程等待执行完毕;async 立即返回。禁止在主队列 sync 以避免死锁。
- QoS:userInteractive、userInitiated、default、utility、background,影响调度优先级。
- 常用模式:barrier(并发队列写操作串行化),group(并行任务收敛),semaphore(资源计数/限流),once(dispatch_once,单例初始化),timer(dispatch_source_t)。
- 内存与生命周期:队列持有 block;block 捕获的对象易形成循环引用,需使用 __weak。
### 🔍 【深入问题】
- GCD 的底层实现原理?队列与线程的关系?线程池如何管理?
- dispatch_sync 与 dispatch_async 的性能差异?何时使用同步,何时使用异步?
- QoS 的优先级继承机制?如何避免优先级反转?QoS 对电池消耗的影响?
- barrier 的实现原理?如何保证写操作的原子性?barrier 与锁的区别?
- dispatch_group 的使用场景?如何实现任务依赖?group 的异常处理?
- semaphore 的信号量机制?如何实现限流和资源控制?semaphore 与锁的区别?
- dispatch_once 的线程安全实现?如何避免重复初始化?once 的性能优化?
- dispatch_source 的使用场景?如何实现定时器、文件监控、信号处理?
- GCD 与 NSOperation 的性能对比?如何选择合适的多线程方案?
### ✅ 【答案与解释】
1. **GCD 底层实现**:
- GCD 基于线程池和队列管理,系统维护全局线程池,按需创建和复用线程
- 队列与线程:队列是任务容器,线程是执行单元;一个线程可以执行多个队列的任务
- 线程池管理:系统根据队列优先级和系统负载动态调整线程数,避免过度创建线程
2. **sync vs async**:
- sync:阻塞当前线程,等待任务完成,适合需要结果的场景
- async:立即返回,不阻塞,适合后台任务
- 性能:async 性能更好,不阻塞线程;sync 会阻塞,可能导致死锁
3. **QoS 优先级继承**:
- 高优先级任务会继承低优先级任务的 QoS,避免优先级反转
- 避免反转:使用 QoS 继承,或使用 os_unfair_lock 替代 OSSpinLock
- 电池影响:高 QoS 任务会提高 CPU 频率,增加电池消耗
4. **barrier 实现**:
- barrier 任务会等待所有之前的任务完成,然后独占执行,之后的任务等待 barrier 完成
- 原子性:通过任务顺序保证,barrier 执行时没有其他任务并发执行
- 与锁区别:barrier 是队列级别的同步,锁是代码块级别的同步
5. **dispatch_group**:
- 使用场景:等待多个异步任务完成,实现任务依赖
- 任务依赖:使用 notify 等待所有任务完成,或使用 wait 阻塞等待
- 异常处理:任务失败不影响 group,需要在任务内部处理异常
6. **semaphore 机制**:
- 信号量维护一个计数器,wait 减1(为0时阻塞),signal 加1(唤醒等待线程)
- 限流:初始化信号量为最大并发数,任务前 wait,完成后 signal
- 与锁区别:信号量可以控制多个资源,锁只能控制一个资源
7. **dispatch_once**:
- 使用原子操作和内存屏障,确保只执行一次,线程安全且性能高
- 避免重复初始化:once 保证只执行一次,后续调用直接返回
- 性能优化:使用静态变量缓存,避免重复初始化开销
8. **dispatch_source**:
- 定时器:dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER) 创建定时器
- 文件监控:DISPATCH_SOURCE_TYPE_VNODE 监控文件变化
- 信号处理:DISPATCH_SOURCE_TYPE_SIGNAL 处理系统信号
9. **GCD vs NSOperation**:
- GCD:C API,轻量,性能好,适合简单异步任务
- NSOperation:面向对象,支持依赖、取消、优先级,适合复杂任务管理
- 选择:简单任务用 GCD,需要依赖、取消、优先级用 NSOperation
---
### 4️⃣ TCP、HTTP、WebSocket 区别
- TCP:传输层协议,面向连接、可靠、字节流;需自定义消息边界和心跳。
- HTTP:基于 TCP 的应用层请求/响应协议,天然有明确定界;1.1 支持持久连接/管线化,2/3 多路复用、头压缩。
- WebSocket:基于 HTTP 握手升级,之后长连接全双工、低开销,适合实时双向通信(IM、行情)。
### 🔍 【深入问题】
- TCP 三次握手和四次挥手的详细过程?为什么需要三次握手?TIME_WAIT 状态的作用?
- TCP 的拥塞控制机制?滑动窗口的作用?如何优化 TCP 性能?
- HTTP/1.1 与 HTTP/2 的区别?HTTP/3 的优势?如何选择 HTTP 版本?
- WebSocket 的握手过程?如何实现心跳机制?WebSocket 的断线重连策略?
- 如何实现自定义协议?如何设计消息格式?如何实现协议升级?
- 网络性能优化策略?如何减少网络延迟?如何实现请求合并和去重?
### ✅ 【答案与解释】
1. **TCP 三次握手**:
- 过程:客户端发送 SYN → 服务器回复 SYN-ACK → 客户端发送 ACK
- 为什么三次:确保双方都能发送和接收,防止已失效的连接请求到达服务器
- 四次挥手:客户端 FIN → 服务器 ACK → 服务器 FIN → 客户端 ACK
- TIME_WAIT:等待 2MSL,确保最后一个 ACK 到达,防止新连接收到旧数据
2. **TCP 拥塞控制**:
- 机制:慢启动、拥塞避免、快重传、快恢复
- 滑动窗口:控制发送速率,避免接收方缓冲区溢出
- 优化:调整窗口大小、使用 TCP_NODELAY、启用 TCP Fast Open
3. **HTTP 版本对比**:
- HTTP/1.1:持久连接、管道化,但仍有队头阻塞
- HTTP/2:多路复用、头部压缩、服务器推送,解决队头阻塞
- HTTP/3:基于 QUIC,解决 TCP 队头阻塞,更快连接建立
- 选择:根据服务器支持情况选择,优先 HTTP/2
4. **WebSocket**:
- 握手:HTTP Upgrade 请求,服务器返回 101 Switching Protocols
- 心跳:定期发送 ping/pong 帧,检测连接状态
- 重连:指数退避策略,限制重连次数,保存未发送消息
5. **自定义协议**:
- 消息格式:长度+类型+数据,或使用分隔符
- 设计:考虑版本兼容、错误处理、数据压缩
- 升级:使用版本号,支持多版本共存,逐步迁移
6. **网络性能优化**:
- 减少延迟:使用 CDN、HTTP/2、连接复用、DNS 缓存
- 请求合并:合并相似请求,使用批量接口
- 请求去重:使用请求 ID,缓存相同请求结果
---
### 5️⃣ 应用程序生命周期(iOS)
- 关键节点:`not running → inactive → active → background → suspended`。
- 常用回调:`application:didFinishLaunchingWithOptions:`、`applicationDidBecomeActive:`、`applicationWillResignActive:`、`applicationDidEnterBackground:`、`applicationWillEnterForeground:`、`applicationWillTerminate:`;Scene 模式对应 scene 的生命周期回调。
- 处理要点:后台任务申请、状态保存/恢复、资源释放、通知与音频权限处理。
### 🔍 【深入问题】
- 应用状态转换的时机?如何监听应用状态变化?状态转换时的资源管理?
- 后台任务的申请和管理?如何实现后台下载?如何实现后台定位?
- 状态保存和恢复机制?如何实现 UI 状态恢复?如何实现数据状态恢复?
- Scene 的生命周期?多窗口场景下的状态管理?如何实现 Scene 间的通信?
- 应用终止的处理?如何实现优雅退出?如何保存关键数据?
- 内存警告的处理?如何实现内存优化?如何避免被系统杀死?
### ✅ 【答案与解释】
1. **应用状态转换**:
- 时机:用户操作、系统事件(来电、通知)、内存压力等触发状态转换
- 监听:实现 UIApplicationDelegate 方法,或监听 UIApplicationDidBecomeActiveNotification
- 资源管理:进入后台时释放不必要资源,进入前台时恢复资源
2. **后台任务**:
- 申请:beginBackgroundTaskWithExpirationHandler 申请后台时间(最多30秒)
- 后台下载:使用 NSURLSession 的 backgroundSessionConfiguration
- 后台定位:使用 locationManager 的 allowsBackgroundLocationUpdates
3. **状态保存和恢复**:
- UI 状态:实现 encodeRestorableStateWithCoder 和 decodeRestorableStateWithCoder
- 数据状态:保存到 UserDefaults 或文件,恢复时读取
- 机制:系统自动保存和恢复,需要在 Info.plist 配置
4. **Scene 生命周期**:
- 生命周期:sceneWillEnterForeground、sceneDidBecomeActive 等
- 多窗口:每个 Scene 独立管理状态,使用 UserActivity 实现通信
- 通信:使用 NSUserActivity 或 NotificationCenter
5. **应用终止**:
- 优雅退出:在 applicationWillTerminate 中保存关键数据
- 保存数据:使用同步方式保存,确保数据不丢失
- 关键数据:用户设置、未保存内容、登录状态等
6. **内存警告**:
- 处理:在 didReceiveMemoryWarning 中释放缓存、清理资源
- 优化:使用 @autoreleasepool、及时释放大对象、优化图片加载
- 避免被杀死:减少内存占用、及时响应内存警告、优化内存使用
---
### 6️⃣ iOS 核心动画
- 框架:Core Animation(QuartzCore)。模型层/呈现层分离,动画提交到渲染服务端执行。
- 常用类:CABasicAnimation、CAKeyframeAnimation、CATransition、CAAnimationGroup、CASpringAnimation;隐式动画由事务控制。
- 关键属性:`duration`、`timingFunction`、`fillMode`、`removedOnCompletion`、`beginTime`;使用 `UIView` block API 封装便捷动画。
- 性能:尽量在 GPU 层完成(位置、透明度、变换),避免频繁离屏渲染;复杂视图可用 shouldRasterize,注意缓存失效。
### 🔍 【深入问题】
- Core Animation 的渲染流程?模型层和呈现层的区别?如何实现自定义动画?
- 动画的性能优化?如何减少离屏渲染?如何优化动画帧率?
- 隐式动画和显式动画的区别?如何控制隐式动画?CATransaction 的作用?
- 动画的暂停和恢复?如何实现动画进度控制?如何实现动画组合?
- 3D 变换的实现?如何实现 3D 动画?如何优化 3D 渲染性能?
- 动画的交互?如何实现手势驱动的动画?如何实现物理动画?
### ✅ 【答案与解释】
1. **Core Animation 渲染流程**:
- 流程:App 提交动画 → Render Server 处理 → GPU 渲染 → 显示
- 模型层:layer 的实际属性值,动画的目标值
- 呈现层:layer.presentationLayer,动画过程中的中间值
- 自定义动画:实现 CAAnimationDelegate,或使用 CADisplayLink
2. **动画性能优化**:
- 减少离屏渲染:避免圆角+阴影组合,使用 shadowPath,预渲染图片
- 优化帧率:使用 GPU 友好属性(transform、opacity),避免频繁更新
- 性能监控:使用 Instruments 的 Core Animation 工具
3. **隐式 vs 显式动画**:
- 隐式:修改 layer 属性自动触发,由 CATransaction 控制
- 显式:使用 CAAnimation 对象,更精确控制
- CATransaction:控制动画事务,可以禁用隐式动画、设置动画参数
4. **动画控制**:
- 暂停:设置 layer.speed = 0,timeOffset = 当前时间
- 恢复:设置 layer.speed = 1
- 进度控制:使用 timeOffset 或 speed 控制动画进度
- 组合:使用 CAAnimationGroup 组合多个动画
5. **3D 变换**:
- 实现:使用 CATransform3D,设置 m34 实现透视效果
- 3D 动画:结合旋转、缩放、位移实现 3D 效果
- 性能优化:使用 shouldRasterize 缓存,避免复杂计算
6. **动画交互**:
- 手势驱动:使用 UIPanGestureRecognizer 等手势,更新动画进度
- 物理动画:使用 UIDynamicAnimator 实现物理效果
- 实现:结合手势识别和动画,实现交互式动画
---
### 7️⃣ 链表 vs 数组
- 数组:连续内存,随机访问 O(1),中间插入/删除 O(n);缓存友好。
- 链表:分散内存,插入/删除 O(1)(已知节点),访问 O(n);额外指针开销,不利于缓存。
- iOS 实践:大多数场景用 NSArray/NSMutableArray(封装动态数组);链表多用于队列/缓存淘汰等特定结构。
### 🔍 【深入问题】
- NSArray 的底层实现?如何实现动态扩容?NSArray 的线程安全性?
- 如何选择合适的数据结构?数组和链表的性能对比?何时使用链表?
- 如何实现高效的数组操作?如何优化数组的插入和删除?如何实现数组的批量操作?
- 缓存友好的数据结构设计?如何利用 CPU 缓存提高性能?如何优化内存访问模式?
### ✅ 【答案与解释】
1. **NSArray 底层实现**:
- 实现:NSArray 是不可变数组,内部使用连续内存存储对象指针
- NSMutableArray:使用动态数组实现,内部维护容量和实际大小,按需扩容
- 动态扩容:当容量不足时,分配新内存(通常为原容量的 1.5-2 倍),复制元素,释放旧内存
- 线程安全:NSArray 不可变,线程安全;NSMutableArray 可变,非线程安全,需要同步保护
2. **数据结构选择**:
- 数组:适合随机访问、顺序遍历、缓存友好场景
- 链表:适合频繁插入/删除、不需要随机访问、内存分散可接受场景
- 性能对比:数组访问 O(1),链表访问 O(n);数组插入 O(n),链表插入 O(1)
- 使用链表:实现队列、栈、LRU 缓存、需要频繁插入/删除的场景
3. **数组操作优化**:
- 插入/删除:批量操作优于单个操作,使用 replaceObjectsInRange 批量替换
- 优化策略:预分配容量,减少扩容次数;使用索引集合批量操作
- 批量操作:使用 enumerateObjectsWithOptions 并行遍历,使用 NSPredicate 过滤
4. **缓存友好设计**:
- 原理:CPU 缓存按行加载(通常 64 字节),连续内存访问命中率高
- 优化:使用连续内存布局,减少指针跳转,按访问顺序组织数据
- 内存访问:顺序访问优于随机访问,局部性原理,减少缓存未命中
---
### 8️⃣ HTTPS 登录请求的端到端流程
- URL 解析与 DNS 解析(可能含 HTTPDNS/缓存)→ TCP 三次握手 → TLS 握手(协商版本、套件、证书验证、对称密钥协商)→ 建立加密通道。
- 客户端发送 HTTP 请求(含账号/密码,通常在 body 中),可能附带 Cookie/Token;服务器解密、鉴权、业务处理(DB/缓存/风控)。
- 服务器返回响应,通过 TLS 加密后经 TCP 传输;客户端解密、解析响应,落地会话信息(如 Set-Cookie)、更新 UI。
### 🔍 【深入问题】
- TLS 握手的详细过程?如何实现证书验证?如何实现证书固定?
- 如何实现安全的密码传输?如何实现密码加密?如何实现密码哈希?
- 会话管理的策略?如何实现 Token 刷新?如何实现单点登录?
- 如何实现请求签名?如何防止重放攻击?如何实现请求防篡改?
- 如何优化 HTTPS 性能?如何实现连接复用?如何减少握手次数?
### ✅ 【答案与解释】
1. **TLS 握手过程**:
- Client Hello:客户端发送支持的 TLS 版本、加密套件、随机数
- Server Hello:服务器选择 TLS 版本和加密套件,发送证书和随机数
- 证书验证:客户端验证服务器证书(CA 链、有效期、域名匹配)
- 密钥交换:客户端生成预主密钥,用服务器公钥加密发送
- 会话密钥:双方根据预主密钥和随机数生成会话密钥
- 完成:双方发送加密完成消息,开始加密通信
- 证书固定:在客户端硬编码服务器证书或公钥哈希,防止中间人攻击
2. **密码安全传输**:
- 传输:使用 HTTPS 加密传输,不在 URL 中传递密码
- 加密:客户端不加密密码,依赖 TLS 层加密;或使用 RSA 加密后传输
- 哈希:服务器端使用 bcrypt、Argon2 等安全哈希算法存储密码
- 加盐:每个密码使用唯一盐值,防止彩虹表攻击
3. **会话管理**:
- 策略:使用 Token(JWT)或 Session ID 管理会话
- Token 刷新:Access Token 短期有效,Refresh Token 长期有效,定期刷新
- 单点登录:使用 SSO 协议(OAuth 2.0、SAML),统一认证中心管理会话
- 存储:Token 存储在 Keychain,Session ID 存储在 HTTP-only Cookie
4. **请求签名与防篡改**:
- 签名:使用 HMAC 或 RSA 签名,对请求参数和时间戳签名
- 防重放:使用时间戳和随机数(nonce),服务器验证时间窗口和 nonce 唯一性
- 防篡改:签名包含所有关键参数,任何修改都会导致签名验证失败
5. **HTTPS 性能优化**:
- 连接复用:使用 HTTP/2 多路复用,一个连接处理多个请求
- 会话复用:TLS 会话恢复,减少握手次数
- 减少握手:使用 TLS 1.3 的 0-RTT,首次连接后支持 0 次往返
- 证书优化:使用 ECDSA 证书,减少证书大小和验证时间
---
### 9️⃣ load 与 initialize 区别
- 调用时机:`+load` 在类/分类被加载进内存时调用,按编译顺序且无需触发;`+initialize` 在首次向类发送消息前调用,可能被多次触发(需自判)。
- 线程:`+load` 在加载阶段串行调用;`+initialize` 由运行时保证线程安全。
- 目的:`+load` 适合方法交换、运行时注册;`+initialize` 适合惰性初始化静态资源/配置。应避免在 `+load` 做重操作。
### 🔍 【深入问题】
- +load 的调用顺序?如何控制 +load 的执行顺序?+load 的性能影响?
- +initialize 的线程安全机制?如何避免 +initialize 的重复调用?+initialize 的调用时机?
- 如何优化启动性能?如何减少 +load 的执行时间?如何延迟初始化?
- 方法交换的最佳实践?如何安全地进行方法交换?方法交换的陷阱?
- 运行时注册的应用场景?如何实现动态注册?如何实现插件化?
### ✅ 【答案与解释】
1. **+load 调用顺序**:
- 顺序:父类 → 子类 → 分类,按编译顺序调用
- 控制顺序:无法直接控制,依赖编译顺序;可通过依赖关系间接控制
- 性能影响:+load 在启动时执行,阻塞启动流程,应避免耗时操作
- 优化:减少 +load 中的操作,将初始化延迟到 +initialize 或首次使用时
2. **+initialize 线程安全**:
- 机制:Runtime 使用锁保证 +initialize 只执行一次,多线程同时调用时只有一个线程执行
- 避免重复:Runtime 自动保证,但需要检查是否已初始化,避免重复初始化
- 调用时机:首次向类发送消息时调用,包括 alloc、类方法调用等
- 注意:子类未实现 +initialize 时会调用父类的 +initialize
3. **启动性能优化**:
- 减少 +load:将非关键初始化移到 +initialize 或懒加载
- 延迟初始化:使用懒加载属性,首次访问时初始化
- 异步初始化:将耗时操作放到后台线程,不阻塞主线程
- 分批初始化:将初始化任务分批执行,避免启动时集中执行
4. **方法交换最佳实践**:
- 安全实践:在 +load 中交换,检查方法是否存在,使用 dispatch_once 避免重复交换
- 实现:使用 method_exchangeImplementations,保存原实现以便调用
- 陷阱:交换后原方法调用会变成新方法,需要注意调用顺序和递归调用
- 调试:使用符号断点跟踪方法调用,验证交换是否正确
5. **运行时注册**:
- 应用场景:动态模块加载、插件系统、AOP 切面编程
- 动态注册:使用 class_addMethod、protocol_addMethodDescription 等 API
- 插件化:定义插件协议,运行时扫描和加载插件类,注册到系统
- 实现:使用 NSBundle 加载动态库,使用 Runtime API 注册类和方法
---
### 🔟 多线程死锁的产生与原因
- 典型场景:在主线程执行 `dispatch_sync` 到主队列;互相 `dispatch_sync` 的串行队列;锁顺序不一致导致循环等待;信号量初始值错误导致永久等待。
- 成因:资源竞争 + 循环等待(同时满足互斥、请求保持、不剥夺、循环等待)。预防:统一锁顺序、缩小锁粒度、避免在持锁状态下同步派发到同一队列、使用超时和死锁检测。
### 🔍 【深入问题】
- 死锁的检测方法?如何实现死锁检测?如何避免死锁?
- 如何设计无锁数据结构?如何实现无锁编程?无锁编程的性能优势?
- 锁的性能对比?如何选择合适的锁?如何优化锁的使用?
- 如何实现超时机制?如何实现死锁恢复?如何实现资源超时释放?
- 多线程调试技巧?如何定位死锁问题?如何分析线程竞争?
### ✅ 【答案与解释】
1. **死锁检测**:
- 检测方法:使用 Instruments 的 Thread States,查看线程是否都在等待
- 实现检测:使用资源分配图算法,检测循环等待;使用超时机制检测长时间等待
- 避免死锁:统一锁顺序、避免嵌套锁、使用超时、使用无锁数据结构
- 预防策略:设计时避免循环依赖,使用锁层次结构,减少锁的持有时间
2. **无锁编程**:
- 设计:使用原子操作(atomic operations)、CAS(Compare-And-Swap)实现无锁数据结构
- 实现:无锁队列、无锁栈、无锁哈希表,使用内存屏障保证可见性
- 性能优势:避免锁竞争,减少上下文切换,提高并发性能
- 适用场景:高并发、低延迟场景,但实现复杂,调试困难
3. **锁性能对比**:
- os_unfair_lock:最快,适合短临界区,不支持递归
- pthread_mutex:性能好,支持递归,适合一般场景
- NSLock:封装 pthread_mutex,性能略差,但 API 友好
- @synchronized:最慢,但异常安全,适合需要异常处理的场景
- 选择:根据场景选择,短临界区用 os_unfair_lock,需要递归用 pthread_mutex
4. **超时机制**:
- 实现:使用 dispatch_semaphore_wait 的超时版本,或使用 NSTimer 超时检测
- 死锁恢复:超时后释放资源,记录日志,通知上层处理
- 资源释放:使用 defer 或 @finally 确保资源释放,设置超时时间限制资源持有
5. **多线程调试**:
- 定位死锁:使用 Xcode 的 Thread Sanitizer,查看线程状态和锁持有情况
- 分析竞争:使用 Instruments 的 Time Profiler,查看锁竞争热点
- 调试技巧:添加日志记录锁的获取和释放,使用符号断点跟踪锁操作
- 工具:Thread Sanitizer、Instruments、LLDB 的 thread info 命令
11. 消息发送机制流程(Objective-C Runtime)
- 流程:先在对象的 isa 指向的类方法缓存中查找 → 类的方法列表 → 父类层级。未找到触发动态解析:`+resolveInstanceMethod:` / `+resolveClassMethod:` → 进入消息转发:`-forwardingTargetForSelector:`(快速转发)→ `-methodSignatureForSelector:` + `-forwardInvocation:`(完整转发)→ 最终触发 `doesNotRecognizeSelector:` 崩溃。
- 方法缓存:IMP 缓存可减少查找开销;分类方法会覆盖同名实例方法。
### 🔍 【深入问题】
- objc_msgSend 的汇编实现?方法查找的优化策略?缓存失效机制?
- 方法缓存的实现原理?如何提高缓存命中率?缓存大小如何控制?
- 动态解析的应用场景?如何实现动态方法添加?resolveInstanceMethod 的性能影响?
- 消息转发的性能开销?forwardInvocation 的调用时机?如何优化消息转发?
- 方法交换(method swizzling)的实现原理?如何安全地进行方法交换?方法交换的陷阱?
- 分类方法覆盖的加载顺序?如何避免分类方法覆盖的不可预期行为?
- 消息发送与直接函数调用的性能差异?如何优化消息发送性能?
- Runtime 的线程安全性?多线程环境下的方法查找和缓存更新?
### ✅ 【答案与解释】
1. **objc_msgSend 实现**:
- 汇编实现:使用快速路径和慢速路径,快速路径直接查找缓存
- 优化策略:方法缓存、方法列表排序、父类缓存
- 缓存失效:类的方法列表变化时,清空相关缓存
2. **方法缓存**:
- 实现:使用哈希表存储方法缓存,key 为 SEL,value 为 IMP
- 提高命中率:常用方法优先,避免频繁添加/删除方法
- 缓存大小:系统自动管理,通常为方法数量的 3/4
3. **动态解析**:
- 应用场景:动态添加方法、实现可选方法、方法转发
- 实现:实现 resolveInstanceMethod,使用 class_addMethod 添加方法
- 性能影响:动态解析只在第一次调用时执行,后续直接调用
4. **消息转发**:
- 性能开销:forwardInvocation 开销较大,需要创建 NSInvocation 对象
- 调用时机:快速转发失败后调用,完整转发最后调用
- 优化:使用快速转发(forwardingTargetForSelector),避免完整转发
5. **方法交换**:
- 实现原理:使用 method_exchangeImplementations 交换方法实现
- 安全实践:在 +load 中交换,检查方法是否存在,避免重复交换
- 陷阱:交换后原方法调用会变成新方法,需要注意调用顺序
6. **分类方法覆盖**:
- 加载顺序:按编译顺序加载,后加载的分类方法会覆盖先加载的
- 避免不可预期:使用前缀区分方法名,或使用子类而非分类
7. **性能差异**:
- 消息发送:需要查找方法,有运行时开销
- 直接调用:编译期确定,无运行时开销
- 优化:使用内联函数、减少消息发送、使用缓存
8. **线程安全性**:
- 方法查找:线程安全,使用读写锁保护
- 缓存更新:线程安全,使用原子操作更新
- 注意:方法添加/删除需要同步,避免并发修改
12. 使用 RunLoop 让线程常驻
- 做法:在子线程创建 `NSRunLoop`,添加至少一个输入源(port/CFRunLoopSource/Timer),然后调用 `run` 或循环 `runMode:beforeDate:`,防止线程立即退出。
- 应用:常驻线程处理 I/O、解码、日志;注意退出时先停止 RunLoop 再释放线程。
### 🔍 【深入问题】
- 常驻线程的生命周期管理?如何优雅地退出常驻线程?如何实现线程池?
- 如何实现线程间的通信?如何实现线程间的数据传递?如何实现线程同步?
- 常驻线程的性能优化?如何减少线程切换开销?如何实现线程复用?
- 如何实现任务队列?如何实现任务优先级?如何实现任务取消?
### ✅ 【答案与解释】
1. **常驻线程生命周期**:
- 管理:创建线程时保存 RunLoop 引用,退出时先停止 RunLoop 再释放线程
- 优雅退出:使用 CFRunLoopStop 停止 RunLoop,等待线程自然退出
- 线程池:使用 NSOperationQueue 管理线程池,系统自动管理线程生命周期
- 实现:创建固定数量的常驻线程,使用队列分发任务,按需创建和销毁
2. **线程间通信**:
- 通信方式:使用 Mach Port、RunLoop Source、队列、通知中心
- 数据传递:传递不可变对象,或使用深拷贝避免共享可变状态
- 线程同步:使用队列、锁、信号量等同步机制保护共享数据
- 最佳实践:使用串行队列封装共享状态,使用异步回调传递结果
3. **性能优化**:
- 减少切换:使用线程池复用线程,避免频繁创建和销毁
- 线程复用:使用 NSOperationQueue 的线程池,系统自动复用线程
- 优化策略:合理设置线程数量,避免过多线程导致上下文切换开销
4. **任务队列**:
- 实现:使用 NSOperationQueue 或自定义队列,使用 dispatch_queue
- 优先级:使用 NSOperation 的 queuePriority 或 dispatch_queue 的 QoS
- 取消:使用 NSOperation 的 cancel 方法,或使用 dispatch_source 取消任务
- 实现:检查取消标志,及时响应取消请求,清理资源
13. 僵尸对象、野指针、内存泄漏
- 僵尸对象:对象已释放但消息仍被发送,触发 EXC_BAD_ACCESS;可用 Zombies 调试定位。
- 野指针:指向未定义内存或已释放内存的指针;防范:置 nil、弱引用自动置 nil。
- 内存泄漏:对象不再需要但引用未释放,常见于循环引用(block/self/代理)、CFCreate 未释放;用 Instruments/Memory Graph 排查。
### 🔍 【深入问题】
- 如何预防僵尸对象?如何实现对象生命周期管理?如何实现对象池?
- 野指针的检测方法?如何实现野指针检测?如何避免野指针?
- 内存泄漏的排查技巧?如何实现内存泄漏检测?如何实现内存监控?
- 如何实现内存优化?如何减少内存占用?如何实现内存缓存?
- 如何实现内存警告处理?如何实现内存清理?如何避免内存峰值?
### ✅ 【答案与解释】
1. **预防僵尸对象**:
- 预防:使用 weak 引用,对象释放后自动置 nil;在 dealloc 中清理引用
- 生命周期管理:使用 ARC 自动管理,明确对象所有权,及时释放不需要的对象
- 对象池:复用对象减少创建和销毁,使用 NSCache 或自定义对象池
- 实现:创建对象池管理对象生命周期,按需分配和回收对象
2. **野指针检测**:
- 检测方法:使用 Address Sanitizer(ASan)检测野指针访问
- 实现检测:在调试模式下启用 Zombie Objects,检测已释放对象的访问
- 避免野指针:使用 weak 引用自动置 nil,在对象释放后不访问对象
- 最佳实践:使用 ARC,避免手动管理内存,使用 weak 引用避免野指针
3. **内存泄漏排查**:
- 排查技巧:使用 Instruments 的 Leaks 工具,使用 Memory Graph 可视化引用关系
- 检测实现:定期检查对象引用计数,使用工具监控内存增长
- 内存监控:使用 Instruments 的 Allocations 工具,监控内存分配和释放
- 定位方法:查看 Memory Graph 的引用链,定位循环引用的源头
4. **内存优化**:
- 优化策略:及时释放不需要的对象,使用 @autoreleasepool 控制临时对象
- 减少占用:使用轻量级对象,避免持有大对象,使用懒加载
- 内存缓存:使用 NSCache 实现内存缓存,设置缓存上限,响应内存警告
- 实现:建立内存优先级,优先释放低优先级缓存,保留关键数据
5. **内存警告处理**:
- 处理:在 didReceiveMemoryWarning 中清理缓存,释放不必要资源
- 清理策略:清理图片缓存、数据缓存、临时对象,停止不必要动画
- 避免峰值:使用 @autoreleasepool 控制峰值,分批处理大数据
- 实现:建立清理优先级,优先清理低优先级资源,保留关键数据
14. 物理地址、虚拟地址、逻辑地址、线性地址、虚拟内存
- **物理地址**:实际硬件内存地址,直接对应物理内存条上的位置。
- **虚拟地址/线性地址**(x86 定义相近):进程视角的地址,经 MMU(内存管理单元)映射到物理地址,提供地址空间隔离。
- **逻辑地址**:包含段选择子+偏移的地址(段式内存模型);经段机制转为线性地址。
- **虚拟内存**:通过页表把虚拟地址映射到物理页,支持隔离、按需换页、内存保护、内存共享等特性。
15. iOS 启动性能优化
- 目标:冷启动时间 < 3s。路径:dyld 阶段、runtime 阶段、首屏渲染。
- 优化点:减少/合并动态库与分类数量;精简启动执行的 ObjC 类/方法(少用 +load 重活);推迟耗时初始化(懒加载、按需注册);减少主线程 I/O(避免同步磁盘/网络);图片按需解码、首屏资源本地化;使用启动耗时埋点 + Instruments(Time Profiler、dyld)定位。
- 预热与缓存:合并小文件、预编译 storyboard/xib(或改代码布局)、缩短主线程阻塞。
### 🔍 【深入问题】
- 如何分析启动耗时?如何定位启动瓶颈?如何实现启动耗时埋点?
- dyld 加载优化?如何减少动态库数量?如何优化符号绑定?
- runtime 初始化优化?如何减少类注册?如何优化方法注册?
- 首屏渲染优化?如何实现首屏预加载?如何优化首屏资源加载?
- 如何实现渐进式启动?如何实现按需加载?如何实现延迟初始化?
- 启动性能监控?如何建立启动性能基准?如何持续优化启动性能?
### ✅ 【答案与解释】
1. **启动耗时分析**:
- 分析方法:使用 Instruments 的 Time Profiler,使用 dyld 工具分析动态库加载
- 定位瓶颈:使用启动耗时埋点,记录各阶段耗时,定位最耗时的操作
- 埋点实现:在关键位置添加时间戳,计算各阶段耗时,上报到监控系统
- 工具:Instruments、Xcode 的 Launch Time Profiler、自定义埋点
2. **dyld 加载优化**:
- 减少动态库:合并动态库,使用静态库替代,减少动态库数量
- 优化绑定:使用 -bind_at_load 减少延迟绑定,使用 -all_load 预绑定
- 符号优化:减少导出符号,使用静态链接减少符号绑定时间
- 实现:分析动态库依赖,合并相关库,优化符号导出
3. **runtime 初始化优化**:
- 减少类注册:延迟类注册,按需注册,减少启动时注册的类数量
- 优化方法注册:减少 +load 方法,将初始化移到 +initialize 或懒加载
- 分类优化:减少分类数量,合并相关分类,延迟分类加载
- 实现:分析类注册耗时,优化类结构,减少启动时注册
4. **首屏渲染优化**:
- 预加载:预加载首屏数据,预解码首屏图片,预热关键资源
- 资源优化:首屏资源本地化,减少网络请求,优化资源大小
- 渲染优化:减少首屏视图层级,优化布局计算,使用异步渲染
- 实现:分析首屏渲染耗时,优化视图结构,减少渲染任务
5. **渐进式启动**:
- 实现:先显示骨架屏,异步加载数据,逐步渲染内容
- 按需加载:延迟加载非关键模块,按需加载功能模块
- 延迟初始化:使用懒加载,将初始化移到首次使用时
- 策略:识别关键路径,优先加载关键功能,延迟加载次要功能
6. **启动性能监控**:
- 建立基准:定义启动性能指标,建立性能基准线
- 持续监控:集成启动耗时监控,定期分析性能趋势
- 优化流程:建立性能优化流程,持续优化启动性能
- 实现:使用 APM 工具监控启动性能,建立性能看板,定期回顾优化
16. Category 与 Extension 的区别
- Extension:编译期伴随类存在,通常写在 .m,能添加实例变量(在类本身实现中);用于声明私有属性/方法。
- Category:运行期加载,不能直接添加实例变量,只能添加方法;用于模块化拆分、为类增加方法;同名方法会覆盖原实现(后编译/加载者优先)。
17. 设计模式概念与常见模式
- 设计模式:可复用的解决方案模板,提升可维护性与扩展性。
- 常见:创建型(单例、工厂、抽象工厂、建造者、原型);结构型(适配器、装饰器、代理、外观、组合、桥接、享元);行为型(观察者、策略、命令、状态、责任链、迭代器、模板方法、备忘录、解释器、访问者、中介者)。
- iOS 实践:单例用于全局管理(配置、会话);代理/观察者用于事件分发;策略用于算法/布局切换;装饰器用于功能增强(分类/组合);外观用于封装子系统;命令/操作队列用于任务封装。
### 🔍 【深入问题】
- 如何选择合适的设计模式?设计模式的适用场景?如何避免过度设计?
- 单例模式的线程安全实现?如何避免单例的滥用?如何实现单例的测试?
- 工厂模式与抽象工厂的区别?如何实现工厂模式?如何实现依赖注入?
- 观察者模式的实现?如何实现发布订阅?如何优化观察者模式的性能?
- 策略模式的应用?如何实现策略切换?如何实现策略的动态加载?
- 如何组合使用设计模式?如何实现设计模式的演进?如何重构到设计模式?
### ✅ 【答案与解释】
1. **设计模式选择**:
- 选择原则:根据问题类型选择模式,考虑可维护性和扩展性
- 适用场景:创建型用于对象创建,结构型用于对象组合,行为型用于对象交互
- 避免过度设计:从简单方案开始,按需引入模式,避免为了模式而模式
- 实践:理解模式本质,根据实际需求选择,保持代码简洁
2. **单例模式**:
- 线程安全:使用 dispatch_once 保证线程安全,使用锁保护共享状态
- 避免滥用:只在真正需要全局唯一实例时使用,避免过度使用
- 测试:使用依赖注入,使用协议抽象,便于 Mock 和测试
- 实现:使用 dispatch_once 创建单例,提供重置方法便于测试
3. **工厂模式**:
- 区别:工厂模式创建单一产品,抽象工厂创建产品族
- 实现:定义工厂接口,实现具体工厂,客户端通过工厂创建对象
- 依赖注入:通过构造函数或属性注入依赖,提高可测试性
- 实践:使用协议定义工厂接口,实现具体工厂,使用依赖注入容器
4. **观察者模式**:
- 实现:使用 NotificationCenter、KVO、自定义观察者实现
- 发布订阅:使用 NotificationCenter 实现一对多通知,使用 KVO 实现属性监听
- 性能优化:使用弱引用避免循环引用,及时移除观察者,批量通知
- 优化:减少观察者数量,使用异步通知,合并相似通知
5. **策略模式**:
- 应用:算法切换、布局切换、支付方式切换等场景
- 策略切换:使用协议定义策略接口,运行时切换策略实现
- 动态加载:使用反射加载策略类,使用配置文件定义策略映射
- 实现:定义策略协议,实现具体策略,使用策略上下文切换
6. **模式组合与演进**:
- 组合使用:工厂+单例、观察者+策略、装饰器+适配器等组合
- 模式演进:从简单到复杂,按需引入模式,逐步重构
- 重构到模式:识别代码异味,应用相应模式,逐步重构代码
- 实践:理解模式关系,合理组合使用,保持架构清晰
18. 使用 self.xx 与 _xx 的区别
- `self.xx` 调用 setter/getter,触发 KVO/KVC、自定义逻辑、线程安全包装等;在 init/dealloc 内常直接用 ivar 以避免副作用(如未完全初始化触发 setter)。
- `_xx` 直接访问 ivar,不触发副作用,性能略优;在属性覆盖或需跳过 KVO 时使用。
19. 继承、接口、多实现与 Category 的取舍
- Objective-C 类不支持多重继承;可以实现多个协议(接口)。
- Category:为已有类增加方法,不可加 ivar;适合拆分实现或为系统类扩展行为。
- 重写方式:若需新增状态或覆写方法行为,优先继承;若仅补充方法、不需持有新状态且不改变原有行为,使用 Category。覆盖同名方法用 Category 有风险(加载顺序不确定),应尽量通过子类或方法交换且谨慎。
20. @property 的本质
- 编译器基于属性生成 ivar、getter、setter、协议声明和内存管理语义(assign/weak/strong/copy/atomic)。
- ivar:默认以 `_propertyName` 命名,可通过 @synthesize 指定;未显式 synthesize 时自动生成。
- getter/setter:根据属性修饰符生成实现,atomic 下会插入简单的自旋锁保证原子性(但不保证对象整体线程安全);copy/strong 等在 setter 中体现持有策略。
### 🔍 【深入问题】
- @property 的编译过程?如何查看生成的代码?如何自定义 getter/setter?
- atomic 的实现原理?atomic 的性能影响?如何实现线程安全的属性?
- copy 的实现机制?如何实现自定义 copy?copy 与 mutableCopy 的区别?
- @synthesize 的作用?如何自定义 ivar 名称?如何实现只读属性的内部可写?
- 属性的性能优化?如何减少属性访问开销?如何优化属性的内存布局?
### ✅ 【答案与解释】
1. **@property 编译过程**:
- 编译过程:编译器解析 @property,生成 ivar、getter、setter 声明和实现
- 查看代码:使用 clang -rewrite-objc 查看编译后的代码,使用 Hopper 反汇编
- 自定义 getter/setter:使用 @synthesize 指定 ivar,手动实现 getter/setter 方法
- 实现:重写 getter/setter,添加自定义逻辑,如懒加载、验证等
2. **atomic 实现**:
- 原理:使用自旋锁保护属性的读写,保证单个操作的原子性
- 性能影响:atomic 有锁开销,性能低于 nonatomic,但保证线程安全
- 线程安全:atomic 只保证单个操作的原子性,不保证对象整体的线程安全
- 实现:使用锁保护属性访问,或使用队列封装属性访问
3. **copy 机制**:
- 实现:setter 中调用 copy 方法,创建对象的不可变副本
- 自定义 copy:实现 NSCopying 协议,实现 copyWithZone 方法
- 区别:copy 返回不可变对象,mutableCopy 返回可变对象
- 使用:NSString、NSArray 等使用 copy,防止外部修改
4. **@synthesize 作用**:
- 作用:指定 ivar 名称,控制 getter/setter 的生成
- 自定义名称:使用 @synthesize propertyName = customIvarName
- 只读内部可写:在 extension 中重新声明为 readwrite,内部可写
- 实现:在 .m 文件的 extension 中声明 readwrite,实现内部可写
5. **属性性能优化**:
- 减少开销:使用 nonatomic,直接访问 ivar,减少方法调用
- 内存布局:合理排列属性顺序,减少内存对齐浪费
- 优化策略:使用懒加载,缓存计算结果,减少重复计算
- 实现:分析属性访问热点,优化频繁访问的属性,使用缓存
21. 属性关键字作用与适用场景
- `readwrite`/`readonly`:是否生成 setter。只读多用于外部只读、内部可写(配合 class extension 声明 readwrite)。
- `assign`:简单赋值,不改变引用计数,适用于基本类型;用于对象易成野指针,除非特殊(如 weak 备选)。
- `retain`/`strong`:持有对象并自动 release 旧值。用于一般对象持有。
- `copy`:拷贝副本,防止可变对象被外部修改,常用于 `NSString`、`NSArray`、`NSDictionary`、Block。
- `nonatomic`:不生成原子性锁,性能更好,iOS UI 属性默认使用;若多线程并发写需自行加锁。
22. weak 的使用场景与 assign 区别
- weak:不增加引用计数,被引用对象释放时自动置 nil;用于避免循环引用(delegate、block 捕获、父子持有)、避免野指针。
- assign:不改引用计数且不置 nil;仅用于基本类型或确定生命周期外部保障的场景。
23. id 特性与 __bridge 系列
- `id`:动态类型指针,可指向任意 OC 对象,编译期方法检查宽松,运行时按实际类分发。
- 桥接:CF/OC 对象互转时的 ARC 语义。
- `__bridge`:仅转类型,不改变引用计数。
- `__bridge_retained` / `CFBridgingRetain`:OC → CF,生成 +1,需要 CFRelease。
- `__bridge_transfer` / `CFBridgingRelease`:CF → OC,转移所有权给 ARC,自动 release。
24. Objective-C 内存管理与实践
- ARC 通过编译期插入 retain/release/autorelease 管理引用计数。核心风险:循环引用、CF 对象、弱引用生命周期。
- 解决:弱引用(delegate/block 捕获)、在 block 内使用 weak-strong dance;CFCreate 手动释放或使用 bridge transfer;autopool 控制峰值;工具(Leaks/Memory Graph)定位。
25. Category、Extension 与继承的区别
- Category:运行期添加方法,不加 ivar,适合拆分实现、为系统类扩展;同名覆盖有顺序风险。
- Extension:编译期的一部分,可加属性/实例变量(在类实现内),多用于私有声明、补充协议实现。
- 继承:创建子类可增添状态、覆写行为,类型层次清晰;用于需要新状态或改写逻辑的场景。
26. Notification 与 KVO 的区别
- Notification:广播式,一对多,解耦发布订阅;基于通知中心;不关注具体属性变化;需在适当时机移除观察者(iOS9 起弱引用自动移除,block 形式需特别注意)。
- KVO:针对对象属性变化监听,精确到 keyPath,自动触发回调;底层通过 isa-swizzling 动态子类;需要在销毁前移除(iOS11 后部分自动,但自定义仍需谨慎)。
### 🔍 【深入问题】
- Notification 的实现原理?通知中心的线程安全性?如何优化通知性能?
- 如何选择合适的通知机制?Notification vs KVO vs Delegate?如何设计事件系统?
- 如何实现通知的优先级?如何实现通知的过滤?如何实现通知的合并?
- 如何避免通知的内存泄漏?如何实现通知的自动移除?如何管理通知的生命周期?
- 如何实现自定义通知?如何实现通知的序列化?如何实现通知的持久化?
### ✅ 【答案与解释】
1. **Notification 实现原理**:
- 原理:使用 NSNotificationCenter 维护观察者列表,发布通知时遍历列表调用回调
- 线程安全:通知中心线程安全,但观察者回调在发布线程执行,需要切回主线程
- 性能优化:减少观察者数量,使用异步通知,合并相似通知,使用队列分发
- 优化:使用弱引用存储观察者,及时移除观察者,避免重复注册
2. **通知机制选择**:
- Notification:一对多、解耦、跨模块通信,适合全局事件
- KVO:属性监听、精确到 keyPath、自动触发,适合数据绑定
- Delegate:一对一、类型安全、编译期检查,适合紧密耦合的场景
- 设计事件系统:根据场景选择机制,可以组合使用,建立统一的事件总线
3. **通知优先级与过滤**:
- 优先级:使用队列分发通知,按优先级排序,高优先级先处理
- 过滤:在观察者回调中过滤通知,或使用自定义通知中心实现过滤
- 合并:使用队列缓冲通知,合并相似通知,减少重复处理
- 实现:自定义通知中心,实现优先级队列,实现通知过滤和合并逻辑
4. **通知内存管理**:
- 避免泄漏:使用弱引用存储观察者,iOS9+ 自动移除,block 形式需要特别注意
- 自动移除:在 dealloc 中移除观察者,或使用关联对象自动移除
- 生命周期:明确观察者生命周期,及时移除不需要的观察者
- 实现:使用 weak 引用,在适当时机移除,使用自动移除机制
5. **自定义通知**:
- 实现:继承 NSNotification,添加自定义属性,使用自定义通知中心
- 序列化:实现 NSCoding 协议,序列化通知内容,支持持久化
- 持久化:将通知存储到数据库或文件,应用启动时恢复
- 实现:定义通知协议,实现序列化和持久化,建立通知存储机制
27. 常用锁
- OSSpinLock(已废弃,存在优先级反转)→ os_unfair_lock(适合短临界区)。
- pthread_mutex(普通/递归),NSLock/NSRecursiveLock(封装 pthread),@synchronized(基于 obj 的递归 mutex + 异常安全),dispatch_semaphore(计数信号量/限流),NSCondition/NSConditionLock(条件变量),读写锁 pthread_rwlock/dispatch_barrier(并发读,串行写)。
### 🔍 【深入问题】
- 各种锁的性能对比?如何选择合适的锁?锁的性能优化?
- 优先级反转的原因和解决方案?如何避免优先级反转?
- 递归锁的使用场景?如何避免死锁?递归锁的性能影响?
- 读写锁的实现原理?如何实现读写锁?读写锁的性能优势?
- 无锁编程的实现?如何实现无锁数据结构?无锁编程的适用场景?
- 锁的粒度控制?如何减少锁竞争?如何优化锁的使用?
### ✅ 【答案与解释】
1. **锁的性能对比**:
- os_unfair_lock:最快,适合短临界区
- pthread_mutex:性能好,支持递归
- NSLock:封装 pthread_mutex,性能略差
- @synchronized:最慢,但异常安全
- 选择:短临界区用 os_unfair_lock,需要递归用 pthread_mutex,需要异常安全用 @synchronized
2. **优先级反转**:
- 原因:高优先级任务等待低优先级任务持有的锁
- 解决:使用优先级继承、优先级天花板、避免锁嵌套
- 避免:使用 os_unfair_lock 替代 OSSpinLock,合理设计锁的持有时间
3. **递归锁**:
- 使用场景:同一线程多次获取同一锁(如递归函数)
- 避免死锁:统一锁顺序,避免嵌套锁,使用超时机制
- 性能影响:递归锁性能略差于普通锁,但避免死锁更重要
4. **读写锁**:
- 实现原理:读操作共享锁,写操作独占锁
- 实现:使用 pthread_rwlock 或 dispatch_barrier
- 性能优势:读多写少场景下,读操作可以并发,提高性能
5. **无锁编程**:
- 实现:使用原子操作(atomic operations)、CAS(Compare-And-Swap)
- 无锁数据结构:无锁队列、无锁栈、无锁哈希表
- 适用场景:高并发、低延迟场景,但实现复杂,调试困难
6. **锁的优化**:
- 粒度控制:缩小锁的范围,减少持有时间
- 减少竞争:使用读写锁、无锁数据结构、减少共享数据
- 优化策略:避免锁嵌套、使用锁超时、监控锁竞争情况
28. KVC 底层实现
- `setValue:forKey:` 查找 setter(setKey:/_setKey:)→ 找实例变量 `_key`、`_isKey`、`key`、`isKey` 并直接赋值(支持 ivarAccess);类型转换通过 `setValue:forUndefinedKey:` 兜底。
- `valueForKey:` 查 getter(key/isKey/_key/_isKey)→ 若找不到,尝试 `valueForUndefinedKey:`;集合类型支持 KVC Collection Operators(@sum/@avg 等)。
### 🔍 【深入问题】
- KVC 的查找顺序?如何优化 KVC 性能?KVC 的性能开销?
- KVC Collection Operators 的使用?如何实现自定义操作符?操作符的性能影响?
- 如何实现自定义 KVC?如何实现 valueForUndefinedKey?如何实现 setValue:forUndefinedKey:?
- KVC 与 KVO 的关系?KVC 如何触发 KVO?如何优化 KVC 和 KVO 的组合使用?
- KVC 的安全性问题?如何防止 KVC 注入?如何实现 KVC 权限控制?
### ✅ 【答案与解释】
1. **KVC 查找顺序**:
- setValue:forKey: 查找顺序:setKey: → _setKey: → 实例变量 _key/_isKey/key/isKey
- valueForKey: 查找顺序:getKey/isKey/_key → 实例变量 _key/_isKey/key/isKey
- 优化:提供明确的 getter/setter,避免查找开销
- 性能开销:KVC 需要查找和类型转换,性能低于直接访问
2. **KVC Collection Operators**:
- 使用:@sum、@avg、@max、@min、@count 等操作符
- 自定义:实现 valueForKeyPath: 方法,处理自定义操作符
- 性能影响:操作符需要遍历集合,大数据集性能较差
3. **自定义 KVC**:
- valueForUndefinedKey::处理未定义的 key,返回默认值或抛出异常
- setValue:forUndefinedKey::处理未定义的 key,可以动态添加属性
- 实现:重写这两个方法,实现自定义逻辑
4. **KVC 与 KVO 关系**:
- KVC 触发 KVO:通过 KVC 设置属性会触发 KVO 通知
- 优化:直接使用 setter 而非 KVC,减少查找开销
- 组合使用:KVC 设置属性,KVO 监听变化,实现数据绑定
5. **KVC 安全性**:
- 注入风险:KVC 可以访问私有属性,存在安全风险
- 防止注入:验证 key 的合法性,限制可访问的属性
- 权限控制:使用白名单机制,只允许访问特定属性
29. KVO 内部原理
- 运行时为被观察对象生成动态子类(NSKVONotifying_ClassName),重写 setter,在其中调用 `willChangeValueForKey:` / `didChangeValueForKey:` 并触发回调;同时重写 class 返回原类、dealloc 清理。
- 影响:观察到的是包装子类;方法实现可能变化,手动调用 setter 触发,直接 ivar 赋值不会触发(需手动 KVO 通知)。
### 🔍 【深入问题】
- KVO 的动态子类生成机制?如何实现 KVO?KVO 的性能开销?
- 如何手动触发 KVO?如何实现 KVO 通知?如何优化 KVO 性能?
- KVO 的线程安全性?多线程环境下的 KVO?如何避免 KVO 的线程问题?
- KVO 的移除时机?如何避免 KVO 崩溃?如何实现 KVO 的自动移除?
- KVO 与 KVC 的关系?如何组合使用 KVO 和 KVC?如何优化组合使用?
### ✅ 【答案与解释】
1. **KVO 动态子类机制**:
- 生成机制:Runtime 动态创建 NSKVONotifying_ClassName 子类,重写 setter 方法
- 实现:使用 class_addMethod 添加 setter,使用 method_setImplementation 替换实现
- 性能开销:首次观察时创建子类,后续 setter 调用有额外开销(KVO 通知)
- 优化:减少观察的属性数量,避免频繁添加/移除观察者,使用批量通知
2. **手动触发 KVO**:
- 触发方法:调用 willChangeValueForKey 和 didChangeValueForKey
- 实现通知:在修改 ivar 前后调用 KVO 通知方法,触发观察者回调
- 性能优化:减少不必要的通知,批量通知,使用异步通知
- 实现:封装属性修改方法,自动触发 KVO,优化通知频率
3. **KVO 线程安全**:
- 线程安全:KVO 通知在修改属性的线程执行,需要切回主线程更新 UI
- 多线程:多线程修改属性时,KVO 通知可能在不同线程触发
- 避免问题:在属性修改线程触发通知,在观察者回调中切回主线程
- 实现:使用队列同步属性修改,在观察者回调中切回主线程
4. **KVO 移除时机**:
- 移除时机:在观察者 dealloc 前移除,避免访问已释放对象
- 避免崩溃:使用 try-catch 保护移除操作,检查观察者是否已移除
- 自动移除:使用关联对象存储观察者信息,在 dealloc 中自动移除
- 实现:封装 KVO 管理类,自动管理观察者生命周期,安全移除
5. **KVO 与 KVC 关系**:
- 关系:KVC 设置属性会触发 KVO 通知,KVO 监听属性变化
- 组合使用:使用 KVC 设置属性,KVO 监听变化,实现数据绑定
- 优化:直接使用 setter 而非 KVC,减少查找开销,提高性能
- 实现:建立数据绑定机制,使用 KVC/KVO 实现响应式编程
30. OC 反射机制
- 概念:运行时基于字符串/SEL 创建类实例、查找方法、发送消息。
- 常用 API:`NSClassFromString`/`objc_getClass` 创建类,`sel_registerName` 获取 SEL,`performSelector:`/`methodForSelector:`/`objc_msgSend` 发送消息,`class_copyMethodList`/`class_copyPropertyList`/`class_copyIvarList` 做元数据遍历。
- 应用:解耦路由、动态模块注册、序列化/反序列化、AOP/方法交换(需谨慎)。
### 🔍 【深入问题】
- 反射的性能开销?如何优化反射性能?如何减少反射的使用?
- 如何实现动态路由?如何实现模块化架构?如何实现插件化?
- 如何实现序列化和反序列化?如何实现 JSON 映射?如何实现数据模型转换?
- AOP 的实现原理?如何实现方法拦截?如何实现横切关注点?
- 反射的安全性?如何防止反射攻击?如何实现反射权限控制?
### ✅ 【答案与解释】
1. **反射性能开销**:
- 开销:字符串查找、方法查找、动态调用都有运行时开销
- 优化:缓存类和方法,使用 SEL 而非字符串,减少反射调用
- 减少使用:使用协议和泛型替代反射,编译期确定类型,减少运行时查找
- 实现:建立反射缓存,使用 SEL 缓存,优化反射调用路径
2. **动态路由与模块化**:
- 动态路由:使用 URL 路由,通过反射创建目标类,实现页面跳转
- 模块化架构:使用协议定义模块接口,运行时注册模块,实现模块间通信
- 插件化:定义插件协议,运行时扫描和加载插件,注册到系统
- 实现:建立路由表,使用反射创建对象,实现模块注册机制
3. **序列化与反序列化**:
- 序列化:使用 NSCoding 协议,或使用 JSONEncoder 序列化对象
- JSON 映射:使用 Codable 协议,或手动实现 JSON 到对象的映射
- 数据转换:使用反射获取属性列表,动态设置属性值,实现模型转换
- 实现:定义序列化协议,使用反射获取属性,实现自动映射
4. **AOP 实现**:
- 原理:使用 Method Swizzling 替换方法实现,在前后插入横切逻辑
- 方法拦截:使用 method_exchangeImplementations 交换方法,添加拦截逻辑
- 横切关注点:日志、性能监控、异常处理等横切逻辑
- 实现:定义 AOP 协议,使用 Method Swizzling 实现拦截,建立 AOP 框架
5. **反射安全性**:
- 安全风险:反射可以访问私有方法,存在安全风险
- 防止攻击:验证类名和方法名的合法性,限制可访问的类和方法
- 权限控制:使用白名单机制,只允许访问特定类和方法
- 实现:建立反射权限系统,验证访问权限,限制反射使用范围
31. const、static、extern、inline 简介
- const:只读限定符,修饰变量或指针指向内容;配合 static/extern 控制作用域。
- static:静态存储期;用于文件内全局或函数内静态变量,限制链接可见性到编译单元。
- extern:声明外部符号,常用于头文件声明全局变量/函数。
- inline:提示编译器内联,减少调用开销(非强制);在 C 中配合 static/extern 控制链接。
32. nil、Nil、NULL、[NSNull null] 区别
- nil:指向 OC 对象的空指针(id 类型)。
- Nil:指向 Class 的空指针。
- NULL:C 指针空值,void* 等。
- `[NSNull null]`:集合中表示“空”的对象,占位符。
33. UIView 与 CALayer 关系
- UIView 是高层封装,负责事件响应、触摸、Auto Layout、Responder Chain;内部持有 `CALayer *layer` 负责绘制与动画。
- 视图层级影响 Layer 层级;很多展示属性映射到 layer(cornerRadius/shadow/transform)。
34. id、NSObject*、instancetype 区别
- `id`:动态类型任意 OC 对象,编译期检查宽松。
- `NSObject *`:静态类型为 NSObject 或其子类。
- `instancetype`:返回类型为当前类(或子类)类型,编译期更严格,常用于 init/factory。
35. isa 指针的作用
- isa 指向对象所属的类对象(实例 → 类对象,类对象的 isa 指向元类),用于方法查找与元数据定位。
- 现代运行时使用优化 isa(位域)同时存放引用计数等标志位;类对象的 isa 指向元类,元类的 isa 最终指向根元类。
36. isa 指针的优化机制
- 优化 isa:使用位域技术,在 isa 指针中同时存储类信息、引用计数、弱引用标志等,节省内存。
- 元类链:实例对象的 isa → 类对象 → 元类 → 根元类 → 根元类自身,形成完整的元类链。
- 作用:支持方法查找、KVO 实现、动态类型检查等运行时特性。
37. Block 本质
- Block 是封装代码和上下文的 OC 对象,底层结构包含 isa、描述符、函数指针、捕获变量。
- 存储类型:全局块(不捕获、在全局区)、栈块(捕获局部,默认在栈)、堆块(copy 后在堆)。ARC 下传出作用域会自动 copy。
- 捕获语义:基本类型拷贝值,`__block` 允许可变并在 copy 时移动到堆;对象按强引用捕获,注意循环引用(用 weak/strong dance)。
### 🔍 【深入问题】
- Block 的底层数据结构?如何查看 Block 的内存布局?Block 的 isa 指向什么?
- Block 的三种类型如何判断?全局块、栈块、堆块的内存管理差异?
- Block 捕获变量的机制?为什么需要 __block?__block 变量的内存管理?
- Block 的 copy 操作何时发生?ARC 下的自动 copy 规则?MRC 下的手动 copy?
- Block 的循环引用如何形成?如何通过 weak-strong dance 解决?weak-strong dance 的实现原理?
- Block 与函数指针的性能对比?Block 的内存开销?如何优化 Block 的使用?
- Block 在多线程环境下的安全性?如何避免 Block 捕获的线程安全问题?
- Block 的序列化与反序列化?如何持久化 Block?Block 的调试技巧?
### ✅ 【答案与解释】
1. **Block 底层结构**:
- 结构:isa 指针、flags、reserved、invoke(函数指针)、descriptor、捕获的变量
- 查看布局:使用 `clang -rewrite-objc` 查看编译后的 C++ 代码
- isa 指向:_NSConcreteStackBlock(栈块)、_NSConcreteGlobalBlock(全局块)、_NSConcreteMallocBlock(堆块)
2. **Block 类型判断**:
- 全局块:不捕获外部变量,存储在全局区
- 栈块:捕获局部变量,存储在栈上
- 堆块:copy 后的块,存储在堆上
- 内存管理:全局块不需要管理,栈块需要 copy 到堆,堆块需要 release
3. **变量捕获机制**:
- 基本类型:按值捕获,在 block 内部是副本
- 对象类型:按指针捕获,强引用(需要 weak 避免循环引用)
- __block:允许在 block 内修改变量,变量会被包装成结构体,copy 时移动到堆
4. **copy 操作**:
- ARC 下:block 作为返回值、赋值给 strong 属性、作为参数传递时自动 copy
- MRC 下:需要手动调用 Block_copy,使用完后 Block_release
- 规则:栈块需要 copy 到堆才能安全使用
5. **循环引用**:
- 形成:block 捕获 self,self 持有 block,形成循环
- 解决:使用 __weak 捕获 self,在 block 内使用 strongSelf 保证执行期间不被释放
- weak-strong dance:先 weak 避免循环,再 strong 保证执行期间存活
6. **性能对比**:
- Block:有内存开销(捕获变量),有运行时开销(函数调用)
- 函数指针:无内存开销,直接调用,性能更好
- 优化:避免不必要的 block,使用内联函数替代简单 block
7. **线程安全**:
- Block 本身线程安全,但捕获的变量需要同步保护
- 避免问题:使用不可变对象,或使用锁保护共享变量
- 注意:__block 变量在多线程环境下需要同步
8. **序列化**:
- Block 不能直接序列化(包含函数指针)
- 持久化:将 block 的逻辑转换为数据,使用时重新创建
- 调试:使用 Instruments 的 Allocations 工具查看 block 的内存分配
38. RunLoop 深度理解
- 作用:事件循环,调度输入源(Port/Source)、定时器、观察者,保持线程存活。
- 模式:常用 `kCFRunLoopDefaultMode`、`UITrackingRunLoopMode`、`common modes`。不同模式隔离事件,避免滚动时阻塞定时器。
- 组件:Source0(App 触发)、Source1(内核)、Timer、Observer(入口/即将休眠/唤醒/退出)。AutoreleasePool 通过 Observer 在 RunLoop 切换时创建/释放。
- 主线程 RunLoop 自动启动;子线程需手动添加源并运行。
### 🔍 【深入问题】
- RunLoop 的事件循环机制?如何实现事件的分发和处理?RunLoop 的休眠与唤醒?
- RunLoop Mode 的作用?为什么需要不同的 Mode?如何自定义 RunLoop Mode?
- Source0 与 Source1 的区别?如何创建自定义的 Source?Source 的优先级?
- Timer 在 RunLoop 中的调度机制?为什么滚动时 Timer 会暂停?如何让 Timer 在滚动时继续工作?
- Observer 的使用场景?如何监听 RunLoop 的状态变化?Observer 的性能影响?
- RunLoop 与 AutoreleasePool 的协作机制?AutoreleasePool 的创建和释放时机?
- 如何实现常驻线程?常驻线程的应用场景?常驻线程的生命周期管理?
- RunLoop 的性能优化?如何避免 RunLoop 阻塞?如何提高 RunLoop 的响应速度?
- RunLoop 与主线程卡顿的关系?如何通过 RunLoop 监控主线程性能?
### ✅ 【答案与解释】
1. **事件循环机制**:
- 流程:检查 Source → 处理 Timer → 处理 Source0 → 处理 Source1 → 休眠等待
- 分发:按优先级处理事件,Source1(内核事件)优先于 Source0(应用事件)
- 休眠:无事件时进入休眠,由内核唤醒(Source1 事件或 Timer 到期)
2. **RunLoop Mode**:
- 作用:隔离不同场景的事件,避免相互影响
- 常用 Mode:Default(默认)、Tracking(滚动时)、Common(通用)
- 自定义:使用 CFRunLoopAddCommonMode 添加自定义 Mode
3. **Source0 vs Source1**:
- Source0:应用层事件,需要手动标记为待处理
- Source1:内核事件(端口、信号),自动唤醒 RunLoop
- 创建:使用 CFRunLoopSourceCreate 创建,添加到 RunLoop
- 优先级:Source1 优先于 Source0
4. **Timer 调度**:
- 机制:Timer 添加到 RunLoop,按时间间隔触发
- 滚动暂停:UITrackingRunLoopMode 下,Default Mode 的 Timer 暂停
- 解决:将 Timer 添加到 Common Modes,或使用 CADisplayLink
5. **Observer 使用**:
- 场景:监听 RunLoop 状态变化,实现性能监控、AutoreleasePool 管理
- 监听:使用 CFRunLoopObserverCreate 创建 Observer
- 性能影响:Observer 回调应尽量轻量,避免阻塞 RunLoop
6. **AutoreleasePool 协作**:
- 机制:RunLoop 进入时创建 pool,退出时释放 pool
- 时机:通过 Observer 监听 RunLoop 状态,自动管理 pool
- 实现:系统在 RunLoop 的进入和退出时自动创建和释放 pool
7. **常驻线程**:
- 实现:创建线程,添加 Source(如 Port),运行 RunLoop
- 应用场景:后台任务处理、网络请求、文件操作
- 生命周期:线程创建后运行 RunLoop,退出时停止 RunLoop
8. **性能优化**:
- 避免阻塞:耗时操作放到后台线程,主线程只处理 UI
- 提高响应:减少主线程任务,优化事件处理逻辑
- 监控:使用 Instruments 的 Time Profiler 监控 RunLoop 性能
9. **主线程卡顿**:
- 关系:主线程 RunLoop 阻塞会导致 UI 卡顿
- 监控:使用 CFRunLoopObserver 监控 RunLoop 状态,检测卡顿
- 优化:减少主线程任务,使用异步处理,优化事件处理逻辑
39. Runtime 常用方法
- 消息派发:`objc_msgSend`。
- 类与对象:`objc_getClass`、`object_getClass`、`class_copyIvarList`、`class_copyPropertyList`、`class_copyMethodList`。
- 方法操作:`class_addMethod`、`method_exchangeImplementations`(swizzling)、`method_setImplementation`。
- 关联对象:`objc_setAssociatedObject`/`objc_getAssociatedObject`。
- 动态解析与转发:`resolveInstanceMethod`、`forwardingTargetForSelector`、`forwardInvocation`。
40. 静态库与动态库的区别
- 静态库(.a/.lib):编译期链接,符号复制到可执行文件,体积增大,运行时无额外加载;更新需重新发布宿主。
- 动态库(.dylib/.framework,iOS 上主要是 .framework 或系统动态库):运行时加载/共享,节省体积,支持独立更新(系统/插件);但加载和符号绑定有启动开销。iOS 第三方动态库需满足平台签名与嵌入规则。
### 🔍 【深入问题】
- 如何选择静态库和动态库?各自的优缺点?如何优化库的体积?
- 动态库的加载过程?如何优化动态库加载?如何减少符号绑定时间?
- 如何实现库的版本管理?如何实现库的兼容性?如何实现库的升级?
- 如何实现库的符号隐藏?如何实现库的接口设计?如何实现库的测试?
- 如何实现库的依赖管理?如何避免循环依赖?如何实现库的模块化?
### ✅ 【答案与解释】
1. **静态库 vs 动态库选择**:
- 静态库:编译期链接,体积大,启动快,适合小库、工具库
- 动态库:运行时加载,体积小,启动慢,适合大库、共享库
- 优化体积:移除未使用代码,使用链接时优化(LTO),压缩资源
- 选择:根据库大小、使用频率、更新需求选择,小库用静态,大库用动态
2. **动态库加载优化**:
- 加载过程:dyld 加载库文件 → 符号绑定 → 初始化 → 可用
- 优化加载:减少动态库数量,合并相关库,使用静态链接减少符号
- 减少绑定:使用 -bind_at_load 减少延迟绑定,优化符号导出
- 实现:分析库依赖,合并相关库,优化符号导出,减少加载时间
3. **库版本管理**:
- 版本管理:使用语义化版本(Major.Minor.Patch),管理库版本
- 兼容性:保持 API 向后兼容,使用版本号标识不兼容变更
- 升级策略:支持多版本共存,逐步迁移,提供迁移指南
- 实现:使用版本号管理,建立兼容性策略,实现版本升级机制
4. **库接口设计**:
- 符号隐藏:使用 -fvisibility=hidden 隐藏内部符号,只导出公共接口
- 接口设计:定义清晰的公共 API,隐藏实现细节,使用协议抽象
- 测试:为库编写单元测试,使用 Mock 对象,建立测试覆盖
- 实现:设计清晰的接口,隐藏实现,建立测试体系
5. **库依赖管理**:
- 依赖管理:使用 CocoaPods、Carthage、SPM 管理依赖,明确依赖关系
- 避免循环依赖:设计清晰的依赖层次,使用依赖注入,避免循环引用
- 模块化:将库拆分为独立模块,定义模块接口,实现模块化架构
- 实现:建立依赖管理机制,设计模块化架构,避免循环依赖
41. APNS 发送系统消息机制概览
- 客户端向 APNS 注册获取 device token;App 将 token 发送到业务服务器。
- 服务器使用 token + 证书/密钥(HTTP/2 APNS 接口)构造推送请求 → APNS 路由到设备 → 系统唤醒/展示通知;前台/后台由通知策略和权限决定。
- iOS 端通过 `UNUserNotificationCenter` 接收与处理,支持静默推送(content-available=1)和可视通知。
### 🔍 【深入问题】
- APNS 的推送流程?如何实现推送的可靠性?如何实现推送的优先级?
- 如何实现推送的统计?如何实现推送的点击率?如何优化推送效果?
- 如何实现推送的个性化?如何实现推送的定时发送?如何实现推送的批量发送?
- 如何实现推送的加密?如何保护推送内容?如何实现推送的权限控制?
- 如何实现推送的降级?如何实现推送的失败重试?如何实现推送的离线存储?
### ✅ 【答案与解释】
1. **APNS 推送流程**:
- 流程:客户端注册 → 获取 token → 发送到服务器 → 服务器构造推送 → APNS 路由 → 设备接收
- 可靠性:使用 HTTP/2 的流控制,支持推送确认,实现推送回执
- 优先级:使用 apns-priority 头(10 立即,5 省电),根据场景选择优先级
- 实现:建立推送服务,实现推送确认机制,支持优先级设置
2. **推送统计与优化**:
- 统计:记录推送发送、到达、点击等数据,分析推送效果
- 点击率:跟踪用户点击行为,分析推送内容效果,优化推送策略
- 优化效果:A/B 测试推送内容,优化推送时机,个性化推送内容
- 实现:建立推送统计系统,跟踪推送效果,持续优化推送策略
3. **推送个性化与定时**:
- 个性化:根据用户画像、行为数据,推送个性化内容
- 定时发送:使用本地通知实现定时推送,或服务器端定时发送
- 批量发送:使用推送服务批量接口,实现批量推送,优化推送效率
- 实现:建立用户画像系统,实现定时推送机制,优化批量推送
4. **推送安全**:
- 加密:推送内容使用端到端加密,保护用户隐私
- 权限控制:控制推送权限,支持用户关闭推送,实现推送权限管理
- 内容保护:敏感内容加密传输,使用安全通道,保护推送内容
- 实现:实现推送加密机制,建立权限管理系统,保护推送安全
5. **推送降级与重试**:
- 降级:推送失败时使用短信、邮件等替代方案,实现推送降级
- 失败重试:实现推送重试机制,指数退避策略,限制重试次数
- 离线存储:推送失败时存储到本地,网络恢复时重试,实现离线推送
- 实现:建立推送降级机制,实现重试逻辑,支持离线推送存储
42. GCD 与 NSOperation 区别
- 抽象层:GCD 为 C API,轻量、队列+Block;NSOperation 封装面向对象,可组合、依赖、取消、KVO。
- 功能:NSOperation 支持依赖关系、优先级、取消、并发控制(maxConcurrentOperationCount)、队列暂停;GCD 更底层、性能优、语法简洁。
- 选择:简单异步用 GCD;需要依赖、可取消、易测、可复用场景用 NSOperation。
43. __block 与 __weak 区别
- `__block`:允许在 block 内修改被捕获的变量;对象类型在 ARC 下默认强引用,被 move 到堆;不自动置 nil。
- `__weak`:弱引用,不增加引用计数,被释放后自动置 nil;常与 block 捕获 self 配合避免循环引用。
44. iOS 事件产生与传递
- 触摸事件由硬件 → IOKit → SpringBoard → App 进程 → UIApplication → UIWindow → hitTest(从最顶层视图递归寻找可接收者)→ 目标 UIView。
- 传递链:`pointInside:` → `hitTest:withEvent:` 确定响应视图;事件再沿响应链(responder chain)向上(view → superview → controller → window → app)传递,未处理则丢弃。
### 🔍 【深入问题】
- hitTest 的实现原理?如何优化 hitTest 性能?如何自定义 hitTest 行为?
- 响应链的传递机制?如何实现事件拦截?如何实现事件转发?
- 如何实现自定义手势?如何实现手势识别?如何优化手势响应?
- 如何实现事件优先级?如何实现事件合并?如何实现事件取消?
- 如何实现多点触控?如何实现手势冲突处理?如何实现手势组合?
### ✅ 【答案与解释】
1. **hitTest 实现原理**:
- 原理:从最顶层视图开始,递归调用 hitTest,使用 pointInside 判断点是否在视图内
- 性能优化:减少视图层级,避免复杂 hitTest 计算,使用缓存结果
- 自定义行为:重写 hitTest 方法,自定义响应区域,实现特殊响应逻辑
- 实现:优化视图层级,简化 hitTest 计算,使用自定义 hitTest 扩展响应区域
2. **响应链传递**:
- 传递机制:事件沿响应链向上传递,每个响应者可以处理或继续传递
- 事件拦截:在响应者方法中处理事件,返回 YES 停止传递
- 事件转发:调用 nextResponder 继续传递,或手动转发到特定响应者
- 实现:实现响应者方法,控制事件传递,实现事件拦截和转发
3. **自定义手势**:
- 实现:继承 UIGestureRecognizer,实现触摸事件处理逻辑
- 手势识别:分析触摸轨迹,识别手势模式,设置手势状态
- 性能优化:减少识别计算,使用状态机优化识别逻辑,缓存识别结果
- 实现:定义手势状态,实现触摸事件处理,优化识别算法
4. **事件优先级与合并**:
- 优先级:使用手势的优先级属性,或使用响应链顺序控制优先级
- 事件合并:合并相似事件,减少事件处理次数,提高性能
- 事件取消:调用 cancel 方法取消事件,或设置手势状态为 cancelled
- 实现:建立事件优先级系统,实现事件合并逻辑,支持事件取消
5. **多点触控与手势组合**:
- 多点触控:使用 UITouch 的多个触摸点,实现多点触控手势
- 冲突处理:使用手势代理方法,控制手势同时识别,处理手势冲突
- 手势组合:使用手势的 requireGestureRecognizerToFail 建立依赖关系
- 实现:处理多个触摸点,实现手势冲突解决,建立手势依赖关系
45. 对 weak 的理解
- 弱引用不增加引用计数,目标释放时自动置 nil(弱引用表由 runtime 维护)。避免循环引用(delegate、block)、避免野指针。读取 weak 有竞态风险,需在多线程下用强引用保护。
46. 离屏渲染及触发场景
- 离屏渲染:GPU 将绘制目标转移到新的缓冲区再合成,增加开销(上下文切换/额外内存)。
- 触发:圆角+maskToBounds+阴影组合、使用 shouldRasterize、group opacity、复杂阴影、图层蒙版、某些混合模式/模糊、渐变+圆角叠加、文本描边等。优化:使用 `shadowPath`、预裁剪圆角图、避免不必要的 mask。
### 🔍 【深入问题】
- 离屏渲染的检测方法?如何定位离屏渲染?如何优化离屏渲染?
- 如何实现圆角优化?如何实现阴影优化?如何实现模糊优化?
- shouldRasterize 的使用场景?如何优化 shouldRasterize?如何避免缓存失效?
- 如何实现预渲染?如何实现图片预裁剪?如何优化图片渲染?
- 如何实现渲染性能监控?如何建立渲染性能基准?如何持续优化渲染性能?
### ✅ 【答案与解释】
1. **离屏渲染检测**:
- 检测方法:使用 Instruments 的 Core Animation 工具,查看离屏渲染标记
- 定位:使用 Xcode 的 Color Offscreen-Rendered 选项,标记离屏渲染视图
- 优化:避免触发离屏渲染的属性组合,使用替代方案,减少离屏渲染
- 实现:使用工具检测,分析触发原因,应用优化方案
2. **圆角阴影优化**:
- 圆角优化:使用预裁剪图片,使用 CAShapeLayer,避免 maskToBounds
- 阴影优化:使用 shadowPath 指定阴影路径,避免自动计算,减少离屏渲染
- 模糊优化:使用预渲染图片,使用 Core Image 滤镜,避免实时模糊
- 实现:预处理图片,使用优化方案,减少实时计算
3. **shouldRasterize 优化**:
- 使用场景:复杂视图、动画视图、需要缓存的视图
- 优化:合理设置缓存大小,及时清理缓存,避免过度使用
- 避免失效:避免频繁修改视图属性,保持视图稳定,合理设置缓存策略
- 实现:分析视图复杂度,合理使用缓存,优化缓存策略
4. **预渲染与图片优化**:
- 预渲染:在后台线程预处理图片,缓存渲染结果,减少主线程渲染
- 图片预裁剪:使用 Core Graphics 预裁剪圆角,缓存裁剪结果
- 渲染优化:使用异步渲染,使用图片缓存,优化图片加载
- 实现:建立图片处理队列,实现预渲染机制,优化图片加载流程
5. **渲染性能监控**:
- 监控:使用 Instruments 监控帧率,使用 CADisplayLink 监控渲染性能
- 建立基准:定义性能指标,建立性能基准线,持续监控
- 持续优化:分析性能瓶颈,应用优化方案,持续改进
- 实现:集成性能监控,建立性能看板,定期优化渲染性能
47. weak-strong dance 原因
- block 捕获 self 为 weak 防止循环引用;在执行时可能已释放,需要在 block 内 `strongSelf = weakSelf` 形成短暂强引用,保证后续使用期间对象不被释放,同时避免 retain cycle。
48. Autoreleasepool 机制
- 线程的 RunLoop 在入口创建 autoreleasepool,出 RunLoop 迭代时释放;手动 `@autoreleasepool{}` 创建嵌套池,在作用域结束时释放池中对象。
- 数据结构:以栈形式维护多个 pool,内部是 AutoreleasePoolPage(双向链表页面),存储指针顺序推入。
- 哨兵对象:每个 pool push 一个哨兵标记(POOL_BOUNDARY),在 drain 时从栈顶回溯到哨兵,依序发送 release;用于界定释放范围并支持嵌套。
A1. ARC 下常见循环引用解除
- block 捕获:使用 `__weak` / `__unsafe_unretained` 捕获 self,再在内部 strong 化;或将回调设为外部注入。
- NSTimer / CADisplayLink:使用中介对象(proxy)、block 版 API,或在 dealloc/invalidate 及时解除。
- 代理:使用 weak(或 assign 对纯 C 指针),避免强持有。
- 容器:注意 NSMutableArray/Dictionary 强持有,必要时用 NSPointerArray/NSMapTable 的 weak 语义。
A2. 线程安全策略
- 数据隔离:尽量使用串行队列封装状态访问,而非到处加锁。
- 锁选择:短临界区用 os_unfair_lock,读多写少用 barrier/rwlock;需要可重入用 recursive mutex 或 @synchronized。
- 不可变性:多线程共享尽量传递不可变快照(immutable),减少同步成本。
A3. 性能与工具
- CPU/时间:Instruments Time Profiler、signpost 跟踪关键路径。
- 内存:Leaks、Allocations、Memory Graph;大循环加 @autoreleasepool,图片解码放后台。
- 启动:dyld_shared_cache 预热、精简 +load、合并动态库,首屏资源本地化。
A4. 网络与安全
- HTTPS/TLS:证书校验、ATS 要求,敏感信息避免明文存储;Keychain 存储凭据。
- 请求优化:HTTP/2 多路复用,压缩、缓存(ETag/If-None-Match),合并请求,降级与重试策略。
A5. 数据持久化选择
- 轻量键值:UserDefaults(非敏感、小数据);敏感用 Keychain。
- 结构化:Core Data/SQLite/Realm,根据事务与查询需求选择;注意后台保存、合并策略、批量操作。
A6. UI 与适配
- Auto Layout + Size Class 适配多尺寸与横竖屏;注意 safe area、异形屏。
- 暗黑模式:使用动态颜色/图片(Assets 中 Any/Dark);避免硬编码颜色。
- 可访问性:VoiceOver、Dynamic Type,自适应字体与对比度。
### 🔍 【深入问题】
- Auto Layout 的性能优化?如何减少约束冲突?约束的优先级机制?
- 如何实现响应式布局?Size Class 的使用场景?如何适配 iPad 和 iPhone?
- Safe Area 的处理策略?如何适配刘海屏和底部安全区域?如何实现全屏显示?
- 暗黑模式的实现原理?如何动态切换主题?如何测试暗黑模式?
- 可访问性的实现要点?如何支持 VoiceOver?如何适配动态字体?
- UI 渲染性能优化?如何减少离屏渲染?如何优化列表滚动性能?
- 动画性能优化?如何实现流畅的动画?如何避免动画卡顿?
- 自定义视图的最佳实践?如何实现高性能的自定义控件?
### ✅ 【答案与解释】
1. **Auto Layout 性能优化**:
- 性能优化:减少约束数量,使用 intrinsicContentSize,避免复杂约束
- 减少冲突:使用优先级解决冲突,合理设计约束,避免循环依赖
- 优先级机制:Required(1000)、High(750)、Medium(500)、Low(250)
- 实现:分析约束复杂度,优化约束设计,使用优先级解决冲突
2. **响应式布局**:
- 实现:使用 Auto Layout 和 Size Class,根据设备尺寸调整布局
- Size Class:Regular/Compact 组合,适配不同设备尺寸和方向
- iPad/iPhone 适配:使用 Size Class 区分,使用条件布局,适配不同设备
- 实现:设计响应式布局,使用 Size Class 适配,测试不同设备
3. **Safe Area 处理**:
- 处理策略:使用 Safe Area Layout Guide,适配刘海屏和底部安全区域
- 适配:使用 topAnchor/bottomAnchor 约束到 Safe Area,避免内容被遮挡
- 全屏显示:使用 edgesForExtendedLayout 控制扩展,使用 prefersStatusBarHidden 隐藏状态栏
- 实现:使用 Safe Area 约束,适配异形屏,实现全屏显示
4. **暗黑模式实现**:
- 实现原理:使用动态颜色和图片,系统自动切换,支持 Any/Dark 模式
- 动态切换:监听 traitCollection 变化,更新 UI,支持实时切换
- 测试:使用 Xcode 的 Appearance 选项测试,使用模拟器测试不同模式
- 实现:使用动态颜色,支持暗黑模式,测试不同外观
5. **可访问性实现**:
- 实现要点:设置 accessibilityLabel、accessibilityHint,支持 VoiceOver
- VoiceOver:实现 accessibility 属性,支持语音导航,测试可访问性
- 动态字体:使用 UIFontMetrics 适配动态字体,支持字体大小调整
- 实现:设置可访问性属性,支持 VoiceOver,适配动态字体
6. **UI 渲染性能**:
- 性能优化:减少视图层级,使用异步渲染,优化布局计算
- 减少离屏渲染:避免触发离屏渲染的属性,使用优化方案
- 列表滚动:使用 cell 复用,异步加载图片,减少主线程任务
- 实现:优化视图结构,减少渲染任务,优化列表性能
7. **动画性能优化**:
- 流畅动画:使用 GPU 友好属性,避免主线程阻塞,优化动画计算
- 避免卡顿:减少动画复杂度,使用异步动画,优化动画性能
- 实现:使用 Core Animation,优化动画属性,监控动画性能
8. **自定义视图实践**:
- 最佳实践:使用 drawRect 绘制,使用 CALayer 优化,支持异步渲染
- 高性能控件:减少重绘,使用缓存,优化绘制逻辑
- 实现:设计高效绘制逻辑,使用缓存机制,优化自定义控件性能
---
## 🚀 Swift 常见面试要点
1. Swift 与 Objective-C 的联系与区别,以及优势
- 联系:同属 Apple 生态,可混编;共享框架(Foundation/UIKit/SwiftUI)、同一运行时(Swift 对 OC runtime 进行桥接);可互调(@objc 暴露给 OC)。
- 区别与优势:静态强类型+类型推断,编译期更多检查;值类型占主导(struct/enum)+copy-on-write,减少共享状态;安全特性(可选类型、自动初始化、错误处理、越界检查);现代语法(泛型、协议扩展、闭包表达式、模式匹配);性能更接近 C++(去掉消息发送开销)。
2. Swift 独有语法与细节差异
- 可选类型 `?`/`!`、可选链、空合并 `??`;guard 早退出。
- 值语义的 struct/enum + 关联值枚举;模式匹配 `switch` 支持区间/where。
- 闭包尾随、map/filter/reduce、`try/throws` 错误处理;defer 延后执行。
- 属性包装器、协议扩展、泛型 where 约束、Result/async-await(取代回调)。
- `let`/`var` 明确常量与变量;没有头文件,模块化导入。
3. 面向对象还是函数式
- 多范式:支持面向对象(class/继承/协议)、函数式(高阶函数/不可变/值语义)、协议导向(protocol + extension)。常用 POD(protocol-oriented design)强调组合胜过继承。
4. 访问控制关键字比较
- `open`:跨模块可访问且可继承/重写(最高开放)。
- `public`:跨模块可访问,但不可被外部继承/重写。
- `internal`:默认级别,模块内可见。
- `fileprivate`:仅当前文件可见。
- `private`:仅当前作用域/类型内可见(扩展同文件也受限)。
5. 引用类型的持有语义 strong / weak / unowned
- strong:默认,增加引用计数。
- weak:不增加引用计数,被释放自动置 nil,仅用于可选类型。
- unowned:不增加引用计数,不自动置 nil,生命周期需保证存在;用于避免循环引用且对象应长于持有者(如闭包对 self 的非可选引用在初始化后)。
6. 阻止方法/属性/下标被子类改写
- 使用 `final` 关键字修饰 class/func/var/subscript,阻止继承或重写;或将类声明为 `final class`。
7. guard 与 defer 用法
- `guard 条件 else { 提前返回 }`:提前退出,提升主路径可读性,guard 后绑定的变量在后续作用域可用。
- `defer { ... }`:当前作用域退出前必执行,先进后出,常用于资源释放、状态恢复、锁解锁。
8. struct 与 class 的区别
- 语义:struct/enum 为值类型,赋值/传参复制(大对象通常 COW 优化);class 为引用类型,需考虑 ARC。
- 继承:struct 不支持继承(可协议实现),class 支持继承与运行时特性(@objc)。
- ARC:class 需要;struct 不需要。
- 动态特性:class 可用 KVC/KVO(需 @objc dynamic),方法派发可 dynamic;struct 静态派发。
9. 以 struct 作为数据模型的优缺点
- 优点:值语义降低共享状态 bug,线程安全性更好;拷贝时 COW,性能可接受;更易与 SwiftUI/Combine 协作(状态驱动)。
- 缺点:需要大对象写入时的拷贝成本(尽管有 COW);无法使用继承;与依赖 OC runtime 的特性(KVC/KVO)互操作有限。
10. Swift 高阶函数
- 常见:`map`(映射)、`compactMap`(映射并剔除 nil)、`flatMap`(扁平化)、`filter`(过滤)、`reduce`(聚合)、`forEach`(遍历)、`sorted`/`sorted(by:)`(排序)、`prefix`/`suffix`、`drop` 系列、`contains`/`allSatisfy`/`first(where:)`、`zip`。
- 使用场景:数据转换、过滤、累加、查找;避免显式 for 循环,提高可读性和声明式风格。
11. Any、AnyHashable、AnyObject、AnyClass 区别
- Any:可以表示任意类型(值/引用),包括函数;使用需显式转换/匹配。
- AnyObject:任意引用类型(class 实例);常用于桥接 OC 或弱引用集合(`NSHashTable<AnyObject>`)。
- AnyClass:`AnyObject.Type` 的别名,表示任意类类型(元类型)。
- AnyHashable:包装任意符合 Hashable 的值,便于在同一集合中存放不同 Hashable 类型。
12. 枚举高级用法
- 关联值(payload):`enum Result { case success(Data), failure(Error) }`。
- 原始值(rawValue):`enum HTTP: Int { case ok = 200 }`。
- 模式匹配/解构:`if case .success(let data) = result {}`。
- 递归枚举:`indirect enum List { case value(Int, List) }`。
- CaseIterable 自动生成 allCases;CustomStringConvertible;枚举作为命名空间;可添加计算属性、方法、协议扩展。
13. 线程安全实现
- 串行队列封装共享状态访问(推荐),或使用 `DispatchQueue(label:..., attributes: .concurrent)` + barrier。
- 锁:`NSLock`/`os_unfair_lock`/`pthread_mutex`;读写锁 `DispatchQueue` barrier 或 `pthread_rwlock`。
- 不可变性:使用值类型、不可变快照减少共享可变状态。
- Actor(Swift 并发):`actor` 提供隔离的可变状态(Swift 5.5+)。
14. 泛型编程及好处
- 语法:`func swap<T>(_ a: inout T, _ b: inout T)`,`struct Stack<Element> { ... }`,`where` 约束。
- 好处:类型安全、复用、性能(编译期特化)、可读性高;配合协议(PAT)可表达抽象行为。
15. 闭包实现异步回调
- 通过函数参数传递闭包:`func fetch(completion: @escaping (Result<Data,Error>) -> Void)`;在异步完成后调用。
- 注意:标记 @escaping,避免循环引用([weak self]),必要时切回主线程 `DispatchQueue.main.async`.
16. 内存泄漏与避免
- 泄漏来源:循环引用(闭包/捕获 self、delegate 强持有)、定时器/通知未移除、非托管资源。
- 解决:weak/unowned 断环;[weak self] + strong dance;定时器用弱代理或 block 版并在 deinit 停止;结合 Instruments/Memory Graph 排查。
17. 动态派发
- Swift 对 class 默认使用动态派发(vtable/msgSend 混合),`dynamic`/`@objc` 强制走 Objective-C 消息派发(支持 KVC/KVO/响应链)。
- 值类型和 final 方法多为静态/直接派发;协议可选择 witness table 派发。派发方式影响性能与 runtime 特性。
18. 模式匹配优化逻辑
- `switch` 支持模式:值匹配、区间、元组、where 条件、类型匹配(`is`/`as`)。
- `if case` / `guard case` 提取枚举关联值;`for case let` 过滤序列中的某模式。
- 使用枚举 + 模式匹配集中处理分支,减少嵌套 if/else。
19. 枚举驱动状态/事件
- 将状态建模为枚举(含关联值):网络状态、视图状态、State Machine。
- 配合 `switch` + 关联值提取上下文;结合 Result/AsyncSequence/Combine 时作为事件载体。
- 使用不可达默认分支(不写 default)让编译器提醒新增 case。
20. 编译时多态 vs 运行时多态
- 编译时:函数重载、泛型特化、内联、运算符重载;类型在编译期决定,派发静态。
- 运行时:协议类型/存在类型(witness table)、类继承的动态派发、@objc/dynamic 的消息发送。编译时多态性能高;运行时多态更灵活可扩展。
21. 访问级别对 API 设计与模块结构的影响
- 通过 `open/public/internal/fileprivate/private` 控制暴露面:对外 SDK 只开放必要 API,隐藏实现细节;模块内用 internal 默认即可。
- 将类型与实现拆分:公共协议/模型放公开层,实现细节放内部或文件私有;减少破坏性变更风险,提升二进制兼容性。
22. 协议与委托模式
- 协议定义行为契约:`protocol MyDelegate: AnyObject { func didFinish(...) }`。
- 委托模式:持有 `weak var delegate: MyDelegate?`,在事件点回调;解耦调用方与实现方,便于测试与替换。
23. 属性包装器
- 通过 `@propertyWrapper` 抽象 get/set 逻辑,复用横切关注点。
- 场景:UserDefaults 持久化(`@UserDefault("key", defaultValue: 0)`)、懒加载缓存、输入校验、数值裁剪、去抖/节流状态、主线程发布(@MainActor)、结合 Combine/SwiftUI 的 @Published/@State。
24. async/await 模式
- Swift 并发(5.5+):`async` 标记异步函数,`await` 暂停等待结果,形成结构化并发,避免回调地狱。
- 解决:回调嵌套、错误传播困难、线程切换显式性差;配合 `Task`、`TaskGroup`、`AsyncSequence`,与 GCD/NSOperation 互操作。
25. 协议实现多态
- 面向接口编程:定义协议作为抽象层,具体类型实现协议。
- 通过协议类型变量/参数接收不同实现,实现运行时多态(witness table 派发);可结合协议扩展提供默认实现,减少重复。
26. 动态库与静态库(Swift 背景)
- 静态库:编译期打包进可执行,体积增大,运行时无额外加载;版本更新需重新分发宿主。
- 动态库:运行时加载/共享,节省体积并可独立更新(系统/插件);但启动时有加载/符号绑定开销,iOS 需签名与嵌入处理。Swift ABI 稳定后(iOS 12+/Swift 5)才支持更好的二进制分发。
Swift 补充要点
- ARC 与值/引用语义:class 受 ARC 管理,struct/enum 为值语义且常用 COW(Array/String/Dictionary)避免无谓拷贝;自定义 COW 可通过 `isKnownUniquelyReferenced`.
- 线程安全与并发:Swift Concurrency 的 `actor`、`@MainActor` 保障主线程隔离;`Sendable` 约束跨线程安全传递;`Task`/`TaskGroup`/`AsyncSequence` 组合并发,注意取消传播(`Task.checkCancellation()`)。
- 错误处理:`throws/try/try?`,Result<T,Error> 作为值语义替代;async/await 与 throws 组合支持结构化错误传播。
- 类型擦除与协议:对含 `Self` 或关联类型的协议,使用类型擦除(AnySequence/AnyPublisher)或封装泛型;必要时使用 PAT + where 约束。
- Codable 与数据映射:`CodingKeys` 自定义键映射,`JSONDecoder`/`Encoder` 性能与灵活性兼顾;大模型解析可分层解码或流式解码。
- 属性特性:`lazy` 延迟初始化;属性观察 `willSet/didSet`;计算属性区分 get/set;避免在 property observer 中递归修改自身。
- Swift/OC 互操作:使用 `@objc`/`dynamic` 暴露给 OC 或启用 KVC/KVO;bridging header 导入 OC,`NS_SWIFT_NAME` 优化接口命名;对非可空 OC API 使用 `NS_ASSUME_NONNULL_BEGIN`.
### 🔍 【深入问题】
- COW 的实现原理?如何判断是否需要拷贝?COW 的性能优化策略?
- Actor 的隔离机制?如何避免数据竞争?Actor 与锁的区别?
- @MainActor 的使用场景?如何确保 UI 操作在主线程?@MainActor 的性能影响?
- Sendable 协议的作用?如何让自定义类型符合 Sendable?Sendable 的编译检查?
- async/await 的底层实现?如何与 GCD 互操作?async/await 的性能优势?
- 类型擦除的实现方式?AnySequence 的实现原理?何时使用类型擦除?
- Codable 的性能优化?如何实现自定义编解码?大数据的流式处理?
- 属性包装器的实现原理?如何创建自定义属性包装器?属性包装器的应用场景?
### ✅ 【答案与解释】
1. **COW 实现原理**:
- 原理:值类型内部使用引用计数,多个副本共享同一块内存,写入时才拷贝
- 判断拷贝:使用 isKnownUniquelyReferenced 检查引用计数,为 1 时直接修改,否则拷贝
- 性能优化:减少不必要的拷贝,使用 inout 参数避免拷贝,优化大对象的 COW
- 实现:Array、String、Dictionary 等标准库类型都使用 COW 优化
2. **Actor 隔离机制**:
- 隔离:Actor 内部状态只能通过 Actor 方法访问,编译器保证线程安全
- 避免竞争:使用 await 调用 Actor 方法,系统自动序列化访问,避免数据竞争
- 与锁区别:Actor 是语言级别的并发安全,锁是库级别的同步机制
- 实现:定义 actor 类型,将共享状态封装在 actor 内,使用 await 访问
3. **@MainActor 使用**:
- 使用场景:UI 相关代码、主线程操作、需要主线程隔离的场景
- 确保主线程:@MainActor 标记的代码自动在主线程执行,编译器保证
- 性能影响:有调度开销,但保证线程安全,适合 UI 操作
- 实现:使用 @MainActor 标记类或方法,系统自动调度到主线程
4. **Sendable 协议**:
- 作用:标记可以在并发环境安全传递的类型,编译器检查并发安全
- 符合 Sendable:值类型自动符合,引用类型需要手动实现,使用 @unchecked Sendable
- 编译检查:编译器检查 Sendable 类型的使用,防止并发安全问题
- 实现:让自定义类型符合 Sendable,使用 @Sendable 标记闭包
5. **async/await 实现**:
- 底层实现:使用 Continuation 和 Task 实现,编译器转换为状态机
- 与 GCD 互操作:使用 withCheckedContinuation 桥接 GCD,使用 Task 包装 GCD 任务
- 性能优势:结构化并发,减少回调嵌套,更好的错误处理,编译器优化
- 实现:使用 async/await 替代回调,使用 Task 管理并发,使用 TaskGroup 组合任务
6. **类型擦除**:
- 实现方式:使用 AnySequence、AnyPublisher 等类型擦除包装器
- AnySequence 原理:封装具体序列类型,隐藏具体类型,提供统一接口
- 使用场景:需要隐藏具体类型,统一接口,协议中不能使用关联类型时
- 实现:定义类型擦除包装器,封装具体类型,提供统一接口
7. **Codable 性能优化**:
- 性能优化:使用自定义编解码,减少反射,使用流式处理大数据
- 自定义编解码:实现 CodingKeys,自定义 encode/decode 方法,优化编解码逻辑
- 流式处理:使用 JSONDecoder 的流式 API,分批处理大数据,减少内存占用
- 实现:分析编解码性能,优化编解码逻辑,使用流式处理大数据
8. **属性包装器**:
- 实现原理:使用 @propertyWrapper 标记,实现 wrappedValue 和 projectedValue
- 自定义包装器:定义包装器类型,实现 wrappedValue,可选实现 projectedValue
- 应用场景:UserDefaults 持久化、懒加载、输入验证、主线程发布等
- 实现:定义属性包装器,封装通用逻辑,应用到属性上
Swift 泛型的作用与能力
- 定义:对类型参数化的代码模板,`func foo<T>(...)`, `struct Box<T> {}`, `where` 约束限定能力。
- 作用:提升复用与类型安全(避免 Any/强转),编译期特化带来性能;抽象算法与容器(Array/Dictionary/Result/Optional 都是泛型)。
- 能力:泛型函数/类型/下标/协议(PAT)、associatedtype + where 约束、类型推断、约束组合(`T: Hashable & Sendable`),配合类型擦除在需要隐藏具体类型时使用。
---
## 🎨 Flutter 常见面试要点
1. Dart 是什么,与 Flutter 的关系
- Dart:Google 开发的客户端语言,支持 AOT(原生)与 JIT(开发热重载),内置垃圾回收、异步/Isolate。
- Flutter:UI 框架,以 Dart 编写应用逻辑与构建 Widget 树;Dart AOT 编译为原生代码,JIT 用于开发热重载。
2. main() 与 runApp() 作用
- `main()`:应用入口,可做依赖注入、初始化(如 WidgetsFlutterBinding.ensureInitialized)。
- `runApp()`:将给定的根 Widget 挂到 Flutter 框架,启动渲染与事件分发;通常在 main 内调用。
3. Streams 概念与种类
- 概念:异步事件序列(多次值);可被监听。
- 种类:单订阅(Single-subscription)与广播(Broadcast)。
- 场景:用户输入流、网络推送、WebSocket、定时心跳、BLoC/Rx 状态流。
4. async / await
- Dart 异步语法糖:`async` 标记异步函数,返回 Future;`await` 等待 Future 完成并解包结果,避免回调地狱。
5. Future vs Stream
- Future:一次性异步结果(单值/错误)。
- Stream:多次异步事件序列(0..n 个值/错误/完成)。
6. Flutter 中的 Key
- 用途:标识 Widget,帮助框架在重建时正确复用/移动对应 Element/State,避免状态错位。
- 类型:ValueKey/ObjectKey/UniqueKey/GlobalKey。列表重排/动画/表单、跨树访问 State(谨慎用 GlobalKey)。
7. Profile mode 场景
- 介于 debug 与 release:关闭断言/调试开销,保留部分性能分析工具。
- 使用:性能调优(帧率、Jank、CPU/GPU 使用)接近真机表现但仍可观测。
8. 理解 Isolate
- Dart 的并发单元,每个 Isolate 有独立内存与事件循环,不共享堆,靠消息传递通信,避免锁竞争。
- Flutter UI 运行在主 Isolate;耗时计算可放后台 Isolate。
9. Dart 如何实现多任务并行
- 通过多个 Isolate 并行运行(可利用多核);消息传递(SendPort/ReceivePort)交换数据。
- Isolate 之外的微任务/事件循环在同一 Isolate 内是协作式并发(非并行)。
10. Dart 异步编程中的 Stream 数据流
- 特性:异步拉/推式事件,支持 `listen`、`await for` 消费;可有 `map/where/asyncMap` 等转换。
- 控制器:StreamController(单订阅/广播)、行为主题类封装(在 Rx/BLoC 中)。
- 结束:`done` 事件标记完成;错误通过 onError 传递。
11. Stream 订阅模式
- 单订阅:默认 `stream.listen(...)`,一次只能一个监听者,适合顺序消费。
- 广播:`stream.asBroadcastStream()` 或 `StreamController.broadcast()`,可多监听,常用于事件总线/全局通知。
12. mixin 机制
- 通过 `mixin Foo { ... }` 定义,可被 `class A with Foo` 混入,实现代码复用不增加继承层级。
- 不可定义构造函数;可用 `on` 约束限定混入目标:`mixin Anim on State { ... }`。
13. StatefulWidget 生命周期
- 创建:`createState` → `initState`(一次) → `didChangeDependencies`(依赖变更可再触发) → `build`。
- 更新:`didUpdateWidget`(父参数变化) → `build`;`setState` 触发 `build`。
- 移除/销毁:`deactivate`(可能重插) → `dispose`(一次,清理资源)。
14. Widgets、Elements、RenderObjects 关系
- Widget:配置描述,纯数据不可变。
- Element:Widget 在树中的实例,维护生命周期与与 RenderObject 的绑定;分 ComponentElement 与 RenderObjectElement。
- RenderObject:负责布局与绘制,持有尺寸/位置信息。链路:Widget → Element → RenderObject。
15. Flutter 线程/隔离模型
- 主 Isolate:运行 Dart 代码、构建/更新 Widget/Element/Render 树。
- GPU 线程:光栅化与合成。
- IO 线程:文件/网络/图片解码等。
- 计算密集:额外 Isolate 并行,消息传递通信。
16. Flutter 与原生通信
- 平台通道:MethodChannel(请求-响应)、EventChannel(持续事件)、BasicMessageChannel(二进制/字符串)。
- 编解码:StandardMethodCodec/JSON/自定义;iOS 用 `FlutterMethodChannel`,Android 用 `MethodChannel`。
- 其他:PlatformView 嵌入原生视图;FFI 直接调用 C 接口(性能敏感、需注意线程/安全)。
Flutter 补充要点
- 构建与性能:避免在 `build` 做重计算;使用 const Widget 降低重建;列表优先 ListView.builder/SliverList;避免频繁 setState 大范围刷新,可用局部状态(ValueListenableBuilder/Provider/BLoC)。
- 状态管理概览:SetState(局部)、InheritedWidget/InheritedModel、Provider、BLoC/Rx、Riverpod、GetX、MobX;选择取决于规模、可测试性和团队习惯。
- 帧渲染管线:动画驱动 → build → layout → paint → compositing → raster;Jank 多由 build/layout 过重或主线程阻塞 IO。
- 内存与泄漏:注意 Stream/StreamSubscription 取消,Controller/FocusNode/AnimationController 在 dispose 释放;图片缓存(PaintingBinding.instance.imageCache)可调节上限。
- 资源与国际化:pubspec.yaml 声明资源;使用 `Intl`/`flutter_localizations`;注意字体子集化减包体。
- 包体与启动:移除未用资源/字体子集化,启用 R8/Proguard(Android),iOS 开启 Bitcode(如需)并剔除未用架构;首屏用占位/骨架屏,预热关键 Isolate/缓存图片。
---
---
## 🏗️ 架构设计与工程化
1. iOS 架构模式深度解析
- **MVC 模式**:Model-View-Controller 的职责划分;iOS 中 ViewController 过重的问题;如何优化 MVC?
- **MVVM 模式**:ViewModel 的设计原则;数据绑定机制;如何实现双向绑定?MVVM 与 ReactiveCocoa/Combine 的结合?
- **VIPER 模式**:View-Interactor-Presenter-Entity-Router 的职责;何时使用 VIPER?VIPER 的优缺点?
- **Clean Architecture**:依赖倒置原则;分层架构设计;如何实现可测试的架构?
- **组件化架构**:模块化拆分策略;如何实现模块间通信?如何避免循环依赖?
### 🔍 【深入问题】
- 如何选择合适的架构模式?架构演进策略?如何重构现有架构?
- 如何实现依赖注入?Service Locator vs Dependency Injection?如何管理依赖关系?
- 如何设计可扩展的架构?如何实现插件化?如何支持动态模块加载?
- 架构的可测试性?如何编写单元测试?如何实现 Mock 和 Stub?
- 架构的性能影响?如何平衡架构复杂度和性能?如何优化架构性能?
### ✅ 【答案与解释】
1. **架构模式选择**:
- 选择原则:根据项目规模、团队规模、复杂度选择,从简单开始逐步演进
- 演进策略:MVC → MVVM → VIPER,按需引入,避免过度设计
- 重构策略:识别代码异味,逐步重构,保持功能稳定,建立测试保护
- 实现:分析项目需求,选择合适的架构,建立重构计划
2. **依赖注入实现**:
- 实现方式:构造函数注入、属性注入、方法注入,使用依赖注入容器
- Service Locator vs DI:Service Locator 主动获取,DI 被动注入,DI 更易测试
- 管理依赖:使用协议定义接口,使用容器管理依赖,实现依赖解析
- 实现:定义依赖协议,实现依赖注入容器,管理依赖生命周期
3. **可扩展架构设计**:
- 设计原则:模块化、松耦合、高内聚,定义清晰的模块接口
- 插件化:定义插件协议,运行时加载插件,注册到系统
- 动态模块:使用动态库实现模块,运行时加载,支持热更新
- 实现:设计模块化架构,实现插件系统,支持动态加载
4. **架构可测试性**:
- 可测试性:依赖注入、协议抽象、单一职责,便于 Mock 和测试
- 单元测试:为每个模块编写测试,使用 Mock 对象,建立测试覆盖
- Mock 和 Stub:使用协议定义接口,实现 Mock 对象,模拟依赖行为
- 实现:设计可测试架构,编写单元测试,建立测试体系
5. **架构性能影响**:
- 性能影响:架构层次增加性能开销,需要平衡复杂度和性能
- 平衡策略:关键路径优化,非关键路径可以复杂,使用缓存减少开销
- 性能优化:减少架构层次,优化数据流,使用异步处理,缓存结果
- 实现:分析性能瓶颈,优化关键路径,平衡架构和性能
2. 网络层架构设计
- **请求封装**:如何设计统一的网络请求接口?如何实现请求拦截器?
- **响应处理**:如何统一处理响应?如何实现错误处理机制?
- **缓存策略**:如何实现多级缓存?如何设计缓存失效机制?
- **重试机制**:如何实现智能重试?如何避免重复请求?
- **请求合并**:如何实现请求去重?如何合并相似请求?
### 🔍 【深入问题】
- 如何设计支持多种协议的网络层?如何实现协议切换?
- 如何实现请求优先级?如何管理请求队列?如何实现请求取消?
- 如何实现离线缓存?如何同步离线数据?如何处理数据冲突?
- 如何实现请求加密?如何保证数据安全?如何实现证书固定?
- 如何监控网络性能?如何实现网络诊断?如何优化网络请求?
### ✅ 【答案与解释】
1. **多协议网络层**:
- 设计:使用协议抽象网络层,实现不同协议的适配器
- 协议切换:运行时切换协议实现,使用策略模式,支持动态切换
- 实现:定义网络协议接口,实现不同协议适配器,支持协议切换
2. **请求优先级与队列**:
- 优先级:使用 NSOperation 的优先级,或自定义优先级队列
- 队列管理:使用 NSOperationQueue 管理请求,设置最大并发数,控制队列
- 请求取消:使用 NSOperation 的 cancel,或使用 URLSessionTask 的 cancel
- 实现:建立请求队列,实现优先级管理,支持请求取消
3. **离线缓存与同步**:
- 离线缓存:使用 Core Data 或 SQLite 缓存数据,实现离线访问
- 数据同步:使用时间戳或版本号,实现增量同步,处理冲突
- 冲突处理:使用最后写入获胜、合并策略、用户选择等方式处理冲突
- 实现:建立缓存机制,实现同步逻辑,处理数据冲突
4. **请求安全**:
- 请求加密:使用 HTTPS,实现端到端加密,保护敏感数据
- 数据安全:使用 Keychain 存储密钥,实现请求签名,防止篡改
- 证书固定:在客户端硬编码证书,防止中间人攻击
- 实现:实现加密机制,建立安全策略,保护数据安全
5. **网络性能监控**:
- 性能监控:记录请求耗时、失败率、网络状态,分析性能瓶颈
- 网络诊断:检测网络连接,诊断网络问题,提供诊断信息
- 优化请求:减少请求数量,合并请求,使用缓存,优化数据传输
- 实现:建立监控体系,实现诊断工具,持续优化网络性能
3. 数据持久化架构
- **Core Data**:如何设计 Core Data 模型?如何优化 Core Data 性能?如何实现数据迁移?
- **SQLite**:如何设计数据库表结构?如何优化 SQL 查询?如何实现数据库升级?
- **Realm**:Realm 的优势和劣势?如何选择持久化方案?
- **文件存储**:如何设计文件存储结构?如何实现文件缓存?如何管理文件生命周期?
### 🔍 【深入问题】
- 如何实现数据同步?如何解决数据冲突?如何实现增量同步?
- 如何设计数据模型?如何实现数据版本管理?如何实现数据迁移?
- 如何优化数据库性能?如何实现数据库连接池?如何优化查询性能?
- 如何实现数据加密?如何保护敏感数据?如何实现数据备份和恢复?
- 如何实现数据缓存?如何设计缓存策略?如何实现缓存失效?
### ✅ 【答案与解释】
1. **数据同步实现**:
- 同步策略:使用时间戳、版本号、增量更新,实现数据同步
- 冲突解决:使用最后写入获胜、合并策略、用户选择等方式
- 增量同步:只同步变更数据,使用变更日志,减少数据传输
- 实现:建立同步机制,实现冲突解决,优化同步性能
2. **数据模型与版本管理**:
- 数据模型:设计清晰的数据模型,使用关系型或文档型数据库
- 版本管理:使用版本号管理数据模型,支持多版本共存
- 数据迁移:实现数据迁移脚本,支持模型升级,保证数据完整性
- 实现:设计数据模型,建立版本管理,实现迁移机制
3. **数据库性能优化**:
- 性能优化:使用索引,优化查询,批量操作,减少事务开销
- 连接池:使用连接池复用连接,减少连接开销,控制连接数量
- 查询优化:使用索引,优化 SQL,避免全表扫描,使用分页
- 实现:分析查询性能,优化数据库设计,建立性能监控
4. **数据安全**:
- 数据加密:使用 SQLCipher 加密数据库,或应用层加密敏感字段
- 敏感数据:使用 Keychain 存储密钥,实现数据脱敏,保护隐私
- 备份恢复:实现数据备份机制,支持数据恢复,保证数据安全
- 实现:建立加密机制,保护敏感数据,实现备份恢复
5. **数据缓存**:
- 缓存实现:使用内存缓存、磁盘缓存,实现多级缓存
- 缓存策略:LRU、LFU、FIFO 等策略,根据场景选择
- 缓存失效:使用时间过期、版本号、手动失效等方式
- 实现:建立缓存系统,实现缓存策略,优化缓存性能
4. 性能优化深度实践
- **启动优化**:如何分析启动耗时?如何优化 dyld 加载?如何优化 runtime 初始化?
- **内存优化**:如何减少内存占用?如何优化图片内存?如何实现内存缓存?
- **CPU 优化**:如何优化算法复杂度?如何减少主线程阻塞?如何优化计算密集型任务?
- **网络优化**:如何减少网络请求?如何实现请求合并?如何优化数据传输?
- **UI 优化**:如何减少离屏渲染?如何优化列表性能?如何实现流畅动画?
### 🔍 【深入问题】
- 如何建立性能监控体系?如何实现性能埋点?如何分析性能瓶颈?
- 如何实现性能测试?如何建立性能基准?如何持续优化性能?
- 如何平衡性能和质量?如何实现渐进式优化?如何避免过度优化?
- 如何优化电池消耗?如何减少后台活动?如何优化定位服务?
- 如何优化包体大小?如何实现按需加载?如何减少资源占用?
### ✅ 【答案与解释】
1. **性能监控体系**:
- 建立体系:定义性能指标,建立监控系统,实现性能看板
- 性能埋点:在关键路径添加埋点,记录性能数据,上报到监控系统
- 分析瓶颈:使用性能分析工具,分析性能数据,定位性能瓶颈
- 实现:建立监控系统,实现埋点机制,分析性能数据
2. **性能测试与优化**:
- 性能测试:建立性能测试用例,使用工具测试,记录性能数据
- 性能基准:定义性能基准线,建立性能目标,持续监控
- 持续优化:分析性能趋势,识别优化机会,持续改进
- 实现:建立测试体系,定义性能基准,持续优化
3. **性能与质量平衡**:
- 平衡策略:关键路径优化,非关键路径可以牺牲性能,保证质量
- 渐进式优化:从简单优化开始,逐步深入,避免过度优化
- 避免过度:分析优化收益,避免过度优化,保持代码可维护性
- 实现:分析优化需求,制定优化计划,平衡性能和质量
4. **电池优化**:
- 电池消耗:减少 CPU 使用,优化网络请求,减少定位使用
- 后台活动:减少后台任务,优化后台刷新,使用后台任务 API
- 定位优化:使用低精度定位,减少定位频率,使用地理围栏
- 实现:分析电池消耗,优化后台活动,减少定位使用
5. **包体优化**:
- 包体大小:移除未使用代码,压缩资源,优化图片,使用动态库
- 按需加载:延迟加载模块,按需加载资源,使用动态库
- 资源优化:压缩图片,移除未使用资源,使用资源优化工具
- 实现:分析包体大小,优化资源,实现按需加载
5. 工程化与 DevOps
- **CI/CD**:如何搭建持续集成?如何实现自动化测试?如何实现自动化部署?
- **代码质量**:如何实现代码审查?如何建立代码规范?如何实现静态分析?
- **版本管理**:如何管理版本号?如何实现版本发布?如何管理依赖版本?
- **监控与日志**:如何实现崩溃监控?如何实现性能监控?如何实现日志系统?
- **测试策略**:如何编写单元测试?如何实现集成测试?如何实现 UI 测试?
### 🔍 【深入问题】
- 如何实现自动化测试?如何提高测试覆盖率?如何实现测试驱动开发?
- 如何实现灰度发布?如何实现 A/B 测试?如何实现功能开关?
- 如何实现错误追踪?如何实现用户行为分析?如何实现数据埋点?
- 如何实现热修复?如何实现动态更新?如何实现远程配置?
- 如何实现安全加固?如何实现代码混淆?如何实现反调试?
### ✅ 【答案与解释】
1. **自动化测试**:
- 实现:使用 XCTest 编写测试,使用 CI/CD 自动运行,建立测试体系
- 测试覆盖率:使用工具统计覆盖率,提高覆盖率,建立覆盖率目标
- TDD:先写测试,再写实现,重构代码,保证质量
- 实现:建立测试框架,编写测试用例,提高覆盖率
2. **灰度发布与 A/B 测试**:
- 灰度发布:逐步发布新版本,监控错误率,逐步扩大范围
- A/B 测试:随机分配用户,对比不同方案,分析效果
- 功能开关:使用远程配置控制功能,支持动态开启关闭
- 实现:建立发布流程,实现 A/B 测试,建立功能开关系统
3. **错误追踪与分析**:
- 错误追踪:集成崩溃监控工具,记录错误信息,分析错误原因
- 用户行为:记录用户操作,分析用户行为,优化用户体验
- 数据埋点:在关键位置添加埋点,记录用户行为,分析数据
- 实现:集成监控工具,实现埋点机制,分析用户数据
4. **热修复与动态更新**:
- 热修复:使用 JSPatch 或 Swift 热修复,动态修复 bug
- 动态更新:使用动态库或脚本,实现功能更新,支持热更新
- 远程配置:使用远程配置服务,动态调整应用行为,支持配置更新
- 实现:建立热修复机制,实现动态更新,建立配置系统
5. **安全加固**:
- 安全加固:使用代码混淆,防止逆向工程,保护代码安全
- 代码混淆:使用工具混淆代码,增加逆向难度,保护知识产权
- 反调试:检测调试器,防止动态调试,保护应用安全
- 实现:使用安全工具,实现代码保护,建立安全机制
6. 跨平台开发
- **React Native**:RN 的原理?如何实现原生模块?如何优化 RN 性能?
- **Flutter**:Flutter 的渲染机制?如何实现平台通道?如何优化 Flutter 性能?
- **混合开发**:如何实现 WebView 与原生通信?如何优化混合应用性能?
- **统一开发**:如何实现代码复用?如何设计跨平台架构?如何管理跨平台代码?
### 🔍 【深入问题】
- 如何选择跨平台方案?各方案的优缺点?如何评估跨平台需求?
- 如何实现原生能力扩展?如何实现平台特定功能?如何实现性能优化?
- 如何实现代码共享?如何管理平台差异?如何实现统一测试?
- 如何实现热更新?如何实现动态加载?如何实现版本管理?
- 如何实现性能监控?如何实现错误追踪?如何实现数据分析?
### ✅ 【答案与解释】
1. **跨平台方案选择**:
- 方案对比:React Native、Flutter、原生开发,各有优缺点
- 评估需求:根据项目需求、团队能力、性能要求选择方案
- 选择原则:性能要求高用原生,快速开发用跨平台,平衡考虑
- 实现:分析项目需求,评估方案,选择合适方案
2. **原生能力扩展**:
- 能力扩展:使用平台通道调用原生功能,实现原生能力扩展
- 平台特定:使用条件编译,实现平台特定功能,保持代码统一
- 性能优化:使用原生模块,优化关键路径,提高性能
- 实现:建立平台通道,实现原生模块,优化性能
3. **代码共享与测试**:
- 代码共享:使用共享代码库,实现业务逻辑共享,减少重复
- 平台差异:使用抽象层封装差异,使用适配器模式,统一接口
- 统一测试:编写跨平台测试,使用 Mock 对象,建立测试体系
- 实现:建立共享代码库,封装平台差异,建立测试体系
4. **热更新与版本管理**:
- 热更新:使用动态加载,实现功能更新,支持热修复
- 动态加载:使用动态库或脚本,实现功能动态加载,支持插件化
- 版本管理:使用语义化版本,管理版本号,支持版本升级
- 实现:建立热更新机制,实现动态加载,建立版本管理
5. **监控与分析**:
- 性能监控:集成性能监控工具,记录性能数据,分析性能瓶颈
- 错误追踪:集成错误追踪工具,记录错误信息,分析错误原因
- 数据分析:记录用户行为,分析用户数据,优化用户体验
- 实现:集成监控工具,建立分析体系,持续优化
---
---
## ⭐ 高级主题与最佳实践
1. 安全与隐私
- **数据加密**:如何实现数据加密?如何管理加密密钥?如何实现端到端加密?
- **网络安全**:如何实现证书固定?如何防止中间人攻击?如何实现请求签名?
- **隐私保护**:如何实现隐私合规?如何实现数据脱敏?如何实现用户授权?
- **代码安全**:如何防止代码注入?如何实现代码混淆?如何防止逆向工程?
2. 用户体验优化
- **响应速度**:如何优化首屏加载?如何实现预加载?如何实现懒加载?
- **交互设计**:如何实现流畅动画?如何优化手势响应?如何实现无障碍支持?
- **错误处理**:如何设计友好的错误提示?如何实现错误恢复?如何实现降级策略?
- **个性化**:如何实现用户画像?如何实现个性化推荐?如何实现智能推送?
3. 大数据与高并发
- **数据处理**:如何实现大数据处理?如何优化数据处理性能?如何实现流式处理?
- **并发控制**:如何实现高并发?如何避免资源竞争?如何实现负载均衡?
- **缓存策略**:如何设计多级缓存?如何实现缓存一致性?如何实现缓存预热?
- **数据同步**:如何实现数据同步?如何解决数据冲突?如何实现最终一致性?
4. 算法与数据结构
- **常用算法**:排序、查找、图算法、动态规划的应用场景?
- **数据结构**:数组、链表、树、哈希表的选择和使用?
- **算法优化**:如何优化算法复杂度?如何实现算法缓存?如何实现算法并行化?
- **实际应用**:如何在 iOS 开发中应用算法?如何优化算法性能?
---
---
## 💼 面试实战技巧
1. 技术深度展示
- 如何深入讲解技术原理?如何展示技术深度?如何体现学习能力?
- 如何回答开放性问题?如何展示解决问题的能力?如何体现工程思维?
2. 项目经验表达
- 如何描述项目难点?如何展示技术亮点?如何体现团队协作能力?
- 如何回答架构设计问题?如何展示技术选型能力?如何体现技术领导力?
3. 问题解决思路
- 如何分析问题?如何制定解决方案?如何评估方案优劣?
- 如何展示调试能力?如何展示性能优化能力?如何展示代码质量意识?
---
---
## 📚 总结
本面试要点总结涵盖了 iOS 开发的各个方面,从基础到高级,从理论到实践。
学习建议
1. **系统学习**:按照主题系统学习,建立完整的知识体系
2. **深入理解**:不仅要知其然,更要知其所以然
3. **实践应用**:将理论知识应用到实际项目中
4. **持续更新**:关注新技术,持续学习和更新知识
5. **总结反思**:定期总结和反思,形成自己的知识体系
### 🔍 【深入问题】
- Flutter 的渲染机制?如何优化 Widget 重建?如何减少不必要的 rebuild?
- 状态管理方案的选择?Provider、BLoC、Riverpod 的优缺点?如何设计全局状态管理?
- 帧渲染管线的优化?如何减少 Jank?如何监控和优化渲染性能?
- 内存泄漏的排查?如何避免常见的内存泄漏?如何优化内存使用?
- 包体优化的策略?如何减少 APK/IPA 体积?如何实现按需加载?
- 启动性能优化?如何缩短冷启动时间?如何实现渐进式加载?
- Isolate 的使用场景?如何实现计算密集型任务?Isolate 间的通信优化?
- 平台通道的性能?如何优化原生通信?如何实现高性能的混合开发?
### ✅ 【答案与解释】
1. **Flutter 渲染机制**:
- 渲染机制:Widget → Element → RenderObject,使用脏标记优化重建
- 优化重建:使用 const Widget,使用 Key,减少不必要的 rebuild
- 减少 rebuild:使用局部状态,使用 ValueListenableBuilder,优化状态管理
- 实现:分析 Widget 树,优化重建逻辑,减少不必要的更新
2. **状态管理方案**:
- 方案对比:Provider 简单易用,BLoC 适合复杂状态,Riverpod 类型安全
- 选择原则:根据项目复杂度、团队习惯选择,从简单开始
- 全局状态:使用 Provider 或 Riverpod 管理全局状态,使用 BLoC 管理业务状态
- 实现:分析状态需求,选择合适方案,建立状态管理体系
3. **帧渲染优化**:
- 优化策略:减少 build 耗时,优化 layout,减少 paint,优化 compositing
- 减少 Jank:使用性能监控工具,分析帧率,优化性能瓶颈
- 性能监控:使用 Flutter DevTools,使用性能分析工具,监控渲染性能
- 实现:分析渲染性能,优化关键路径,建立性能监控
4. **内存泄漏排查**:
- 排查方法:使用 Flutter DevTools,使用内存分析工具,检查对象引用
- 避免泄漏:及时取消 Stream 订阅,释放 Controller,避免循环引用
- 内存优化:使用图片缓存,优化图片加载,及时释放资源
- 实现:建立内存监控,排查泄漏,优化内存使用
5. **包体优化**:
- 优化策略:移除未使用代码,压缩资源,优化图片,使用动态库
- 减少体积:使用 ProGuard/R8,移除未使用资源,优化资源大小
- 按需加载:延迟加载模块,按需加载资源,使用代码分割
- 实现:分析包体大小,优化资源,实现按需加载
6. **启动性能优化**:
- 优化策略:减少启动时任务,延迟初始化,使用异步加载
- 冷启动:优化首屏渲染,减少启动时任务,使用骨架屏
- 渐进式加载:先显示骨架屏,异步加载数据,逐步渲染内容
- 实现:分析启动耗时,优化启动流程,实现渐进式加载
7. **Isolate 使用**:
- 使用场景:计算密集型任务,图像处理,大数据处理
- 计算任务:使用 compute 函数,创建 Isolate,处理计算任务
- 通信优化:减少数据传输,使用 SendPort/ReceivePort,优化消息传递
- 实现:识别计算任务,使用 Isolate 处理,优化通信
8. **平台通道性能**:
- 性能优化:减少平台通道调用,批量传输数据,使用二进制编码
- 原生通信:优化通信协议,减少序列化开销,使用高效编码
- 混合开发:使用 PlatformView 嵌入原生视图,优化混合性能
- 实现:优化通信协议,减少调用次数,提高通信效率
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)