属性包装器是一种机制,可以封装与属性相关的逻辑(例如,验证、修改或以特定方式存储),并通过代码中的注解在不同的属性之间重用这些逻辑。它们有助于消除重复代码并提高可读性。
属性包装器是结构体、类或枚举,通过@propertyWrapper注解实现属性包装器协议,并具有必需的wrappedValue属性。
限制和细节:
**示例:**我们将为自动限制值范围(Clamped)编写属性包装器。
@propertyWrapper struct Clamped<Value: Comparable> { var value: Value let range: ClosedRange<Value> var wrappedValue: Value { get { value } set { value = min(max(newValue, range.lowerBound), range.upperBound) } } init(wrappedValue initialValue: Value, _ range: ClosedRange<Value>) { self.range = range self.value = min(max(initialValue, range.lowerBound), range.upperBound) } } struct Person { @Clamped(0...120) var age: Int = 25 } var p = Person() p.age = 200 // 现在 p.age = 120 p.age = -10 // 现在 p.age = 0
如何从结构/类外部访问原始的属性包装器对象?
**回答:**通过带下划线的属性名称(_)。例如,如果属性名为age,则可以通过_age获取属性包装器对象:
var p = Person() let wrapper = p._age // 这是类型 Clamped<Int>
故事
在一个项目中,存储UserDefaults时实现了一个自定义的属性包装器,专门处理原始类型。在尝试对引用类型(class)使用它时,出现了意外的内存泄漏——属性包装器持有对对象的强引用,导致强循环和数据泄漏。错误通过在包装器内部转向弱引用/无主引用(weak/unowned reference)来修正。
故事
在一个项目中,尝试将属性包装器应用于计算属性,但编译器报错:属性包装器只能与存储属性一起使用。这个事实被忽视,导致模块开发延迟了2天。
故事
在创建包装结构时,忘记实现通过init(wrappedValue:...)的正确初始化语法。结果无法通过属性包装器设置默认值,这一问题仅在将包装集成到大量模型中后被发现。必须重新考虑架构。