一、Swift 属性观察器(Property Observers)
1. 核心概念
属性观察器用于监听存储属性值的变化,在值被修改前后执行自定义逻辑。包含两种观察器:
willSet
:在值即将被设置前 调用,默认提供newValue
参数(新值)。didSet
:在值已被设置后 调用,默认提供oldValue
参数(旧值)。
2. 使用条件
-
适用对象 :仅用于 非
lazy
的var
存储属性 。swiftvar score: Int = 0 { // 正确:非 lazy 的存储属性 willSet { print("新值:\(newValue)") } didSet { print("旧值:\(oldValue)") } } // lazy var data: [Int] = loadData() { willSet { } } // 错误:lazy 属性不支持
-
不适用场景 :
- 常量(
let
属性)。 - 计算属性(需通过
set
方法监听)。 - 延迟存储属性(
lazy var
)。
- 常量(
3. 注意事项
-
初始化不触发 :属性在初始化赋值时不会调用观察器。
swiftstruct User { var name: String = "Guest" { // 初始化赋值不会触发观察器 didSet { print("名字被修改") } } }
-
自定义参数名 :可显式命名参数(如
willSet(newScore)
)。 -
避免循环修改 :在
didSet
中修改属性本身会再次触发观察器。
4. 使用场景
-
数据验证 :确保值在合法范围内。
swiftvar age: Int = 0 { didSet { age = min(max(age, 0), 120) // 限制年龄在 0~120 } }
-
UI 同步更新 :值变化时刷新界面。
swiftvar isLoggedIn: Bool = false { didSet { updateLoginUI() } }
-
日志记录 :跟踪属性变化历史。
swiftvar temperature: Double = 25.0 { willSet { print("温度将从 \(temperature) 变为 \(newValue)") } didSet { print("温度已从 \(oldValue) 更新为 \(temperature)") } }
5. 与计算属性的区别
特性 | 属性观察器 | 计算属性 |
---|---|---|
用途 | 监听存储属性变化 | 通过计算动态获取值 |
触发时机 | 属性值被修改前后 | 每次访问时计算 |
存储能力 | 需有存储空间(必须初始化) | 无存储空间(依赖其他属性) |
语法 | 仅 willSet /didSet |
get + set (或只读 get ) |
6. 总结
- 核心作用:在属性值变化前后注入逻辑,提升代码灵活性和可维护性。
- 适用场景:数据验证、日志记录、UI 同步等。
- 限制 :仅适用于非
lazy
的var
存储属性,初始化不触发。
二、Swift 中的异常捕获
Swift 通过 Error
协议和 throws
关键字实现异常处理,提供多种方式捕获和处理错误。以下是主要的异常捕获方法:
1. do-catch
语句(详细错误处理)
-
用法:捕获特定错误类型并处理。
-
适用场景:需要根据不同错误类型执行不同逻辑。
-
示例 :
swiftenum NetworkError: Error { case invalidURL case timeout(seconds: Int) } func fetchData() throws { throw NetworkError.timeout(seconds: 30) } do { try fetchData() } catch NetworkError.invalidURL { print("URL 无效") } catch NetworkError.timeout(let seconds) { print("请求超时:\(seconds) 秒") } catch { print("未知错误:\(error)") }
2. try?
(转换为可选类型)
-
用法 :忽略具体错误,返回
nil
。 -
适用场景:不关心错误细节,只需判断是否成功。
-
示例 :
swiftlet result = try? someThrowingFunction() if let data = result { // 成功 } else { // 失败 }
3. try!
(强制解包,慎用)
-
用法:假设函数不会抛出错误,若出错则触发运行时崩溃。
-
适用场景:确信代码不会失败(如本地静态数据加载)。
-
示例 :
swiftlet data = try! loadLocalConfig() // 确定本地文件存在时使用
4. 向上传递错误(throws
关键字)
-
用法 :函数声明
throws
,错误由调用者处理。 -
适用场景:将错误传递给上层调用链。
-
示例 :
swiftfunc processFile() throws { let content = try String(contentsOfFile: "path/to/file") // 处理内容 } // 调用处需处理或继续传递 do { try processFile() } catch { print("文件处理失败:\(error)") }
5. rethrows
(传递闭包中的错误)
-
用法:函数本身不产生错误,但可能传递闭包的异常。
-
适用场景 :高阶函数(如
map
、filter
)中处理闭包可能抛出的错误。 -
示例 :
swiftfunc customMap<T>(_ array: [T], _ transform: (T) throws -> T) rethrows -> [T] { var result = [T]() for item in array { try result.append(transform(item)) } return result } let numbers = [1, 2, 3] let doubled = try? customMap(numbers) { num in if num == 2 { throw NetworkError.invalidURL } return num * 2 }
6. 异步错误处理(async
/await
)
-
用法 :在异步函数中使用
throws
和try
。 -
适用场景:异步操作中的错误处理。
-
示例 :
swiftfunc downloadData() async throws -> Data { let url = URL(string: "https://example.com")! let (data, _) = try await URLSession.shared.data(from: url) return data } Task { do { let data = try await downloadData() } catch { print("下载失败:\(error)") } }
总结
方法 | 关键字 | 特点 | 适用场景 |
---|---|---|---|
详细错误处理 | do-catch |
精准捕获错误类型 | 需要区分不同错误逻辑 |
可选值简化 | try? |
忽略错误,返回 nil |
不关心错误细节 |
强制解包(高危) | try! |
假设成功,崩溃风险高 | 确定不会失败的场景 |
向上传递错误 | throws |
将错误传递给调用者 | 多层调用链中的错误处理 |
闭包错误传递 | rethrows |
传递闭包中的错误 | 高阶函数中的闭包操作 |
异步错误处理 | async throws |
结合 async /await 处理异步错误 |
网络请求、文件读写等异步操作 |
最佳实践:
- 优先使用
do-catch
处理可预见的错误。 - 谨慎使用
try!
,确保代码绝对安全。 - 异步操作中结合
async
/await
提升可读性。
三、Swift 中 defer
关键字的详解
1. 基本概念
defer
用于定义一个代码块(延迟执行块 ),该代码块会在 当前作用域结束前 执行,无论作用域是通过正常返回、抛出错误还是其他控制流(如 return
、break
)结束的。
核心作用:确保清理或收尾逻辑(如资源释放)一定会执行,避免资源泄漏。
2. 使用场景
- 资源管理:文件操作、网络请求、锁的获取与释放等。
- 错误处理:在抛出错误前确保清理逻辑执行。
- 代码可读性:将清理代码紧跟在初始化代码后,提高可维护性。
3. 基本用法
swift
func readFile() {
let file = openFile()
defer {
closeFile(file) // 作用域结束前执行
}
// 处理文件...
if someCondition {
return // 触发 defer
}
// 更多操作...
} // 函数结束,触发 defer
4. 执行规则
-
逆序执行 :同一作用域内的多个
defer
按 定义顺序的逆序 执行(类似栈结构)。swiftdefer { print("1") } defer { print("2") } // 输出:2 → 1
-
作用域限制 :
defer
仅在 当前作用域 有效(如函数、循环、条件语句)。swiftfunc example() { if condition { defer { print("if 块结束") } // ... } // 此处执行 defer // 函数后续代码... }
5. 常见应用示例
(1) 文件操作
swift
func readFile(path: String) throws -> String {
let file = try openFile(path)
defer {
closeFile(file) // 确保文件关闭
}
return try parseFile(file)
}
(2) 加锁与解锁
swift
let lock = NSLock()
func criticalSection() {
lock.lock()
defer { lock.unlock() } // 确保锁释放
// 执行关键代码...
}
(3) 循环中的资源清理
swift
for url in urls {
let data = try downloadData(from: url)
defer {
cleanupTemporaryFiles() // 每次循环结束清理
}
process(data)
}
6. 注意事项
-
禁止控制流操作 :
defer
块中不能使用break
、return
或抛出错误。 -
避免副作用 :修改外部变量可能导致逻辑混乱。
swiftvar value = 0 func example() { defer { value += 1 } // 不推荐 // ... }
-
性能影响 :频繁使用
defer
可能影响性能(极少数情况)。
7. 错误处理中的 defer
即使函数抛出错误,defer
仍会执行:
swift
func riskyOperation() throws {
let resource = allocateResource()
defer { releaseResource(resource) } // 错误抛出前执行
try mayThrowError()
}
8. 总结
场景 | 示例 | 作用 |
---|---|---|
文件/网络资源释放 | defer { file.close() } |
防止资源泄漏 |
锁的获取与释放 | defer { lock.unlock() } |
避免死锁 |
错误处理中的清理 | defer { cleanup() } |
确保异常时资源释放 |
临时数据清理 | defer { removeTempFiles() } |
提升代码健壮性 |
最佳实践:
- 将
defer
紧跟在资源获取代码后,提高可读性。 - 避免在
defer
中执行耗时操作或修改外部状态。 - 优先用于必须执行的清理逻辑,而非复杂业务代码。
通过合理使用 defer
,可以显著提升代码的健壮性和可维护性,确保资源安全和逻辑清晰。
四、Swift 与 Objective-C 中的 Protocol 区别
1. 基本概念
- Objective-C :Protocol 是一种定义方法列表的方式,用于声明接口,实现类必须遵守这些接口(除非标记为
@optional
)。 - Swift:Protocol 不仅定义接口,还支持属性、方法、关联类型、默认实现等,是面向协议编程(POP)的核心工具。
2. 核心区别
特性 | Objective-C Protocol | Swift Protocol |
---|---|---|
可选方法 | 使用 @optional 标记可选方法 |
默认无可选方法,可通过 @objc optional 兼容 OC,或通过协议扩展提供默认实现 |
默认实现 | 不支持 | 支持通过 协议扩展 提供默认实现 |
关联类型 | 不支持 | 支持 associatedtype ,实现泛型协议 |
值类型支持 | 仅适用于类(Class) | 适用于类、结构体(Struct)、枚举(Enum) |
协议继承与组合 | 单继承(只能继承一个协议) | 支持多继承(ProtocolA & ProtocolB ) |
泛型支持 | 无 | 支持泛型约束(where 子句) |
属性定义 | 只能定义方法 | 可定义属性(需指定 { get } 或 { get set } ) |
类型检查 | 运行时检查(conformsToProtocol: ) |
编译时检查 + 运行时检查(is 、as? ) |
3. 使用场景与示例
Objective-C Protocol
objc
@protocol DataSource <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (NSString *)titleForItemAtIndex:(NSInteger)index;
@end
// 类遵循协议
@interface ViewController : UIViewController <DataSource>
@property(nonatomic,weak)id <DataSource> delegate;
@end
- 特点:主要用于委托模式(Delegate)、数据源模式(DataSource)。
Swift Protocol
swift
protocol Drawable {
func draw()
}
// 协议扩展提供默认实现
extension Drawable {
func draw() { print("默认绘制") }
}
struct Circle: Drawable {} // 结构体遵循协议
class Square: Drawable {} // 类遵循协议
// 关联类型
protocol Container {
associatedtype Item
var items: [Item] { get }
mutating func add(_ item: Item)
}
// 泛型约束
func process<T: Container>(container: T) where T.Item: Equatable {
// ...
}
- 特点:支持面向协议编程,适用于值类型和引用类型,灵活扩展功能。
4. 协议扩展(Swift 独有)
Swift 允许通过扩展为协议添加默认实现,而 Objective-C 无法实现:
swift
protocol Loggable {
func log()
}
extension Loggable {
func log() { print("日志记录") }
}
struct User: Loggable {} // 自动获得默认 log() 实现
5. 可选方法的实现方式
-
Objective-C :显式标记
@optional
,调用时需检查respondsToSelector:
。objc@protocol NetworkDelegate <NSObject> @optional - (void)didReceiveData:(NSData *)data; @end // 调用前检查 if ([delegate respondsToSelector:@selector(didReceiveData:)]) { [delegate didReceiveData:data]; }
-
Swift :通过协议扩展或
@objc optional
(仅限类)实现可选方法。swift@objc protocol NetworkDelegate { @objc optional func didReceiveData(_ data: Data) } class Handler: NetworkDelegate {} // 可选实现方法 // 调用时检查 delegate?.didReceiveData?(data)
6. 总结
场景 | Objective-C | Swift |
---|---|---|
接口定义 | 委托、数据源等简单场景 | 面向协议编程、泛型抽象、功能扩展 |
类型支持 | 仅类 | 类、结构体、枚举 |
灵活性 | 基础功能,依赖运行时检查 | 编译时安全,支持默认实现和关联类型 |
跨平台设计 | 主要用于 iOS/macOS 开发 | 跨平台(iOS/macOS/服务器/开源项目) |
选择建议:
- Objective-C:在传统项目或需要与 OC 代码交互时使用,适合简单接口定义。
- Swift:在新项目或需要高度抽象、复用和类型安全时使用,充分发挥面向协议编程的优势。
五、Swift 与 Objective-C 初始化方法(init
)有什么不一样?
1. 核心设计理念
- Swift :强调 安全性 和 严格性 ,通过两段式初始化 、编译时检查确保对象完整初始化。
- Objective-C :灵活性优先,依赖开发者自觉管理初始化过程,缺少编译时强制约束。
2. 主要区别
特性 | Swift | Objective-C |
---|---|---|
初始化阶段 | 两段式初始化(属性初始化 → 自定义操作) | 无明确阶段划分 |
安全检查 | 强制所有非可选(non-optional)存储属性初始化 | 无强制检查,未初始化的属性可能为 nil 或默认值 |
可选属性 | 必须显式初始化或声明为 Optional |
默认允许 nil (引用类型) |
初始化器类型 | 支持 指定初始化器 (Designated)和 便利初始化器(Convenience) | 无明确分类,但可通过 NS_DESIGNATED_INITIALIZER 标记指定初始化器 |
修饰符 | convenience (便利初始化器)、required (强制子类实现)、override (重写父类初始化器) |
无类似关键字 |
可失败初始化器 | 支持 init? 和 init! |
返回 nil 表示失败 |
继承与重写规则 | 子类必须重写父类的指定初始化器或自动继承,规则严格 | 子类可自由重写初始化器,无强制要求 |
值类型支持 | 结构体(struct )、枚举(enum )可定义初始化器 |
仅类(class )支持初始化器 |
3. 关键机制详解
(1) 两段式初始化(Swift 独有)
-
阶段 1:确保所有存储属性被初始化。
-
阶段 2 :在属性初始化完成后,进一步自定义实例(如调用方法、访问
self
)。 -
优势:避免属性未初始化就被使用,提升安全性。
-
示例 :
swiftclass Person { var name: String var age: Int // 指定初始化器 init(name: String, age: Int) { self.name = name // 阶段1:初始化属性 self.age = age // 阶段2:可调用方法 setup() } convenience init() { self.init(name: "Unknown", age: 0) // 必须调用指定初始化器 } private func setup() { /*...*/ } }
(2) 强制属性初始化(Swift)
-
所有非可选存储属性必须在初始化完成前赋值,否则编译器报错。
-
示例 :
swiftclass User { var id: Int // 错误:未初始化 var name: String? init(id: Int) { self.id = 1234 // 正确:非可选属性必须赋值 } }
(3) 初始化器修饰符(Swift)
-
convenience
:定义便利初始化器,必须调用同类中的指定初始化器。swiftclass Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // 调用指定初始化器 } }
-
required
:强制子类实现该初始化器。swiftclass Vehicle { required init() { /*...*/ } } class Car: Vehicle { required init() { // 必须实现 super.init() } }
(4) 可失败初始化器
-
Swift :通过
init?
返回可选实例,init!
返回隐式解包实例。swiftstruct Temperature { let celsius: Double init?(celsius: Double) { guard celsius >= -273.15 else { return nil } self.celsius = celsius } }
-
Objective-C :返回
nil
表示失败。objc@interface MyClass : NSObject - (instancetype)initWithValue:(NSInteger)value; @end @implementation MyClass - (instancetype)initWithValue:(NSInteger)value { if (value < 0) return nil; self = [super init]; return self; } @end
4. 初始化器继承规则
- Swift :
- 子类默认不继承父类初始化器。
- 若子类未定义任何指定初始化器,则自动继承父类所有指定初始化器。
- 若子类实现了父类所有指定初始化器,则自动继承父类便利初始化器。
- Objective-C:子类自动继承父类所有初始化器,除非显式重写。
5. 总结
场景 | Swift | Objective-C |
---|---|---|
安全性 | 高(编译时强制检查) | 低(依赖开发者自觉) |
灵活性 | 较低(严格规则限制) | 高(自由定义初始化逻辑) |
代码复杂度 | 高(需遵循两段式、修饰符规则) | 低(简单直接) |
适用类型 | 类、结构体、枚举 | 仅类 |
错误处理 | 可失败初始化器、异常抛出 | 返回 nil 或 NSError |
选择建议:
- Swift:在需要高安全性、复杂类型设计的场景下使用,遵循严格初始化规则。
- Objective-C:在维护旧项目或需要快速灵活初始化时使用,但需注意潜在风险。