SwiftUI Property Wrapper理解

676 阅读2分钟

最近在读喵神的swiftUI与Combine编程,在swift5.1后,swiftUI可以说是极大的简化了我们搭建UI的代码,另外的Combine这种响应式编程我觉得更像是继flutter,rx等的压力的背景下产生的(swiftUI中一些概念和rxswift差不多)。内容取材自swiftUI与Combine编程和网络,目的就是为了加深记忆。emm...就是这个亚子。

在swiftUI中,我们会接触到各种@State,@Binding等属性,这种@属性在swift中称为属性包装Property Wrapper,@State,@Binding等它们都是被 @propertyWrapper修饰的struct类型。以@State为例子,在SwiftUI中State定义的关键部分如下:

@propertyWrapper public struct State<Value>: DynamicProperty{
 public init(initialValue value: Value)
 public var value: Value {get nonmutating set}
 public var wrapperdValue: Value{get nonmutating set}
 public var projectedValue: Binding<Value>{get}
}

init(initialValue:),wrappedValue 和 projectedValue 构成了一个 propertyWrapper 最重要的部分。在 @State 的实际使用里:

struct ContentView: View {
   @State private var brain: CalculatorBrain = .left("0") //1
   
   var body: some View {
       VStack(spacing:12) {
           Spacer()
           Text(brain.output) //2            
           CalculatorButtonPad(brain: $brain) //3
               .padding(.bottom)
       }
   }
}

1.由于 init(initialValue value: Value)的存在,我们可以使用直接给brain赋值的写法,将一个CalculatorBrain传递给brain.我们可以为属性包装中定义的 init 方法添 加更多的参数,不过 initialValue 这个 参数名相对特殊:当它出现在 init 方法的第一个参数位置时,编译器将允许我 们在声明的时候直接为 @State var brain 进行赋值 2.在访问 brain 时,这个变量暴露出来的就是 CalculatorBrain 的行为和属性。 对 brain 进行赋值,看起来也就和普通的变量赋值没有区别。但是,实际上这 些调用都触发的是属性包装中的 wrappedValue。@State 的声明,在底层将 brain 属性 “包装” 到了一个 State 中,并保留外界使用者 通过 CalculatorBrain 接口对它进行操作的可能性。 3.使用美元符号前缀 ($) 访问 brain,其实访问的是projectedValue 属性。在 State 中,这个属性返回一个 Binding 类型的值,通过遵守 BindingConvertible,State 暴露了修改其内部存储的方法,这也就是为什么 传递 Binding 可以让 brain 具有引用语义的原因. #####属性包装特性给了我们一个机会,可以在一定程度上简化语言的模 板代码,并且通过 “标注” 的方式来改变特性。它与自定义 getter 和 setter 做的事情 相似,只不过功能更强大,而且不需要到处重复去写一样的代码。 #####利用属性包装实现货币转换

import UIKit

class ViewController: UIViewController {
    
    @Converter(initialValue: "100", from: "USD", to: "CNY", rate: 6.88) var usd_cny
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("\(usd_cny)")
        print("\($usd_cny)")
    }


}

@propertyWrapper struct Converter {
    let from: String
    let to: String
    let rate: Double
    var value: Double
    
    var wrappedValue: String {
        get{"\(from) \(value)"}
        set{value = Double(newValue) ?? -1}
    }
    
    var projectedValue: String {
        return "\(to) \(value * rate)"
    }
    init(
        initialValue: String,
        from: String,
        to: String,
        rate: Double
    )
    {
        self.rate = rate
        self.value = 0
        self.from = from
        self.to = to
        self.wrappedValue = initialValue
    }
}
//输出:
//USD 100.0
//CNY 688.0

利用属性包装实现UserDefault存储(取材自网络)
@propertyWrapper 
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
///  wrappedValue是@propertyWrapper必须要实现的属性
/// 当操作我们要包裹的属性时  其具体set get方法实际上走的都是wrappedValue 的set get 方法。 
    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

struct UserDefaultsConfig {
  @UserDefault("had_shown_guide_view", defaultValue: false)
  static var hadShownGuideView: Bool
}

///具体的业务代码。
UserDefaultsConfig.hadShownGuideView = false
print(UserDefaultsConfig.hadShownGuideView) // false
UserDefaultsConfig.hadShownGuideView = true
print(UserDefaultsConfig.hadShownGuideView) // true