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 { | 
三、高级用法和注意事项
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等特性实现复杂功能。
 
 
          