Swift PropertyWrapper

官方教程:《Property Wrappers》

特点包装器在办理特点存储办法的代码和界说特点的代码之间添加了一层别离。例如,如果您有供给线程安全查看或将其根底数据存储在数据库中的特点,则有必要在每个特点上编写该代码。当您运用特点包装时,您在界说包装时编写一次办理代码,然后经过将其应用于多个特点来重用该办理代码。

简略讲,Property Wrapper 是对特点的一层封装,躲藏与特点相关的逻辑细节,提高代码的复用性。

由于它将特点包装器和特点自身别离开来,并将某些常见的行为抽象出来,使代码愈加简洁、可读、易于维护。

1. 界说特点包装器

@propertyWrapper// 告知编译器这是一个特点包装器
structNumberWrapper{
 // 这是包装的特点
 varwrappedValue:Int
}
​
structNumber{
 // 告知编译器运用Wrapper包装器包装该特点
 @NumberWrappervarnumber:Int
}

怎样了解 其作用是将特点的 界说代码 与特点的存储办法代码 进行别离

需求要求: 存储的number不能够大于10.

@propertyWrapper
structNumberWrapper{ 
 privatevarvalue:Int=0
 varwrappedValue:Int{
   get{
     returnvalue
   }
   set{
     value=min(newValue,10)
   }
 }
}

将特点包装器改成这样的实现,被修饰的number特点,就具有了永不大于10的逻辑。

再来看一个例子:

这是一个姓氏拾取器,经过传入的人名,拾取第一个字作为姓氏。

@propertyWrapper
structSurnameExtractor{
 privatevarsurname:String=""
 
 varwrappedValue:String{
   get{returnsurname}
   set{
     surname=String(newValue.prefix(1))
   }
 }
}
​
structXiaoMing{
 @SurnameExtractorvarsurname:String
}
​
varxiaoming=XiaoMing()
xiaoming.surname="李小明"
print(xiaoming.surname)
​
// 输出:李

总结

  • 界说特点包装器,需求运用 @propertyWrapper 关键词声明。
  • 特点包装器有必要要有 wrappedValue 特点(名字是固定的)。
  • 特点包装器最好是Struct类型。Struct是值类型,会自动办理饮用,不必担心循环引证。

2. 包装器投影值 projectedValue

projectedValue 为 property wrapper 供给了额定的功能(如:标志某个状况,或许记录 property wrapper 内部的变化等)

NumberWrapper 包装器中,咱们想知道改特点是否被修正过。就需求主角 projectedValue 上台。

@propertyWrapper
structNumberWrapper{
 privatevarvalue:Int=0
 
 varprojectedValue:Bool=false
 
 varwrappedValue:Int{
   get{
     returnvalue
   }
   set{
     ifnewValue>10{
       value=10
       projectedValue=true
     }else{
       value=newValue
       projectedValue=false
     }
   }
 }
 
 init(){}
}
​
structSmallNumber{
 @NumberWrappervarnumber:Int
}
​
varsmallNumber=SmallNumber()
smallNumber.number=5
print(smallNumber.number)
print(smallNumber.$number)
// 输出:5 false
​
smallNumber.number=20
print(smallNumber.number)
print(smallNumber.$number)
// 输出:10 true

咱们经过特点包装器成功的从名字中获取到了姓氏,在此根底上在实现获取姓氏的中文拼音首字母用来排序,应该怎样处理呢?

@propertyWrapper
structSurnameExtractor{
 privatevarsurname:String=""
 
 varprojectedValue:String{
   // 获取中文拼音首字母的办法
   returnsurname.transformToPinyinHead()
 }
 
 varwrappedValue:String{
   get{returnsurname}
   set{
     surname=String(newValue.prefix(1))
   }
 }
 
 init(){
   surname=""
 }
}
​
varxiaoming=XiaoMing()
xiaoming.surname="李小明"
print(xiaoming.surname)
print(xiaoming.$surname)// ⚠️注意这儿的 $
// 输出:李,L

再来看第三个案例:经过projectedValue直接返回self,为propertyWrapper 供给辅助能力。

@propertyWrapper
structRGBValue{
privatevarvalue:Int=0
varprojectedValue:RGBValue{self}
​
varwrappedValue:Int{
 get{returnvalue}
 set{value=max(0,min(255,newValue))}
}
​
varhex:String{
 String(format:"%02X",value)
}
}
​
structRGB{
@RGBValuevarr:Int
@RGBValuevarg:Int
@RGBValuevarb:Int
​
funchexRGB()->String{
​
  letrHex=$r.hex
  letgHex=$g.hex
  letbHex=$b.hex
  
 return"#(rHex)(gHex)(bHex)"
}
}

总结

  • projectedValue 可所以任意类型
  • projectedValue可是存储特点,也可所以核算特点
  • 两者都是经过实例的特点名进行拜访的。不同的是 projectedValue 需求在特点名前加上 $ 才能够拜访。

    • wrappedValue: 实例.特点名,特点包装存储的值。
    • projectedValue: 实例.$特点名,映射值。
  • projectedValue的命名是固定的。

3. 给特点包装器设置初始值

声明一个这样的特点包装器

@propertyWrapper
structTenOrLess{
 privatevarmaximum:Int
 privatevarnumber:Int
 
 varwrappedValue:Int{
   get{returnnumber}
   set{number=min(newValue,maximum)}
 }
 
 init(){
   maximum=10
   number=0
 }
 
 init(wrappedValue:Int){
   print("init(wrappedValue:)")
   maximum=12
   number=min(wrappedValue,maximum)
 }
 
 init(wrappedValue:Int,maximum:Int){
   print("init(wrappedValue:maximum:)")
   self.maximum=maximum
   number=min(wrappedValue,maximum)
 }
}

状况1:运用了 @TenOrLess 但没有指定初始化值

structRectangle{
 @TenOrLessvarheight:Int
 @TenOrLessvarwidth:Int
}
​
varrectangle=Rectangle()
print(rectangle.height,rectangle.width)
// 输出 0 0

状况2:运用了 @TenOrLess ,并指定初始化值

struct Rectangle {
    @TenOrLess var height: Int = 8
    @TenOrLess var width: Int = 8
}
var rectangle = Rectangle()
print(rectangle.height, rectangle.width)
// 输出 8 8

会调用 init(wrappedValue:) 办法 ,print出来 init(wrappedValue:) .

状况3:运用@TenOrLess,并传参进行初始化

struct Rectangle {
    @TenOrLess(wrappedValue: 10, maximum: 10) var height: Int
    @TenOrLess(wrappedValue: 10, maximum: 10) var width: Int
}
var rectangle = Rectangle()
print(rectangle.height, rectangle.width)
// 输出 10 10

会调用 init(wrappedValue:maximum:) 办法 ,print出来init(wrappedValue:maximum:).

struct HasWrapperWithInitialValue {
    @Wrapper var x = 10
    @Wrapper(wrappedValue: 20) var y
}

以上两种声明之间有区别:

  1. 设置了默认值,会自动调用 init(wrappedValue: Int) 办法,编译器隐式地调用 `init(wrappedValue:) 用0初始化x。
  2. 初始化办法被明确指定为特点的一部分。

状况4:运用@TenOrLess,并传参进行初始化,并给特点默认值

struct Rectangle {
    @TenOrLess(wrappedValue: 10, maximum: 10) var height: Int = 0
    @TenOrLess(wrappedValue: 10, maximum: 10) var width: Int = 0
}

❌: Extra argument ‘wrappedValue’ in call。 在调用中有剩余的参数。

4. 拜访特点包装器

@propertyWrapper
struct VisitWrapper<T> {
    var wrappedValue: T
    var projectedValue: VisitWrapper<T> { return self }
    func welcome() {
        print("welcome ~~")
    }
}
struct VisitHasWrapper {
    @VisitWrapper var x = 0
    func welcome() {
        /// 这儿的_x是包装器的实例,因此能够调用welcome办法,但是从VisitHasWrapper外部调用就会产生便衣错误 “'_x' is inaccessible due to 'private' protection level”
        _x.welcome()
        // $符号是拜访包装器特点的一个语法糖
        $x.welcome()
        print(x)  // 拜访的wrappedValue, 输出:0
        print(_x) // 拜访的是 wrapper type itself, VisitWrapper<Int>(wrappedValue: 0)
        print($x) // 拜访的是projectedValue, VisitWrapper<Int>(wrappedValue: 0)
    }
}
  • $符号是拜访包装器特点的一个语法糖
  • x: 拜访的wrappedValue
  • _ x: 拜访的是 wrapper type itself
  • $x: 拜访的是projectedValue

5. 运用限制

5. 1 协议的特点不支持运用特点包装器

❌: property ‘some’ declared inside a protocol cannot have a wrapper。在协议中声明的特点’some’不能有包装器

protocol SomeProtocol {
    @TenOrLess var some: Int { get set }
}

5.2 extension中不能够运用

❌:Non-static property ‘some’ declared inside an extension cannot have a wrapper. 在扩展内声明的非静态特点’some’不能有包装器.

extension Rectangle {
    @TenOrLess var some: Int { return 5 }
}

5.3 enum中不能够运用

❌:Property wrapper attribute ‘TenOrLess’ can only be applied to a property. 特点包装器特点’TenOrLess’只能应用于特点

enum SomeEnum: Int {
    @TenOrLess case one
    case two
}

5.4 class里的 wrapper property 不能重写

❌:Cannot override with a stored property ‘some’. 不能用存储特点“some”重写

class SomeClass {
    @TenOrLess var some: Int
}
class OtherClass: SomeClass {
    override var some: Int = 0
}

5.5 wrapper 不能界说 gettersetter 办法

❌:Property wrapper cannot be applied to a computed property. 特点包装器不能应用于核算特点

struct SomeStruct {
    @TenOrLess var some: Int {
        return 0
    }
}

5.6 wrapper 特点不能被 lazy@NSCopying@NSManagedweak、 或许 unowned 修饰

6. 其他的一些示例

6.1 验证传入的字符串值是否为空。

@propertyWrapper
struct CheckEmptyString {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set {
            if newValue.isEmpty {
                assert(false, "传入的值为空")
                self.value = "unknowed"
            } else {
                self.value = newValue
            }
        }
    }
}
struct Person {
    @CheckEmptyString var name: String
}
var p1 = Person()
p1.name = ""
print(p1.name)

6.2 给UserDefault供给一个便利的调用办法。

@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
}
GlobalSettings.isFooFeatureEnabled = true
let value = GlobalSettings.isFooFeatureEnabled

6.3 更多案例能够查看官方文档

0258-property-wrappers