Swift 中属性 Getter、Setter 和观察者的生命周期
一、属性 Getter 和 Setter 的基础
Swift 中的属性分为存储属性(stored properties)和计算属性(computed properties)。getter
和 setter
是属性的核心机制,用于控制属性值的读取和设置。
1.1 存储属性
- 定义:存储属性直接存储值,常见于
struct
和class
。 - Getter/Setter:
- 默认情况下,存储属性的
getter
直接返回存储值,setter
直接修改存储值。 - 可以自定义
getter
和setter
来添加逻辑。
- 默认情况下,存储属性的
- 示例:
struct Person {
private var _name: String
var name: String {
get { _name }
set { _name = newValue.uppercased() }
}
}
var person = Person(_name: "John")
print(person.name) // 输出: JOHN
person.name = "Alice"
print(person.name) // 输出: ALICE
1.2 计算属性
- 定义:计算属性不存储值,通过
getter
和(可选)setter
计算得出。 - Getter/Setter:
getter
必须实现,返回计算值。setter
可选,用于更新相关状态。
- 示例:
struct Rectangle {
var width: Double
var height: Double
var area: Double {
get { width * height }
set { // 按比例更新 width 和 height
let ratio = width / height
height = sqrt(newValue / ratio)
width = newValue / height
}
}
}
var rect = Rectangle(width: 5, height: 10)
print(rect.area) // 输出: 50.0
rect.area = 200
print(rect.width, rect.height) // 输出: 10.0 20.0
1.3 属性观察者(willSet 和 didSet)
- 定义:存储属性支持
willSet
和didSet
,在值将要改变和已改变时触发。 - 用途:
willSet
:在属性值改变前执行,可访问新值(newValue
)。didSet
:在属性值改变后执行,可访问旧值(oldValue
)。
- 限制:
- 仅适用于存储属性(计算属性不支持)。
- 不能与自定义
setter
同时使用(但可与默认setter
结合)。
- 示例:
class Counter {
var count: Int = 0 {
willSet {
print("Will set count to \(newValue)")
}
didSet {
print("Did set count from \(oldValue) to \(count)")
}
}
}
let counter = Counter()
counter.count = 10
// 输出:
// Will set count to 10
// Did set count from 0 to 10
二、属性 Getter 和 Setter 的生命周期
属性的 getter
和 setter
生命周期可以分解为以下阶段:
2.1 Getter 生命周期
- 触发:当访问属性时,调用
getter
。 - 执行逻辑:
- 返回存储值(存储属性)或计算值(计算属性)。
- 可执行附加逻辑(如日志、转换)。
- 完成:返回结果给调用者。
- 示例:
struct Logger {
var value: Int {
get {
print("Accessing value")
return 42
}
}
}
let logger = Logger()
print(logger.value) // 输出: Accessing value \n 42
2.2 Setter 生命周期
- 触发:当设置属性值时,调用
setter
。 - 执行逻辑:
- 接收
newValue
(默认参数名,可自定义)。 - 更新存储值或相关状态。
- 可执行附加逻辑(如验证、通知)。
- 接收
- 完成:属性值更新。
- 示例:
struct Validator {
var age: Int {
get { _age }
set {
_age = max(0, min(newValue, 120)) // 限制年龄范围
}
}
private var _age: Int = 0
}
var validator = Validator()
validator.age = 150
print(validator.age) // 输出: 120
2.3 属性观察者生命周期
属性观察者的生命周期与 setter
紧密相关,分为以下阶段:
willSet 阶段:
- 触发:在
setter
更新存储值之前。 - 可访问:
newValue
:即将设置的新值。- 当前值(
self.property
):尚未改变。
- 用途:
- 验证新值。
- 触发前置通知。
- 限制:无法阻止值更新(只能抛出错误或间接处理)。
- 示例:
class User {
var name: String = "" {
willSet(newName) {
if newName.isEmpty {
print("Warning: Name cannot be empty")
}
}
}
}
let user = User()
user.name = "" // 输出: Warning: Name cannot be empty
- 触发:在
setter 执行:
- 更新存储属性的值。
- 如果有自定义
setter
,则执行其逻辑。
didSet 阶段:
- 触发:在
setter
更新存储值之后。 - 可访问:
oldValue
:更新前的值。- 当前值(
self.property
):已更新。
- 用途:
- 记录变化日志。
- 触发后置通知或 UI 更新。
- 示例:
class DataModel {
var data: Int = 0 {
didSet {
print("Data changed from \(oldValue) to \(data)")
}
}
}
let model = DataModel()
model.data = 42 // 输出: Data changed from 0 to 42
- 触发:在
完成:
- 属性值更新完成,观察者逻辑执行完毕。
- 如果涉及 UI 或通知,可能触发后续事件(如 KVO 或 Combine 发布)。
2.4 生命周期完整示例
以下示例展示了 getter
、setter
、willSet
和 didSet
的完整生命周期:class Temperature {
private var _celsius: Double = 0
var celsius: Double {
get {
print("Getting celsius: \(_celsius)")
return _celsius
}
set {
print("Setting celsius to \(newValue)")
_celsius = newValue
}
willSet {
print("Will set celsius to \(newValue)")
}
didSet {
print("Did set celsius from \(oldValue) to \(_celsius)")
}
}
}
let temp = Temperature()
temp.celsius = 25
// 输出:
// Will set celsius to 25.0
// Setting celsius to 25.0
// Did set celsius from 0.0 to 25.0
print(temp.celsius)
// 输出:
// Getting celsius: 25.0
// 25.0
三、高级用法和注意事项
3.1 自定义 Getter 和 Setter 的高级场景
- 延迟加载:
- 使用
getter
实现惰性初始化。 - 示例:
class ImageLoader {
var image: UIImage? {
get {
if _image == nil {
_image = UIImage(named: "default")
}
return _image
}
}
private var _image: UIImage?
}
- 使用
- 权限控制:
- 使用
private(set)
或自定义setter
限制写权限。 - 示例:
struct Config {
private(set) var apiKey: String = "default"
mutating func resetKey() {
apiKey = "reset"
}
}
- 使用
3.2 属性观察者的高级用法
- 通知和状态同步:
- 在
didSet
中触发通知或更新 UI。 - 示例(结合 Combine):
import Combine
class ViewModel: ObservableObject {
var username: String = "" {
didSet {
print("Username updated to \(username)")
}
}
}
- 在
- 验证和回滚:
- 在
willSet
中验证新值,必要时抛出错误。 - 示例:
class SafeCounter {
var count: Int = 0 {
willSet {
guard newValue >= 0 else {
fatalError("Count cannot be negative")
}
}
}
}
- 在
3.3 属性观察者 vs 自定义 Setter
- 区别:
willSet
和didSet
适用于存储属性,适合观察值变化。- 自定义
setter
提供更灵活的控制(如转换、拒绝更新)。
- 结合使用:
- 可以同时使用默认
setter
和观察者。 - 示例:
struct Circle {
var radius: Double = 0 {
willSet { print("Will set radius to \(newValue)") }
didSet { print("Radius changed to \(radius)") }
}
} - 但自定义
setter
会覆盖willSet
和didSet
,需手动调用观察逻辑。
- 可以同时使用默认
3.4 并发环境下的注意事项
- 线程安全:
- 属性访问和修改可能涉及多线程,需确保线程安全。
- 使用
@MainActor
或actor
保护属性。 - 示例:
actor SafeStore {
var value: Int = 0 {
didSet { print("Value updated to \(value)") }
}
}
- 异步更新:
- 在
async/await
中,属性观察者可能触发多次,需避免重复逻辑。 - 示例:
class AsyncModel {
var data: String = "" {
didSet { Task { await notifyChange() } }
}
private func notifyChange() { print("Notified") }
}
- 在
3.5 性能注意事项
- 避免复杂逻辑:
getter
和setter
的复杂计算可能影响性能,考虑缓存结果。- 示例:
class Cache {
private var _value: Int?
var value: Int {
get {
if let cached = _value { return cached }
let computed = heavyCalculation()
_value = computed
return computed
}
}
private func heavyCalculation() -> Int { 42 }
}
- 观察者开销:
willSet
和didSet
的频繁触发可能导致性能瓶颈,尽量简化逻辑。
四、常见面试问题
问题:
willSet
和didSet
可以用在计算属性上吗?为什么?
答案:- 不能。计算属性没有存储值,
willSet
和didSet
仅适用于存储属性。 - 替代方案:使用自定义
setter
实现类似逻辑。
- 不能。计算属性没有存储值,
问题: 如何在
setter
中防止无限递归?
答案:- 使用私有存储属性,避免直接调用
setter
。 - 示例:
class RecursiveSafe {
private var _count: Int = 0
var count: Int {
get { _count }
set {
if newValue != _count { // 避免递归
_count = newValue
}
}
}
}
- 使用私有存储属性,避免直接调用
问题: 如何在属性观察者中实现 KVO-like 功能?
答案:- 使用
didSet
发布通知或调用回调。 - 示例:
class Observable {
var onChange: ((Int) -> Void)?
var value: Int = 0 {
didSet { onChange?(value) }
}
}
- 使用
问题: 如何在
willSet
中取消属性更新?
答案:- 无法直接取消,但可抛出错误或使用自定义
setter
。 - 示例:
struct StrictModel {
var value: Int {
get { _value }
set {
guard newValue >= 0 else { fatalError("Invalid value") }
_value = newValue
}
}
private var _value: Int = 0
}
- 无法直接取消,但可抛出错误或使用自定义
五、高级场景:结合其他 Swift 特性
5.1 结合 Codable
- 使用属性观察者监控
Codable
属性变化。 - 示例:
struct User: Codable {
var name: String {
didSet { print("Name updated to \(name)") }
}
}
5.2 结合 Combine
- 使用
@Published
自动触发观察者。 - 示例:
import Combine
class ViewModel: ObservableObject {
var count: Int = 0 {
willSet { print("Will set count to \(newValue)") }
}
}
5.3 结合泛型和协议
- 在协议中定义属性观察者逻辑。
- 示例:
protocol ObservableProperty {
associatedtype T
var value: T { get set }
var onChange: ((T) -> Void)? { get set }
}
struct ObservableValue<T>: ObservableProperty {
var value: T {
didSet { onChange?(value) }
}
var onChange: ((T) -> Void)?
}
六、总结
- Getter:控制属性读取,返回值或计算结果。
- Setter:控制属性写入,更新存储值或状态。
- willSet:在值改变前触发,适合验证或前置通知。
- didSet:在值改变后触发,适合日志或后置通知。
- 生命周期:
willSet
→setter
→didSet
→ 完成。 - 注意事项:
- 避免复杂逻辑以优化性能。
- 在并发环境中使用
actor
或锁。 - 结合
Codable
、Combine
等特性实现复杂功能。