一、Objective-C & Swift 基础
1. ★ Objective-C 中 #import
和 #include
的区别?
答案:
-
#import
:自动防止重复包含,OC推荐使用。 -
#include
:需配合#ifndef
避免重复包含,C/C++中使用。
2. Swift 中 struct
和 class
的核心区别?
答案:
-
struct
:值类型,栈存储,不可继承。 -
class
:引用类型,堆存储,支持继承。
3. @property (nonatomic, copy)
中 copy
的作用?
答案:
防止可变对象(如 NSMutableString
)被篡改,深拷贝为不可变对象。
4. Swift 的 optional
本质是什么?
答案:
枚举类型:
enum Optional<Wrapped> { case none; case some(Wrapped) }
5. __block
关键字在 Block 中的作用?
答案:
允许 Block 内修改外部局部变量(默认捕获的变量是只读的)。
6. ★ weak
和 unowned
的区别?
答案:
-
weak
:对象释放后自动置nil
,必须为 Optional。 -
unowned
:对象释放后不置nil
,访问会崩溃,性能稍高。
7. Swift 中 final
关键字的作用?
答案:
禁止类或方法被继承/重写,提高性能(静态派发)。
8. @synthesize
和 @dynamic
的区别?
答案:
-
@synthesize
:编译器自动生成 getter/setter 和实例变量。 -
@dynamic
:开发者需手动实现存取方法(如 Core Data 模型)。
9. performSelector:
为什么可能引发内存问题?
答案:
可能因 ARC 无法识别方法返回值类型,导致内存泄漏或过早释放。
10. Swift 协议中 associatedtype
的作用?
答案:
定义协议中的泛型占位类型,允许实现时指定具体类型:
protocol Container { associatedtype Item; func add(_ item: Item) }
二、内存管理
11. ★ ARC 下哪些情况会导致循环引用?
答案:
- 对象互相强引用(如
A
强持有B
,B
强持有A
)。 - Block 强持有
self
且self
强持有 Block。
12. NSTimer
如何避免内存泄漏?
答案:
- 使用
weak self
。 - 手动调用
invalidate()
并置nil
。
13. autoreleasepool
的使用场景?
答案:
循环中创建大量临时对象时减少内存峰值(如解析 JSON 数组)。
14. ★ CADisplayLink
和 NSTimer
的内存管理区别?
答案:
-
CADisplayLink
:与屏幕刷新率同步,需手动移除RunLoop
。 -
NSTimer
:需手动invalidate
,否则可能泄漏。
15. CFRelease
在 ARC 环境是否需要调用?
答案:
需要,Core Foundation 对象不受 ARC 管理。
16. __unsafe_unretained
的使用风险?
答案:
对象释放后指针不置 nil
,访问会野指针崩溃。
17. 如何检测 iOS 应用的内存泄漏?
答案:
- Xcode Debug Memory Graph。
- Instruments 的 Leaks 工具。
18. ★ dealloc
方法中不能调用什么?
答案:
- 不能调用
[super dealloc]
(ARC 禁止)。 - 避免访问
__weak
变量(可能已释放)。
19. NSMapTable
和 NSDictionary
的内存特性区别?
答案:
-
NSMapTable
:可配置弱引用键/值(如NSPointerFunctionsWeakMemory
)。 -
NSDictionary
:始终强引用键和值。
20. UIGraphicsBeginImageContext
后是否需要手动释放内存?
答案:
需要,调用 UIGraphicsEndImageContext()
释放上下文。
三、多线程与GCD(续)
21. GCD的dispatch_barrier_async
作用?
答案:
在并发队列中充当“栅栏”,确保屏障前的任务全部完成后,才执行屏障后的任务。常用于多线程读写安全。
let queue = DispatchQueue(label: "com.test", attributes: .concurrent)
queue.async { /* 读操作 */ }
queue.async(flags: .barrier) { /* 写操作 */ } // 确保写操作独占
22. NSOperation
相比GCD的优势?
答案:
- 支持任务依赖(
addDependency
)。 - 可取消(
cancel()
)、暂停(isSuspended
)和设置优先级(queuePriority
)。 - 更面向对象,适合复杂任务管理。
23. 多线程读写问题如何解决?
答案:
-
GCD方案:
dispatch_barrier_async
+ 并发队列。 -
锁方案:
os_unfair_lock
(iOS 10+)或NSLock
。 -
Swift方案:
Actor
(Swift 5.5+)。
24. 主队列(main queue
)和全局队列(global queue
)的区别?
答案:
- 主队列:串行,UI更新必须在此队列执行。
-
全局队列:并发,分优先级(
.default
、.background
等),适合耗时任务。
25. dispatch_once
的实现原理?
答案:
通过静态变量和原子操作确保代码块仅执行一次(Swift中废弃,改用静态属性或lazy
)。
class Singleton {
static let shared = Singleton() // 替代dispatch_once
}
四、网络通信
31. HTTPS握手流程?
答案:
- ClientHello:客户端发送支持的加密套件列表。
- ServerHello:服务端选择加密套件并返回证书。
- 密钥交换:客户端验证证书后生成预主密钥(Premaster Secret)。
- 加密通信:双方使用对称加密(如AES)传输数据。
32. NSURLSession
的delegate
和completionHandler
区别?
答案:
- Delegate:适合大文件下载(实时回调进度),需遵守协议。
- CompletionHandler:简单请求,回调在主线程,代码更紧凑。
33. 如何优化HTTP/2的多路复用?
答案:
- 减少域名分片(避免DNS查询)。
- 使用长连接(Keep-Alive)。
- 压缩请求头(HPACK算法)。
34. TCP粘包问题如何解决?
答案:
- 固定长度:每条消息定长(如1024字节)。
-
分隔符:用特殊字符(如
\n
)分割消息。 - 长度前缀:消息头声明长度(如4字节表示消息体大小)。
35. WebSocket
和HTTP
的长连接区别?
答案:
- HTTP长连接:基于TCP,仍需每次携带完整Header。
- WebSocket:全双工通信,首次握手后无需重复Header,适合实时聊天。
五、UI与动画
41. layoutIfNeeded
和setNeedsLayout
的区别?
答案:
- setNeedsLayout:标记视图需要更新布局,异步执行。
- layoutIfNeeded:立即强制更新布局(常用于动画中同步布局)。
42. drawRect:
的性能优化技巧?
答案:
- 避免频繁调用:用
CALayer
替代自定义绘制。 - 限制绘制区域:
setNeedsDisplayInRect(_:)
。 - 离屏渲染优化:使用
shouldRasterize
缓存复杂图层。
43. UITableView
复用机制原理?
答案:
通过reuseIdentifier
从复用池获取闲置Cell,减少频繁创建销毁。
// 复用池逻辑伪代码
func dequeueReusableCell(withIdentifier: String) -> UITableViewCell? {
if let cell = reusePool[identifier]?.popLast() {
return cell // 从池中取出
}
return nil
}
44. 离屏渲染(Offscreen Rendering)的触发条件?
答案:
- 圆角+裁剪(
cornerRadius + masksToBounds
)。 - 阴影(
shadowPath
未设置时)。 - 高斯模糊(
UIVisualEffectView
)。
45. Core Animation
和UIView
动画的区别?
答案:
-
UIView动画:基于
CALayer
的封装,自动处理图层属性。 - Core Animation:更底层,可控制动画暂停、反转、插值。
六、性能优化
51. Instruments
中Time Profiler
的使用技巧?
答案:
- 勾选Hide System Libraries:聚焦App代码耗时。
- 检查Heavy Stack Trace:定位耗时函数调用链。
52. 如何降低App启动时间?
答案:
- 减少动态库:合并或静态化。
- 延迟初始化:非必要任务放到首屏渲染后。
- 二进制重排:调整函数布局减少Page Fault。
六、性能优化(续)
53. UIImage
加载大图的内存优化方案?
答案:
-
分块加载:使用
CGImageSourceCreateThumbnailAtIndex
生成缩略图。 -
降采样:通过
CGImageSource
设置kCGImageSourceShouldCacheImmediately
为false
。 -
内存映射:用
mmap
读取文件(适合极大型图片)。
let options = [kCGImageSourceShouldCache: false] as CFDictionary
let source = CGImageSourceCreateWithURL(url as CFURL, options)!
54. ★ UITableView
滚动卡顿的排查步骤?
答案:
-
检查离屏渲染:用
Core Animation
工具检测黄色警告。 -
分析主线程阻塞:
Time Profiler
定位耗时方法。 -
优化Cell布局:避免动态计算高度(缓存
systemLayoutSizeFitting
结果)。 -
减少图层混合:确保Cell背景色不透明(
backgroundColor = .white
)。
55. ARC
环境下如何减少内存峰值?
答案:
-
局部自动释放池:在循环内嵌套
autoreleasepool
。 - 懒加载:延迟初始化大对象。
-
图片缓存:用
NSCache
替代全局变量存储图片。
56. NSDateFormatter
的性能优化技巧?
答案:
-
全局缓存:静态共享一个
NSDateFormatter
实例(线程安全需加锁)。 - 设置固定Locale:避免系统语言切换导致性能损耗。
static let sharedFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
57. 如何检测CPU
使用率过高?
答案:
-
Instruments:
CPU Profiler
工具查看各线程占比。 -
代码监控:
mach_task_self(), &threads) thread_info(threads[i], THREAD_BASIC_INFO, &info, &count)
58. ★ 离屏渲染的检测与优化方法?
答案:
-
检测:Xcode调试面板勾选
Color Offscreen-Rendered
(黄色标记)。 -
优化:
- 用
CAShapeLayer
绘制圆角替代cornerRadius
。 - 预渲染静态内容为位图(
UIGraphicsBeginImageContext
)。
- 用
59. WebView
白屏问题的解决方案?
答案:
-
内存不足:监听
didReceiveMemoryWarning
,清理缓存。 -
进程崩溃:WKWebView独立进程,需实现
webViewWebContentProcessDidTerminate
恢复。 - 资源加载失败:检查本地HTML文件路径或CDN可用性。
60. APM
(性能监控)需要采集哪些指标?
答案:
- 基础指标:CPU、内存、FPS、启动耗时。
- 网络指标:请求成功率、延迟、流量消耗。
- Crash:堆栈符号化、发生场景(前后台)。
七、架构与设计模式
61. ★ MVC
的缺点及改进方案?
答案:
- 缺点:Controller臃肿、单元测试困难。
-
改进:
-
MVVM:将业务逻辑抽到
ViewModel
。 - VIPER:进一步拆分职责(Router、Interactor)。
-
MVVM:将业务逻辑抽到
62. MVVM
中ViewModel
的职责边界?
答案:
- 负责:数据格式化、网络请求、业务逻辑。
- 不负责:直接操作UI或持有View引用。
63. 如何实现组件化
通信?(如URL Router
)
答案:
-
URL路由:定义统一Scheme(如
app://detail?id=1
)。 - 协议抽象:各组件暴露接口协议,依赖注入实现类。
protocol UserServiceProtocol { func getUser(id: Int) -> User }
64. ★ 单例模式
的线程安全实现?
答案:
class Singleton {
static let shared = Singleton()
private init() {} // 防止外部实例化
}
65. Delegate
和Notification
的使用场景区别?
答案:
-
Delegate:1对1精准回调(如
UITableViewDelegate
)。 - Notification:1对多广播(如全局事件通知)。
66. 如何设计一个缓存模块
?(如NSCache
替代方案)
答案:
-
内存缓存:
NSCache
(自动清理)。 -
磁盘缓存:
FileManager
+ 序列化(如Codable
)。 - LRU策略:链表+字典实现最近最少使用淘汰。
67. ★ 响应式编程
(如Combine
)的核心思想?
答案:
-
数据流:将事件、状态封装为流(
Publisher
)。 -
声明式:通过
subscribe
自动响应数据变化。
68. 依赖注入
在iOS中的实践?
答案:
-
构造函数注入:
class UserService { init(api: APIClientProtocol) { ... } }
-
属性注入:通过
setter
方法动态替换依赖。
69. 如何避免Massive ViewController
?
答案:
-
职责拆分:
- 数据源抽离为单独类(如
TableViewDataSource
)。 - 业务逻辑移到
Presenter
或Interactor
。
- 数据源抽离为单独类(如
70. Clean Architecture
的分层逻辑?
答案:
- Entities:核心业务模型。
- Use Cases:业务规则封装。
- Interface Adapters:转换数据格式(如DTO)。
- Frameworks:UI、数据库等具体实现。
八、系统与安全
71. ★ 沙盒机制
的目录结构及权限?
答案:
- Documents:用户数据(iCloud同步)。
- Library/Caches:缓存(系统可清理)。
- tmp:临时文件(App重启可能删除)。
72. 如何防止API
数据被抓包?
答案:
-
证书绑定:
URLSessionDelegate
中校验服务器证书指纹。 - 请求加密:敏感参数AES加密。
73. Keychain
和UserDefaults
的安全区别?
答案:
- Keychain:加密存储(适合密码、Token)。
- UserDefaults:明文存储(仅适合非敏感配置)。
74. ★ 越狱检测
的常见方法?
答案:
-
文件检测:检查
/Applications/Cydia.app
是否存在。 -
API检测:调用
stat
函数判断动态库注入。
75. App
签名与证书的工作原理?
答案:
- 开发者生成私钥,苹果用公钥验证签名。
- 打包时嵌入
embedded.mobileprovision
文件。
76. 如何实现敏感逻辑
的代码混淆?
答案:
-
LLVM混淆:编译时重命名符号(如
-mllvm -fla
)。 - 字符串加密:运行时动态解密关键字符串。
77. ★ Biometric
(指纹/面容)的集成步骤?
答案:
- 导入
LocalAuthentication
框架。 - 调用
LAContext().evaluatePolicy()
。
78. Universal Link
的配置流程?
答案:
- 配置
apple-app-site-association
文件到HTTPS服务器。 - Xcode中开启
Associated Domains
能力。
79. 如何防御SQL
注入?
答案:
-
参数化查询:
let query = "SELECT * FROM Users WHERE id = ?" sqlite3_bind_int(stmt, 1, userID)
80. iOS
系统权限的动态申请策略?
答案:
- 按需申请:首次使用功能时弹出权限对话框。
-
引导设置:被拒绝后跳转
UIApplication.openSettingsURLString
。
iOS面试问题答案整理(第81题到第100题)
九、Swift 高级特性(10题)
81. ★ 泛型在协议中的使用(associatedtype
)?
答案:
associatedtype
是 Swift 协议中用于定义泛型关联类型的关键字。它允许协议声明一个或多个占位类型,由遵循该协议的类型在实现时指定具体类型。例如:
protocol Container {
associatedtype Item
var count: Int { get }
mutating func append(_ item: Item)
}
遵循该协议的类型需要指定 Item
的具体类型,如 Int
或 String
。
82. Result
类型的作用?
答案:
Result
是 Swift 标准库中的枚举类型,用于封装异步操作的成功或失败结果。其定义为:
public enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
它取代了传统的回调方式(如 (T?, Error?)
),强制开发者显式处理成功和失败两种情况,避免遗漏错误处理。
83. Swift 中 反射
(Mirror)的局限性?
答案:
Mirror
是 Swift 的反射机制,用于在运行时检查类型信息,但有以下局限性:
- 性能较差,不适合高频调用。
- 只能获取有限的类型信息(如属性名和值),无法修改属性或调用方法。
- 不支持获取泛型类型的具体参数信息。
- 无法访问
private
或fileprivate
成员。
84. ★ @escaping
和 @nonescaping
的区别?
答案:
-
@nonescaping
(默认):闭包在函数返回前必须被调用,不能逃逸出函数作用域。编译器可以优化内存管理。 -
@escaping
:闭包可能在函数返回后被调用(如存储为属性或异步回调)。需要显式标记,且闭包内引用self
时必须捕获(如[weak self]
)。
85. Property Wrapper
的实现原理?
答案:
属性包装器(@propertyWrapper
)通过包装属性的存储逻辑来复用代码。例如:
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespaces) }
}
}
使用时:
@Trimmed var name: String // 自动去除首尾空格
86. Swift 与 Objective-C
混编的注意事项?
答案:
-
兼容性:Swift 类需继承
NSObject
或标记@objc
才能在 Objective-C 中使用。 - 命名冲突:避免使用 Swift 特有语法(如元组)暴露给 Objective-C。
-
桥接文件:通过
{ProjectName}-Bridging-Header.h
导入 Objective-C 头文件。 -
类型映射:如 Swift 的
String
对应 Objective-C 的NSString
。
87. ★ async/await
如何替代回调地狱?
答案:
async/await
是 Swift Concurrency 的特性,通过同步写法处理异步代码。例如:
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
替代传统嵌套回调,代码更简洁,错误处理更直观(直接使用 try/catch
)。
88. Swift Concurrency 的 Actor
作用?
答案:
Actor
是 Swift 中用于线程安全的类型,通过隔离数据访问避免竞态条件:
actor Counter {
private var value = 0
func increment() { value += 1 }
}
访问 Actor
的方法必须使用 await
,确保串行执行。
89. Lazy
属性的线程安全性?
答案:
Swift 的 lazy
属性不是线程安全的。首次访问时若多个线程同时调用,可能导致初始化多次。解决方案:
- 使用
DispatchQueue
加锁。 - 改用
static let
(线程安全的单例初始化)。
90. Swift 中 KVO
的实现方式?
答案:
Swift 通过 @objc dynamic
标记属性和继承 NSObject
来支持 KVO:
class Observed: NSObject {
@objc dynamic var value: String = ""
}
let observer = observed.observe(\.value, options: [.new]) { _, change in
print("New value: \(change.newValue ?? "")")
}
十、综合场景(10题)
91. ★ 如何实现 Dark Mode
动态切换?
答案:
-
颜色与图片:使用
UIColor(dynamicProvider:)
和UIImageAsset
定义动态资源。 -
监听模式变化:通过
traitCollectionDidChange(_:)
响应系统模式变化。 -
强制切换:
window.overrideUserInterfaceStyle = .dark // 或 .light
-
自定义模式:结合
UserDefaults
存储用户偏好。
92. 国际化
的完整实现流程?
答案:
-
字符串本地化:将文本放入
Localizable.strings
文件,按语言分组(如en.lproj
、zh-Hans.lproj
)。 -
图片本地化:在
Assets.xcassets
中为图片设置不同语言版本。 -
代码调用:
NSLocalizedString("key", comment: "")
- 动态切换语言:需重启 App 或手动刷新 UI。
93. App
从点击图标到启动完成的流程?
答案:
- 系统加载:内核加载 Mach-O 可执行文件,动态链接器加载依赖库。
-
运行时初始化:调用
+load
和+initialize
方法(Swift 中已废弃)。 -
main()
函数:执行UIApplicationMain
,创建AppDelegate
。 -
生命周期回调:调用
application(_:didFinishLaunchingWithOptions:)
。 -
主线程 RunLoop:启动事件循环,显示
LaunchScreen
,加载根视图控制器。
94. ★ Crash
日志的符号化解析步骤?
答案:
- 获取日志:从设备(Xcode -> Window -> Devices)或第三方平台(如 Firebase)。
-
匹配符号文件(dSYM):确保 UUID 一致(通过
dwarfdump --uuid
检查)。 -
符号化:
atos -arch arm64 -o AppName.app.dSYM -l 0x100000000 0x100003000
-
工具辅助:使用 Xcode 的
symbolicatecrash
脚本或第三方服务。
95. 如何实现 增量更新
(热更新)?
答案:
-
JavaScriptCore/JSI:通过
React Native
或自研引擎下发 JS 脚本。 -
二进制补丁:使用
bsdiff
生成差异文件,客户端合并(需审核合规)。 - 服务器控制:动态配置资源或业务逻辑(如 A/B 测试)。
- 注意事项:苹果禁止下载可执行代码,需确保符合 App Store 政策。
96. App Clips
的开发注意事项?
答案:
- 大小限制:压缩资源至 10MB 以内。
- 功能精简:仅保留核心功能(如支付、扫码)。
-
关联体验:配置
Associated Domains
和Universal Links
。 -
生命周期:数据存储使用
NSUserActivity
或共享容器。
97. ★ Widget
的数据共享方案?
答案:
-
App Groups:启用
Capability
,使用UserDefaults(suiteName:)
或FileManager
共享容器。 -
Timeline Provider:通过
Intent
配置动态数据。 - 网络请求:Widget 内直接发起请求(需精简数据量)。
-
刷新策略:合理设置
Timeline
的刷新频率。
98. 如何优化 App Store
审核通过率?
答案:
- 遵守指南:避免私有 API、隐藏功能。
- 元数据完整:提供清晰的截图和描述。
- 测试账号:审核人员可访问所有功能。
- 崩溃修复:确保提交版本稳定。
- 快速响应:若被拒,针对性修改后重新提交。
99. SwiftUI
与 UIKit
的混合开发技巧?
答案:
-
嵌入 SwiftUI:使用
UIHostingController
包装View
。let vc = UIHostingController(rootView: ContentView())
-
嵌入 UIKit:通过
UIViewRepresentable
或UIViewControllerRepresentable
包装。 -
数据同步:使用
@ObservedObject
或@StateObject
共享状态。
100. 如何设计一个 高性能日志系统
?
答案:
-
异步写入:使用
DispatchQueue
分离日志记录与主线程。 -
分级过滤:支持
Error
、Debug
等级别,按需输出。 - 内存缓冲:批量写入磁盘,减少 I/O 操作。
-
崩溃保护:通过
mmap
或原子操作避免日志损坏。 - 符号化:集成崩溃堆栈的离线解析功能。