Swift Property Wrapper 属性包装器

@propertyWrapper属性包装器:在定义存储属性时添加一个分离层,代表该属性被包装起来,且在包装器内部可以做一些事情。把一些通用复用的代码放在了包装器中,比如线程安全检查或者数据存储到数据库中。

个人理解:简单定义的属性只能具备存储功能,供其他方法调用。而被属性包装器修饰的属性不仅具备存储能力,还可以具备计算型属性的能力(setter,与 getter),甚至还可以通过属性包装器存储更多变量或者方法。

官方文档: Property Wrappers Property Wrappers 演化进程

下面我们就通过官网示例了解下 Property Wrappers

基本使用

抽象出特点 定义属性包装器

示例中需要定义一个保证属性不大于12的特点的属性包装器,即代表着该属性包装器修饰的的属性都具备不大于12的特点。

@propertyWrapper   // 使用@propertyWrapper标记这是一个属性包装器
struct TwelveOrLess { // 可以定义结构体、类、枚举
    private var number: Int // 定义一个私有属性存储外部属性的值
    init() { self.number = 0 } // 初始化
    var wrappedValue: Int { // 属性包装器必须包含一个非 static 的属性,可以在这里进行值或者方法操作
        get { return number } // 具备 get 效果 | 也可以实现 willSet\didSet(但是不能同时)
        set { number = min(newValue, 12) } // 具备 set 效果
    }
}

wrappedValue相当于外部的属性值,所以我们可以实现setter``getter来进行值存储或者其他方法调用。

包装器修饰属性

struct SmallRectangle {
    @TwelveOrLess var height: Int  // 代表 height 属性无论赋予什么值,也不会大于12
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"

系统内部会进行如下转化效果,通过这种方式就很好理解了,相当实际存储的时 TwelveOrLess, height值只是通过 .wappedValue来获取。

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }  // 相当于即实现了存储效果,又实现了 set/get 效果
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

而且 _height_width是真实存在的,可以访问的。

Tips: 所以不建议属性用下划线开始命名,以免以后和系统的产生冲突。

实现属性包装器初始化

属性包装器不仅可以存储 修饰属性值,可以存储其他值和实现方法

为了具备能为属性添加初始值我们可以定义包装器的初始化方法

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }
}

不带参数初始化

init() {
    maximum = 12
    number = 0
}

使用方式@TwelveOrLess var height: Int

直接给外部属性赋值@TwelveOrLess var height: Int = 10将报错Argument passed to call that takes no arguments

只含 wrappedValue 参数的初始化

init(wrappedValue: Int) {  // init(_ wrappedValue: Int)也可以
    maximum = 12
    number = min(wrappedValue, maximum)
}

如果只有一个wrappedValue参数,可以直接使用的时候赋初始值。

@TwelveOrLess var height: Int = 10

含其他参数的初始化

init(wrappedValue: Int, maximum: Int) {
   self.maximum = maximum
   number = min(wrappedValue, maximum)
}

如果第一个参数为wrappedValue,则可以直接使用的时候赋初始值

@SmallNumber(maximum: 80) // 推荐 好理解点
var num: Int = 40
// 等效于
@SmallNumber(wrappedValue: 40, maximum: 80) // 通过初始化方法赋值
var num: Int //= 40  此时不能再赋值

相反的wrappedValue参数放在后面

init(maximum: Int, wrappedValue: Int) {
  ...
}

使用 @SmallNumber(maximum: 80) var num: Int = 40

报错Argument 'maximum' must precede argument 'wrappedValue'

只能使用@SmallNumber(maximum: 80, wrappedValue: 40)var num: Int //= 40

projectedValue 使用

实现@propertyWrapper标记之后,除了必须实现 wrappedValue属性外,还可以实现projectedValue属性在外部一$+属性的方式使用。

@propertyWrapper
struct SmallNumber {
    private var number: Int
    var projectedValue: Bool  // 类型自行指定
    init() {
        self.number = 0
        self.projectedValue = false
    }
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int  // 通过.$someNumber访问 projecedValue
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

问:height_height$height分别代表什么,是不是一个类型。

答: 不是同一个类型

  • height:对应 wrappedValue 值
  • _height: 对应 TwelveOrLess 类型
  • $height:对应 projectedValue, 类型不一定和 height 一致

应用场景

@propertyWrapper
struct UserDefault<T> {
  let key: String
  let defaultValue: T
  
  var wrappedValue: T {
    get {
      return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
    }
    set {
      UserDefaults.standard.set(newValue, forKey: key)
    }
  }
}
enum GlobalSettings {
  @UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
  static var isFooFeatureEnabled: Bool
  
  @UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
  static var isBarFeatureEnabled: Bool
}
	@Animatable(animated: Animations.TranslationAnimation(), configure: {
        if isRepeat {
            $0.autoreverses = true
            $0.repeatCount = 100
            }
    })
    var translationView: UILabel = UILabel()

另外 SwiftUI 使用了大量属性包装器,比如 @State、@Binding

参考

Swift-PropertyWrapper

如何运用 Swift 的属性包装器实现本地化 – 掘金 - CodingSuccess

Advanced Property Wrapper in Swift – 老司机周报

什么是 Property Wrapper – Sarunw

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值