以下是一套系统的 Swift 面试题整理,涵盖核心语法、多线程编程、内存管理及主流框架,问题按模块分类并附详细解答:
一、Struct 与 Class 深度对比
1. 核心区别
- 值类型 vs 引用类型:
struct
是值类型(栈内存),class
是引用类型(堆内存) - 继承:
class
支持继承,struct
不支持 - 内存管理:
class
需要手动处理引用计数(ARC),struct
无需 - 线程安全:
struct
天然线程安全(值拷贝),class
需自行同步 - 性能:
struct
在小数据量时更高效(无堆分配和引用计数开销)
2. 初始化注意事项
- Struct:
- 自动生成成员初始化器(除非自定义
init
)struct Point {
var x: Int
var y: Int
// 自动生成 init(x: Int, y: Int)
}
- 自动生成成员初始化器(除非自定义
- Class:
- 必须为所有非可选属性赋初值(通过默认值或
init
) - 子类必须先初始化自身属性,再调用
super.init
class Person {
var name: String
init(name: String) { self.name = name }
}
- 必须为所有非可选属性赋初值(通过默认值或
二、Codable 协议实战
1. Class 支持 Codable
- 可以:只要所有存储属性均遵守
Codable
class User: Codable {
var id: Int
var name: String
}
2. 字段兼容技巧
- 字段名映射:使用
CodingKeys
枚举struct Book: Codable {
var title: String
var price: Double
enum CodingKeys: String, CodingKey {
case title = "book_title"
case price
}
} - 类型兼容:自定义
init(from:)
处理类型转换struct Product: Codable {
var id: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// 兼容接口返回 id 为 Int 或 String
if let intId = try? container.decode(Int.self, forKey: .id) {
id = String(intId)
} else {
id = try container.decode(String.self, forKey: .id)
}
}
}
三、Optional 本质剖析
- 本质:
Optional
是标准库中的泛型枚举public enum Optional<Wrapped> {
case none
case some(Wrapped)
} - 底层行为:编译器通过语法糖(
?
和!
)简化操作,实际仍走枚举模式匹配
四、多线程编程
1. GCD 核心概念
- 队列类型:
- Serial(串行):
DispatchQueue(label: "serial")
- Concurrent(并行):
DispatchQueue(label: "concurrent", attributes: .concurrent)
- Serial(串行):
- 常见操作:
DispatchQueue.global(qos: .background).async {
// 后台任务
DispatchQueue.main.async { /* 更新UI */ }
}
2. async/await (Swift Concurrency)
- 关键语法:
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
} - Task 使用:
Task {
let result = await someAsyncFunction()
print(result)
}
五、Swift 核心语法
1. Protocol 高级用法
- 关联类型(Associated Types):
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
} - 协议组合:
func foo(bar: SomeProtocol & AnotherProtocol)
2. 泛型约束
- Where 子句:
func process<T>(_ value: T) where T: Numeric {
print(value + value)
}
3. 闭包管理
- 逃逸闭包:标记为
@escaping
需注意循环引用class Manager {
var completion: (() -> Void)?
func setup(completion: @escaping () -> Void) {
self.completion = completion
}
}
六、内存管理(ARC)
1. 循环引用解决方案
- weak:
weak var delegate: SomeDelegate?
- unowned:
unowned let owner: SomeClass
(无主引用,必须确保对象存活) - 捕获列表:
lazy var closure: () -> Void = { [weak self] in
guard let self = self else { return }
self.doSomething()
}
七、响应式编程框架
1. Combine 核心组件
- Publisher/Subscriber 模式:
[1, 2, 3].publisher
.sink { print($0) }
2. RxSwift 核心操作符
- 常用链式调用:
Observable.of(1, 2, 3)
.filter { $0 > 1 }
.map { $0 * 2 }
.subscribe(onNext: { print($0) })
高频进阶面试题
为什么 Swift 推荐多用 Struct?
- 值类型线程安全、无引用计数开销、适合不可变数据场景
如何实现一个线程安全的单例?
class Singleton {
static let shared = Singleton()
private init() {}
}Protocol Extension 如何实现默认实现?
protocol Drawable { func draw() }
extension Drawable {
func draw() { print("Default implementation") }
}@MainActor 的作用是什么?
- 确保代码在主线程执行,替代
DispatchQueue.main.async
- 确保代码在主线程执行,替代
以下是一份全面覆盖 Objective-C 和 Swift 的 iOS 基础面试题,涵盖 内存管理、多线程、设计模式、网络、数据库、框架 等核心领域,并针对 Swift 语法(如 struct
/class
、Codable
、Optional
、多线程、闭包、响应式编程等)进行深入整理。
📌 iOS 基础面试题(Objective-C + Swift)
一、Objective-C 基础
1. 内存管理
- 什么是 ARC?MRC 和 ARC 的区别?
strong
、weak
、assign
、copy
、unsafe_unretained
的区别?- 循环引用的原因及解决方案(
weak
、block
弱引用)? autoreleasepool
的作用?使用场景?dealloc
方法的作用?能否调用[super dealloc]
?
2. Runtime
- 什么是 Runtime?动态特性有哪些?
objc_msgSend
的作用?消息转发流程(resolveInstanceMethod
、forwardingTargetForSelector
、methodSignatureForSelector
)?class
和meta-class
的区别?isKindOfClass
和isMemberOfClass
的区别?method swizzling
是什么?如何实现?有哪些坑?- KVO 的实现原理?如何手动触发 KVO?
3. 多线程
- GCD 的队列类型(
serial
、concurrent
、main
、global
)? dispatch_barrier_async
的作用?- NSOperation 和 GCD 的区别?
NSOperationQueue
如何控制并发数? @synchronized
的原理?它和NSLock
、dispatch_semaphore
的区别?- 多线程常见问题(死锁、线程安全、优先级反转)?
4. 设计模式
- MVC、MVP、MVVM 的区别?iOS 中 MVC 的问题?
- 单例模式的实现(线程安全)?
- Delegate 和 Notification 的区别?
- 工厂模式、观察者模式、责任链模式的应用场景?
- KVC 和 KVO 的使用场景?
二、Swift 基础
1. struct
vs class
struct
和class
的核心区别(值类型 vs 引用类型、内存管理、继承等)?struct
自动生成init
,class
必须手动初始化?mutating
关键字的作用?final
关键字的作用?
2. Optional
Optional
的本质是什么?(enum
:.none
和.some(Wrapped)
)??
、if let
、guard let
、optional chaining
的区别?@unwrapped
和隐式解包(String!
)的区别?
3. 协议 & 泛型
Protocol
的associatedtype
是什么?如何约束?where
子句在泛型中的作用?Self
和self
的区别?
4. 闭包
- 逃逸闭包(
@escaping
)和非逃逸闭包的区别? [weak self]
和[unowned self]
的区别?lazy var
+ 闭包初始化?
5. 多线程(Swift)
- GCD 在 Swift 中的使用(
DispatchQueue
、DispatchGroup
)? async/await
和Task
的使用?@MainActor
的作用?
6. Codable
class
可以遵守Codable
吗?如何实现?- 如何用
CodingKeys
映射 JSON 字段? - 如何处理接口返回的
Int
/String
兼容问题?
7. 内存管理(Swift)
weak
、unowned
、strong
的区别?- 循环引用在 Swift 中的常见场景及解决方案?
deinit
的作用?
8. 响应式编程
- Combine 的核心组件(
Publisher
、Subscriber
、Subject
)? - RxSwift 的常见操作符(
map
、filter
、flatMap
)? Observable
和Future
的区别?
三、iOS 开发基础
1. UI & 动画
UIView
和CALayer
的区别?frame
、bounds
、center
的区别?- Auto Layout 的原理?
intrinsicContentSize
的作用? UIView.animate
和Core Animation
的区别?- 离屏渲染(
cornerRadius
、shadow
)的优化?
2. 网络
- HTTP 和 HTTPS 的区别?HTTPS 的握手过程?
URLSession
的使用(dataTask
、uploadTask
、downloadTask
)?Alamofire
的核心实现?Cookie
和Session
的区别?
3. 数据库
Core Data
和Realm
的区别?- SQLite 的增删改查(
FMDB
的使用)? @FetchRequest
在 SwiftUI 中的作用?
4. 性能优化
- 卡顿检测(
CADisplayLink
、Instruments
)? - 内存泄漏检测(
Leaks
、MLeaksFinder
)? - 图片加载优化(
SDWebImage
的缓存策略)?
5. 架构 & 设计模式
- 如何设计一个网络层?
- 如何实现一个路由(URL Router)?
- 依赖注入(DI)的实现方式?
📌 高频 Swift 面试题
1. struct
vs class
- 值类型 vs 引用类型
- 内存分配(栈 vs 堆)
- 线程安全性
mutating
关键字的作用
2. Codable
class
如何遵守Codable
?CodingKeys
的使用- 处理接口字段不一致(
Int
/String
兼容)
3. 多线程
- GCD(
DispatchQueue
、DispatchGroup
) async/await
的使用@MainActor
的作用
4. 内存管理
weak
vsunowned
- 循环引用的解决方案
5. 响应式编程
- Combine 的核心概念
- RxSwift 的常见操作符
📌 总结
- Objective-C:重点在 内存管理(ARC)、Runtime、多线程(GCD)、设计模式(Delegate、KVO)
- Swift:重点在
struct
/class
、Optional
、Codable
、多线程(async/await
)、内存管理 - iOS 基础:UI(Auto Layout)、网络(
URLSession
)、数据库(Core Data
)、性能优化
以下是为资深 iOS 开发工程师设计的高阶综合面试题,涵盖底层原理、架构设计、性能优化及前沿技术,分为 语言深度、系统机制、架构设计、性能优化 和 综合场景 五大模块:
一、语言深度与底层原理
1. Swift 高阶特性
泛型与协议
- 如何设计一个支持泛型缓存的
Repository
模式? associatedtype
与some
、any
关键字的区别?Swift 5.7 的不透明类型优化了什么?- 为什么
Protocol
不能直接作为类型使用?如何用类型擦除(Type Erasure)解决?
- 如何设计一个支持泛型缓存的
内存管理
- Swift 的
unowned
和weak
在底层是如何实现的?何时会导致野指针? withUnsafeBytes
和withMemoryRebound
的使用场景?如何安全操作指针?
- Swift 的
并发模型
actor
的隔离机制如何实现?与@MainActor
的线程调度差异?Sendable
协议的作用?如何让自定义类型支持跨线程传递?
2. Objective-C 底层
Runtime 进阶
objc_msgSend
的快速查找(缓存)和慢速查找(方法列表)流程?- 如何通过
NSInvocation
动态调用任意参数的方法? class_ro_t
和class_rw_t
的区别?方法列表的动态扩展如何实现?
内存管理
NSCache
和NSDictionary
的内存策略差异?如何实现 LRU 缓存?__weak
变量的底层实现(SideTable 结构)?
二、系统机制与框架原理
1. UIKit 与渲染
渲染流水线
- Core Animation 的
CALayer
提交到 GPU 的完整流程? - 离屏渲染(Offscreen Rendering)的底层触发条件及优化方案?
- Core Animation 的
事件传递
hitTest:withEvent:
和pointInside:withEvent:
的调用链路?如何实现穿透点击?
2. 网络与安全
HTTP/2 与 QUIC
- HTTP/2 的多路复用如何提升性能?与 QUIC 协议的差异?
- iOS 如何实现 TLS 1.3 的 0-RTT 加速?
长连接优化
- 如何设计一个支持断线重连、心跳保活的 WebSocket 客户端?
3. 数据库与持久化
Core Data 进阶
- 如何优化
NSFetchedResultsController
在大数据量下的性能? - 多线程环境下
NSManagedObjectContext
的合并策略(MergePolicy
)?
- 如何优化
SQLite 优化
- WAL(Write-Ahead Logging)模式的工作原理?如何避免
SQLITE_BUSY
错误?
- WAL(Write-Ahead Logging)模式的工作原理?如何避免
三、架构设计与模式
1. 架构演进
从 MVC 到 MVVM
- 如何在不引入 Reactive 框架的情况下实现 MVVM 的数据绑定?
ViewModel
如何避免持有View
导致的循环引用?
模块化与组件化
- 如何设计一个支持动态插拔的模块化架构(如路由 + Protocol)?
CocoaPods
与Swift Package Manager
的二进制化方案?
2. 设计模式实战
响应式编程
- Combine 的
Publisher
冷热信号差异?如何实现自定义Publisher
? - RxSwift 的
flatMap
与flatMapLatest
在搜索场景中的应用?
- Combine 的
依赖注入
- 如何利用
Property Wrapper
实现类型安全的 DI 容器?
- 如何利用
四、性能优化与调试
1. 性能调优
启动优化
dyld
动态链接的耗时如何测量?如何减少__DATA
段的修复时间?+load
和+initialize
的优化策略?
内存优化
- 如何通过
VM Tracker
分析内存碎片问题? NSCache
的totalCostLimit
策略与内存警告的关系?
- 如何通过
2. 调试与逆向
LLDB 高阶
- 如何通过
image lookup -a
定位野指针崩溃? - 动态修改
UIView
属性的 LLDB 命令?
- 如何通过
逆向分析
- 如何通过
Hopper
静态分析第三方库的私有 API 调用?
- 如何通过
五、综合场景与前沿技术
1. 复杂场景设计
跨平台方案
- SwiftUI 与 Flutter 的渲染引擎差异?如何实现高性能的跨平台列表?
大文件传输
- 如何设计一个支持断点续传、分块校验的文件上传服务?
2. 前沿技术
机器学习
- Core ML 的模型加密与动态更新方案?
AR 与 Metal
- 如何通过
Metal
实现自定义的 AR 渲染管线?
- 如何通过
六、实战编码题
实现线程安全的 LRU 缓存
final class LRUCache<Key: Hashable, Value> {
private let lock = NSLock()
private let capacity: Int
private var cache: [Key: Value] = [:]
private var order: [Key] = []
init(capacity: Int) { self.capacity = capacity }
func get(_ key: Key) -> Value? {
lock.lock()
defer { lock.unlock() }
guard let value = cache[key] else { return nil }
order.removeAll { $0 == key }
order.append(key)
return value
}
func set(_ key: Key, value: Value) {
lock.lock()
defer { lock.unlock() }
if cache[key] == nil && cache.count >= capacity {
let removedKey = order.removeFirst()
cache[removedKey] = nil
}
cache[key] = value
order.append(key)
}
}基于 Combine 实现防抖搜索
import Combine
class SearchViewModel {
var query: String = ""
private var cancellables = Set<AnyCancellable>()
init() {
$query
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] query in
self?.performSearch(query: query)
}
.store(in: &cancellables)
}
private func performSearch(query: String) {
print("Searching for: \(query)")
}
}
考察重点
- 深度:是否理解语言特性背后的底层机制(如 Swift 的
Optional
枚举、OC 的消息转发)。 - 架构能力:能否设计高扩展、低耦合的模块化方案。
- 性能敏感度:对渲染管线、内存管理等系统级优化的实践经验。
- 技术前瞻性:对 Combine、Swift Concurrency 等新技术的落地思考。
在 Swift 中,属性的 getter、setter 及其观察器(willSet
、didSet
)的生命周期和调用顺序是属性管理的核心机制。以下是它们的详细解析:
1. 基本结构
Swift 中的属性可以分为:
- 存储属性(Stored Property):直接存储值,可附加观察器。
- 计算属性(Computed Property):通过 getter/setter 动态计算值,不可附加观察器。
示例代码
struct Person { |
2. Getter 和 Setter 的生命周期
(1) 计算属性(Computed Property)
- Getter:每次访问属性时调用。
- Setter:每次赋值时调用(必须有
set
才能修改值)。var person = Person(name: "Alice")
print(person.uppercaseName) // 调用 getter
person.uppercaseName = "Bob" // 调用 setter → 触发 name 的观察器
(2) 存储属性(Stored Property)
- 默认行为:直接读写存储的值。
- 附加观察器:通过
willSet
和didSet
监听变化。
3. 观察器(willSet
和 didSet
)的生命周期
调用顺序
当属性值被修改时,触发顺序如下:
- 原始值:保存当前值(用于
didSet
的oldValue
)。 willSet
:新值即将写入(可访问newValue
)。- 赋值操作:实际修改存储的值。
didSet
:新值已写入(可访问oldValue
)。
示例流程
var person = Person(name: "Alice") |
4. 关键区别与作用
机制 | 触发时机 | 可访问的值 | 用途 |
---|---|---|---|
Getter | 每次读取属性时 | 返回计算值 | 动态计算属性值(如格式化数据)。 |
Setter | 每次赋值时(计算属性) | newValue |
验证或处理新值(如过滤非法输入)。 |
willSet |
值被写入前 | newValue |
执行前置操作(如日志记录、触发 UI 更新)。 |
didSet |
值被写入后 | oldValue |
执行后置操作(如数据同步、发送通知)。 |
5. 高级场景与注意事项
(1) 初始化阶段不触发观察器
- 观察器仅在初始化完成后生效:
init(name: String) {
self.name = name // 不会触发 willSet/didSet
}
(2) 避免在 didSet
中重复触发修改
- 死循环风险:解决方案:通过临时变量或条件判断避免循环。
var score: Int = 0 {
didSet {
score = min(score, 100) // 会再次触发 didSet!
}
}
(3) 计算属性与观察器的互斥
- 计算属性不能附加观察器(因为它的值由 getter/setter 动态决定)。
(4) 性能影响
- 观察器会增加调用开销:高频修改的属性需谨慎使用。
6. 综合示例
class Temperature { |
总结
- Getter/Setter:控制属性的读写行为(计算属性专属)。
willSet
/didSet
:监听存储属性的变化(初始化时不触发)。设计原则:
- 用计算属性封装派生数据。
- 用观察器处理副作用(如持久化、UI 更新)。
以下是一套详尽的 Swift 面试题与解析,涵盖你提到的关键点,包括结构体与类的差异、初始化、
Codable
协议、Optional
本质、以及多线程编程(GCD 与async/await
)等内容。
🧱 一、Struct 与 Class 的区别
特性 | Struct | Class |
---|---|---|
类型 | 值类型(Value Type) | 引用类型(Reference Type) |
存储方式 | 每次赋值都会复制一份 | 多个引用指向同一实例 |
继承 | 不支持继承 | 支持继承 |
ARC 管理 | 不使用 ARC | 使用 ARC |
可变性 | let 定义不可变 |
let 定义的类实例,其属性可变 |
可用于哪些场景 | 更适合轻量模型、不可变数据 | 更适合有生命周期管理、共享状态的对象 |
结构体适用场景
- 数据模型(如坐标、尺寸)
- SwiftUI View
- Codable 轻量数据解析
🔧 二、Struct 与 Class 初始化注意事项
Struct 初始化
- 自动生成
memberwise initializer
(逐一成员初始化器) - 如果你自定义了
init()
,系统不再自动生成默认的成员初始化器
struct Person { |
Class 初始化
- 必须保证所有非可选属性在
init
中初始化 - 可以定义
convenience
初始化器调用designated
初始化器 - 支持继承链中的初始化顺序
class Animal { |
🧬 三、Class 是否可以遵守 Codable 协议?
✅ 可以。
class User: Codable { |
注意:
- 如果包含自定义 init 或继承结构体,需手动实现
encode(to:)
和init(from:)
- 遇到嵌套 class 时,使用 JSONDecoder 会递归解码
🧩 四、如何处理 Codable 字段名/类型不一致
✅ 方法一:使用 CodingKeys
自定义字段映射
struct User: Codable { |
✅ 方法二:字段类型不一致,手动 decode/encode
struct User: Codable { |
❓ 五、Optional 的本质是什么?
enum Optional<Wrapped> { |
Optional
是一个泛型enum
,有两个 casenil
实际上就是.none
- 常用
if let
/guard let
/??
等语法糖简化解包过程
🚦 六、Swift 多线程编程
1. GCD(Grand Central Dispatch)
异步队列 & 同步队列
DispatchQueue.global().async { |
延迟执行
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { |
并发队列 & 串行队列
let serialQueue = DispatchQueue(label: "serial") |
DispatchGroup
let group = DispatchGroup() |
2. Swift 5.5+ async/await(推荐)
func fetchData() async -> String { |
并发执行任务
async let a = loadA() |
TaskGroup 使用
await withTaskGroup(of: Int.self) { group in |
🧠 七、补充高级面试题建议
❓ Swift 中 enum 是值类型还是引用类型?
- 值类型
- 可以携带关联值
- 可以实现协议、支持模式匹配(
switch
)
❓ Swift 中 mutating 关键字作用?
- 允许在
struct
或enum
的方法中修改self
或属性值
❓ Swift 如何防止 retain cycle?
- 使用
weak
/unowned
引用 - 注意闭包捕获列表:
[weak self]
/[unowned self]
以下是对 Swift 更高阶语法领域的详尽面试题扩展,涵盖 Protocol
、泛型、闭包、高阶函数、Combine、RxSwift 及 ARC 内存管理。
🧩 一、Protocol 协议相关面试题
❓ Protocol 和 Class/Struct 有什么不同?
特性 | Protocol | Class/Struct |
---|---|---|
目的 | 描述行为规范 | 数据结构定义 |
是否可继承 | 支持多继承 | Class 仅支持单继承 |
是否可实例化 | ❌ 不可 | ✅ 可以 |
可否添加默认实现 | ✅ 通过 extension | ✅ 直接实现 |
❓ 协议中的 associatedtype
有什么作用?
- 定义占位符类型,用于泛型协议
protocol DataSource { |
- 不能直接在
let x: DataSource
中使用,需使用泛型约束some DataSource
❓ Protocol Extension 有什么用?
- 为协议统一添加默认实现
- 允许扩展功能而不破坏已有 conformer 的代码
extension CustomStringConvertible { |
🧬 二、泛型(Generics)
❓ 如何写一个泛型函数?
func swapTwo<T>(_ a: inout T, _ b: inout T) { |
❓ 泛型约束写法有哪些?
func printDescription<T: CustomStringConvertible>(_ value: T) { |
- 多协议约束:
T: A & B
- where 语句:
where T.Element == String
func allStrings<T: Sequence>(input: T) where T.Element == String { |
🧠 三、闭包(Closure)
❓ 闭包与函数的区别?
- 闭包是“可捕获上下文”的代码块
- 函数是命名的、不能捕获上下文
❓ 闭包的语法简化过程?
// 原始 |
❓ 如何避免闭包导致的循环引用?
- 使用
[weak self]
或[unowned self]
捕获列表
loadData { [weak self] result in |
📚 四、高阶函数面试题
常见高阶函数总结
函数 | 功能 |
---|---|
map |
转换每个元素 |
filter |
过滤元素 |
reduce |
聚合为一个值 |
compactMap |
去除 nil 并转换 |
flatMap |
展平嵌套结构 |
面试示例题
let names = ["A", "B", "C"] |
🔁 五、Combine 基础面试题
❓ Combine 的核心组件有哪些?
组件 | 说明 |
---|---|
Publisher | 发布者,发出数据流 |
Subscriber | 订阅者,接收数据流 |
Subject | 可作为 Publisher 和订阅点 |
Operator | 转换数据流,如 map , filter |
❓ 示例:使用 Combine 实现双向绑定
var username: String = "" |
🌊 六、RxSwift 基础面试题
❓ RxSwift 与 Combine 的异同?
项目 | RxSwift | Combine |
---|---|---|
苹果支持 | ❌ 第三方 | ✅ 官方 |
平台 | iOS 9+ | iOS 13+ |
错误处理 | onError |
Failure 泛型 |
资源管理 | DisposeBag |
AnyCancellable |
❓ 如何创建 Observable?
Observable<String>.create { observer in |
🗑️ 七、ARC 内存管理面试题
❓ Swift 的 ARC 如何工作?
- 自动引用计数(ARC):每次引用计数 +1/-1
- 当计数为 0 时释放内存
❓ strong / weak / unowned 区别?
修饰符 | 特点 |
---|---|
strong |
默认,增加引用计数 |
weak |
不增加计数,自动设为 nil |
unowned |
不增加计数,不设为 nil(危险) |
❓ 如何解决循环引用(Retain Cycle)?
- 弱引用委托模式
delegate: SomeProtocol?
- 闭包中使用
[weak self]
捕获
🧪 Bonus:Swift 面试设计题
- 实现一个泛型缓存类
- 使用 RxSwift 实现登录输入校验
- Combine 版网络请求封装器
- 如何用闭包替代 delegate?
- 使用协议实现多种网络层解耦
如果你希望我把这套完整面试题集整理成 Markdown、PDF、Xcode Playground、或面试笔记格式,我可以立即帮你生成。是否需要?或者还想深入 SwiftUI、内存布局、性能优化、源码解读等方向?
iOS 面试题大全(Objective-C + Swift)
一、语言基础
Objective-C
- Objective-C 的类和对象结构?
- 什么是 Category 和 Extension?有什么区别?
- 什么是 Selector?怎么调用?
- id 和 instancetype 的区别?
- @property 的属性修饰符有哪些?
- 什么是 KVC 和 KVO?原理是什么?
- 什么是 Runtime?你用过哪些 Runtime 特性?
- 如何实现一个动态方法交换(Method Swizzling)?
Swift
- struct 和 class 的区别?
- struct、class 初始化时属性/init 的注意事项?
- class 是否可以服从 Codable 协议?
- 如何利用 Codable 协议兼容接口字段名和类型?
- Optional 的本质是什么?
- Swift 是值语义还是引用语义?
- 什么是 mutating 关键字?
- enum 的高级用法有哪些?关联值、递归枚举?
二、内存管理
- ARC 是什么?如何工作的?
- 强引用、弱引用、unowned 区别?
- 如何避免循环引用?delegate 用 weak?
- Block(ObjC)或 Closure(Swift)如何导致循环引用?
- @autoreleasepool 是做什么的?
- MRC 和 ARC 有哪些不同?是否可以混用?
三、多线程与并发
GCD
- GCD 的基本概念?同步/异步,串行/并行队列?
- 如何使用 DispatchGroup 实现任务组?
- dispatch_barrier 有什么作用?
- 如何使用信号量 DispatchSemaphore 控制线程?
OperationQueue
- NSOperation 的优点是什么?如何自定义?
- 如何设置依赖?如何取消操作?
Swift async/await
- Swift 中如何使用 async/await 实现并发任务?
- async let 和 TaskGroup 的区别与使用场景?
- actor 是什么?如何解决数据竞争?
四、网络编程
- NSURLSession 常用方法?
- 如何处理 JSON?手动解析 vs Codable?
- 如何处理 HTTPS 双向验证?
- 如何实现断点续传?
- AFNetworking 和 Alamofire 的底层原理?
五、数据库与持久化
- UserDefaults 用法和原理?
- plist 存储适合什么场景?
- Core Data 的基本使用?NSManagedObject?
- 如何设计 Core Data 的线程模型?
- FMDB 和 SQLite 如何使用?
六、设计模式
- MVC、MVP、MVVM 有什么区别?
- 单例模式如何实现(ObjC 和 Swift)?
- 观察者模式在 iOS 中的实现?
- 代理模式如何实现?delegate vs closure?
- 响应链模式(Responder Chain)是什么?
七、常用框架
Combine
- Combine 的核心概念(Publisher/Subscriber/Operator)?
- @Published 和 ObservableObject 有什么区别?
- 如何取消订阅?AnyCancellable?
RxSwift
- 什么是 Observable?Observer?
- 如何使用 Subjects?(Publish/Behavior/Replay/Async)
- DisposeBag 有什么用?
- RxSwift 与 Combine 有哪些异同?
八、高阶 Swift 语法
- 泛型如何定义?如何约束?
- 如何实现一个泛型缓存类?
- Protocol 和 associatedtype 的高级用法?
- Swift 的闭包语法和使用?
- 什么是逃逸闭包(@escaping)?
- 高阶函数:map/filter/reduce/flatMap/compactMap 的用法?
- 如何使用 Swift 的 Result 类型处理异步任务?
九、其他基础知识
- App 的启动过程是什么?
- iOS 的事件传递机制?(Hit-Testing)
- UIResponder 的作用?
- 通知中心 NotificationCenter 原理?
- iOS 的沙盒机制与目录结构?
- App 生命周期与场景管理(SceneDelegate)?
欢迎根据职位要求重点准备某些模块,是否还需要我整理为 PDF 或可搜索的 Markdown 文件?
以下是对文档内容的进一步完善建议,包括新增几个关键面试模块与问题点,特别是:
- Swift 的属性包装器(Property Wrappers)
- Swift 的访问控制与模块化
- iOS 性能优化与调试工具
- 测试与 CI/CD 流程
- iOS 安全机制
你可以选择我来更新文档,也可以先看看新增内容如下:
十、Swift 进阶机制
- Property Wrapper 的原理和常见用法(@State、@Published、@UserDefault 等)?
- Swift 的访问控制(open、public、internal、fileprivate、private)有哪些场景?
- Swift 的模块化开发方式?如何拆分模块?
- 如何实现依赖注入(DI)?有无使用框架(如 Resolver、Swinject)?
十一、性能优化与调试
- Instruments 中常用工具有哪些?Time Profiler、Allocations、Leaks 的使用?
- 如何查找内存泄漏和性能瓶颈?
- View 的离屏渲染(Offscreen Rendering)问题及优化?
- 如何优化列表滚动性能(特别是 SwiftUI)?
十二、测试与 CI/CD
- 单元测试、UI 测试的区别和框架(XCTest/XCUITest)?
- 如何进行 Mock 测试?
- 使用 Xcode Server、Jenkins、GitHub Actions 做 CI/CD?
- Fastlane 的基本使用方法?
十三、iOS 安全性
- iOS 应用如何做本地数据加密(Keychain 使用)?
- 什么是代码混淆与反调试手段?
- iOS 的 App Transport Security(ATS)是什么?
- 如何处理用户隐私合规问题(IDFA、App Tracking Transparency)?
如你希望我将这些内容补充进当前 Canvas 文档,请回复“请更新文档”;或者如希望将其导出为 PDF 或 Markdown 文件,我也可直接生成。是否需要我继续整理更高阶的系统设计题?
在 Swift 中,property getter
和 setter
是属性访问的核心机制,允许你自定义属性的读取和写入行为。而 willSet
和 didSet
是 Swift 中存储属性的观察者(Property Observers),用于在属性即将被设置前或刚被设置后响应操作。下面详细介绍这两类机制及它们的生命周期、作用与区别。
一、Getter 与 Setter(计算属性)
**计算属性(Computed Property)**使用 get
和 set
明确地声明读取和写入逻辑。
struct Rectangle { |
生命周期:
get
在每次读取属性时调用。set
在每次设置属性值时调用。set
可省略参数名,使用系统默认的newValue
。- 计算属性 不能使用 willSet/didSet(因为它们没有存储值)。
二、willSet 与 didSet(存储属性观察者)
观察者用于监听存储属性的变化,常用于副作用操作(如 UI 更新、日志记录)。
class Person { |
生命周期和执行顺序:
(设置属性值发生时) |
特点和规则:
特性 | willSet | didSet |
---|---|---|
触发时机 | 设置前 | 设置后 |
是否可访问新旧值 | newValue (默认名) |
oldValue (默认名) |
可否更改新值 | ❌(只读) | ✅(但再次触发 willSet) |
对比 get/set | 是副作用处理 | 非属性逻辑/行为型处理 |
对比 get/set 应用场景 | 响应式(通知/记录/限制等) | 不是值计算 |
三、get/set 与 willSet/didSet 的本质区别
项目 | get / set(计算属性) | willSet / didSet(观察者) |
---|---|---|
是否存在存储值 | ❌ 不存储数据,只计算 | ✅ 是真实的存储属性 |
可否添加计算逻辑 | ✅(如 area 计算) | ✅(响应变化,如 UI 刷新) |
支持作用范围 | struct、class、enum 中所有属性 | 仅限 存储属性(不适用于计算属性) |
使用场景 | 自定义属性值逻辑,如转换、代理其他属性 | 监听属性值变化,例如通知、同步更新 |
四、实践建议
- 如果你想自定义 属性值的逻辑(如格式转换),使用
get/set
。 - 如果你想在 属性变化时响应操作(如 UI 通知或日志记录),使用
willSet/didSet
。 - 避免在
didSet
中再次改变该属性值,容易引发递归循环。 - 只在确实需要响应属性变化时使用观察者,否则可能影响性能或可读性。
在 Swift 中,属性的 getter、setter 及其观察器(willSet
、didSet
)的生命周期和调用顺序是属性管理的核心机制。以下是它们的详细解析:
1. 基本结构
Swift 中的属性可以分为:
- 存储属性(Stored Property):直接存储值,可附加观察器。
- 计算属性(Computed Property):通过 getter/setter 动态计算值,不可附加观察器。
示例代码
struct Person { |
2. Getter 和 Setter 的生命周期
(1) 计算属性(Computed Property)
- Getter:每次访问属性时调用。
- Setter:每次赋值时调用(必须有
set
才能修改值)。var person = Person(name: "Alice")
print(person.uppercaseName) // 调用 getter
person.uppercaseName = "Bob" // 调用 setter → 触发 name 的观察器
(2) 存储属性(Stored Property)
- 默认行为:直接读写存储的值。
- 附加观察器:通过
willSet
和didSet
监听变化。
3. 观察器(willSet
和 didSet
)的生命周期
调用顺序
当属性值被修改时,触发顺序如下:
- 原始值:保存当前值(用于
didSet
的oldValue
)。 willSet
:新值即将写入(可访问newValue
)。- 赋值操作:实际修改存储的值。
didSet
:新值已写入(可访问oldValue
)。
示例流程
var person = Person(name: "Alice") |
4. 关键区别与作用
机制 | 触发时机 | 可访问的值 | 用途 |
---|---|---|---|
Getter | 每次读取属性时 | 返回计算值 | 动态计算属性值(如格式化数据)。 |
Setter | 每次赋值时(计算属性) | newValue |
验证或处理新值(如过滤非法输入)。 |
willSet |
值被写入前 | newValue |
执行前置操作(如日志记录、触发 UI 更新)。 |
didSet |
值被写入后 | oldValue |
执行后置操作(如数据同步、发送通知)。 |
5. 高级场景与注意事项
(1) 初始化阶段不触发观察器
- 观察器仅在初始化完成后生效:
init(name: String) {
self.name = name // 不会触发 willSet/didSet
}
(2) 避免在 didSet
中重复触发修改
- 死循环风险:解决方案:通过临时变量或条件判断避免循环。
var score: Int = 0 {
didSet {
score = min(score, 100) // 会再次触发 didSet!
}
}
(3) 计算属性与观察器的互斥
- 计算属性不能附加观察器(因为它的值由 getter/setter 动态决定)。
(4) 性能影响
- 观察器会增加调用开销:高频修改的属性需谨慎使用。
6. 综合示例
class Temperature { |
总结
- Getter/Setter:控制属性的读写行为(计算属性专属)。
willSet
/didSet
:监听存储属性的变化(初始化时不触发)。- 设计原则:
- 用计算属性封装派生数据。
- 用观察器处理副作用(如持久化、UI 更新)。
- 避免在观察器中修改自身属性导致循环。