Hi~
Hi~
文章目录
  1. Swift 中属性 Getter、Setter 和观察者的生命周期
    1. 一、属性 Getter 和 Setter 的基础
      1. 1.1 存储属性
      2. 1.2 计算属性
      3. 1.3 属性观察者(willSet 和 didSet)
    2. 二、属性 Getter 和 Setter 的生命周期
      1. 2.1 Getter 生命周期
      2. 2.2 Setter 生命周期
      3. 2.3 属性观察者生命周期
      4. 2.4 生命周期完整示例
    3. 三、高级用法和注意事项
      1. 3.1 自定义 Getter 和 Setter 的高级场景
      2. 3.2 属性观察者的高级用法
      3. 3.3 属性观察者 vs 自定义 Setter
      4. 3.4 并发环境下的注意事项
      5. 3.5 性能注意事项
    4. 四、常见面试问题
    5. 五、高级场景:结合其他 Swift 特性
      1. 5.1 结合 Codable
      2. 5.2 结合 Combine
      3. 5.3 结合泛型和协议
    6. 六、总结

Swift 中属性 Getter、Setter 和观察者的生命周期

Swift 中属性 Getter、Setter 和观察者的生命周期

一、属性 Getter 和 Setter 的基础

Swift 中的属性分为存储属性(stored properties)和计算属性(computed properties)。gettersetter 是属性的核心机制,用于控制属性值的读取和设置。

1.1 存储属性

  • 定义:存储属性直接存储值,常见于 structclass
  • Getter/Setter
    • 默认情况下,存储属性的 getter 直接返回存储值,setter 直接修改存储值。
    • 可以自定义 gettersetter 来添加逻辑。
  • 示例
    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)

  • 定义:存储属性支持 willSetdidSet,在值将要改变和已改变时触发。
  • 用途
    • 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 的生命周期

属性的 gettersetter 生命周期可以分解为以下阶段:

2.1 Getter 生命周期

  1. 触发:当访问属性时,调用 getter
  2. 执行逻辑
    • 返回存储值(存储属性)或计算值(计算属性)。
    • 可执行附加逻辑(如日志、转换)。
  3. 完成:返回结果给调用者。
  • 示例
    struct Logger {
    var value: Int {
    get {
    print("Accessing value")
    return 42
    }
    }
    }
    let logger = Logger()
    print(logger.value) // 输出: Accessing value \n 42

2.2 Setter 生命周期

  1. 触发:当设置属性值时,调用 setter
  2. 执行逻辑
    • 接收 newValue(默认参数名,可自定义)。
    • 更新存储值或相关状态。
    • 可执行附加逻辑(如验证、通知)。
  3. 完成:属性值更新。
  • 示例
    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 紧密相关,分为以下阶段:

  1. 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
  2. setter 执行

    • 更新存储属性的值。
    • 如果有自定义 setter,则执行其逻辑。
  3. 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
  4. 完成

    • 属性值更新完成,观察者逻辑执行完毕。
    • 如果涉及 UI 或通知,可能触发后续事件(如 KVO 或 Combine 发布)。

2.4 生命周期完整示例

以下示例展示了 gettersetterwillSetdidSet 的完整生命周期:

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 {
      @Published 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

  • 区别
    • willSetdidSet 适用于存储属性,适合观察值变化。
    • 自定义 setter 提供更灵活的控制(如转换、拒绝更新)。
  • 结合使用
    • 可以同时使用默认 setter 和观察者。
    • 示例:
      struct Circle {
      var radius: Double = 0 {
      willSet { print("Will set radius to \(newValue)") }
      didSet { print("Radius changed to \(radius)") }
      }
      }
    • 但自定义 setter 会覆盖 willSetdidSet,需手动调用观察逻辑。

3.4 并发环境下的注意事项

  • 线程安全
    • 属性访问和修改可能涉及多线程,需确保线程安全。
    • 使用 @MainActoractor 保护属性。
    • 示例:
      actor SafeStore {
      var value: Int = 0 {
      didSet { print("Value updated to \(value)") }
      }
      }
  • 异步更新
    • async/await 中,属性观察者可能触发多次,需避免重复逻辑。
    • 示例:
      class AsyncModel {
      var data: String = "" {
      didSet { Task { await notifyChange() } }
      }
      @MainActor private func notifyChange() { print("Notified") }
      }

3.5 性能注意事项

  • 避免复杂逻辑
    • gettersetter 的复杂计算可能影响性能,考虑缓存结果。
    • 示例:
      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 }
      }
  • 观察者开销
    • willSetdidSet 的频繁触发可能导致性能瓶颈,尽量简化逻辑。

四、常见面试问题

  1. 问题: willSetdidSet 可以用在计算属性上吗?为什么?
    答案:

    • 不能。计算属性没有存储值,willSetdidSet 仅适用于存储属性。
    • 替代方案:使用自定义 setter 实现类似逻辑。
  2. 问题: 如何在 setter 中防止无限递归?
    答案:

    • 使用私有存储属性,避免直接调用 setter
    • 示例:
      class RecursiveSafe {
      private var _count: Int = 0
      var count: Int {
      get { _count }
      set {
      if newValue != _count { // 避免递归
      _count = newValue
      }
      }
      }
      }
  3. 问题: 如何在属性观察者中实现 KVO-like 功能?
    答案:

    • 使用 didSet 发布通知或调用回调。
    • 示例:
      class Observable {
      var onChange: ((Int) -> Void)?
      var value: Int = 0 {
      didSet { onChange?(value) }
      }
      }
  4. 问题: 如何在 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 {
    @Published 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:在值改变后触发,适合日志或后置通知。
  • 生命周期willSetsetterdidSet → 完成。
  • 注意事项
    • 避免复杂逻辑以优化性能。
    • 在并发环境中使用 actor 或锁。
    • 结合 CodableCombine 等特性实现复杂功能。
支持一下
扫一扫,支持forsigner
  • 微信扫一扫
  • 支付宝扫一扫