The new features SwiftUI brings to Swift 5.1 are more important than the framework itself. We can expect these new language features to be used by library authors soon. In the previous article, we explained what some of some View is in SwiftUI code and why it is important. In this article, we will learn about preparing @state and @binding in Swift UI, Property Delegates are known as Property Delegates, or Property Wrappers. The code is as follows:

struct OrderForm : View {@State private var order: Order
  
  var body: some View {
    Stepper(value: $order.quantity, in: 1.10) {
      Text("Quantity: \(order.quantity)")}}}Copy the code

This language feature is so generic that any access to attributes that has a “routine” can be wrapped around it. Let’s learn some routines first.

1. Wrap lazy initialization logic

To implement lazy initialization of the property text, we can write the following code:

public struct MyType {
  var textStorage: String? = nil
  
  public var text: String {
    get {
      guard let value = textStorage else {
        fatalError("text has not yet been set!")}return value
    }
    
    set {
      textStorage = newValue
    }
  }
}
Copy the code

However, if there are many properties that follow this logic, this notation is redundant. So property brokering solves this problem:

@propertyDelegate
public struct LateInitialized<Value> {
  private var storage: Value?
  
  public init() {
    storage = nil
  }
  
  public var value: Value {
    get{
      guard let value = storage else {
        fatalError("value has not yet been set!")}return value
    }
    set {
      storage = newValue
    }
  }
}

// Apply the attribute proxy LateInitialized
public struct MyType {@LateInitialized public var text: String?
}
Copy the code

Property proxy LateInitialized is a generic type, which itself is decorated with @propertyDelegate. It must have a property called value of type value. With these conventions in place, the compiler can generate the following code for MyType’s text:

public struct MyType {
  var $text: LateInitialized<String> = LateInitialized<String> ()public var text: String {
      get { $text.value }
      set { $text.value = newValue}
  }
}
Copy the code

As you can see, the compiler helps generate a storage property called $text wrapped by the property broker. The type is the property broker, and text itself becomes a calculated property. While you might think that the $text attribute is compiler generated and therefore not accessible, in fact, both text and $text are available.

2. Wrap defensive copies

Let’s look again at an example of defensive copying based on NSCopying

@propertyDelegate
public struct DefensiveCopying<Value: NSCopying> {
  private var storage: Value
  
  public init(initialValue value: Value) {
    storage = value.copy() as! Value
  }
  
  public var value: Value {
    get { storage }
    set {
      storage = newValue.copy() as! Value}}}// Apply the property proxy DefensiveCopying
public struct MyType {@DefensiveCopying public var path: UIBezierPath = UIBezierPath()}Copy the code

The property broker DefensiveCopying differs in its initialization function init(initialValue:), which must be called by this name due to compiler convention. As in the previous example, the compiler generates the storage property $PATH and initializes it with its initial value.

The UIBezierPath was forced to be copied once, so we provide another initialization function for the property broker and apply it:

// DefensiveCopying is added
  public init(withoutCopying value: Value) {
    storage = value
  }
  
// Apply the non-copy initialization function
public struct MyType {@DefensiveCopying public var path: UIBezierPath
  
  init() {
    $path = DefensiveCopying(withoutCopying: UIBezierPath()}}Copy the code

In the application section, we see that $path can be initialized just like a normal variable, which confirms the nature of $path and path. But this syntax is a bit ugly and should be hidden as much as possible when it is not needed:

public struct MyType {@DefensiveCopying(withoutCopying: UIBezierPath())
  public var path: UIBezierPath
}
Copy the code

3. Wrap access to UserDefaults

We often need to write properties as calculated properties for UserDefaults access, and this general access strategy can also be implemented using property proxies:

@propertyDelegate
struct UserDefault<T> {
  let key: String
  let defaultValue: T
  
  var value: T {
    get {
      return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
    }
    set {
      UserDefaults.standard.set(newValue, forKey: key)
    }
  }
}

// Apply the property proxy UserDefault
enum GlobalSettings {@UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
  static var isFooFeatureEnabled: Bool
  
  @UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
  static var isBarFeatureEnabled: Bool
}
Copy the code

conclusion

Property proxy can be used to abstract all property access policies. We can also think of thread-local storage (Thread local storage) property access, atomic property access, copy-on-write property access, reference wrapper type property access can be implemented using property proxy. Of course, @state and @Binding are also property proxies for SwiftUI. To explain them in detail, you need some knowledge of Swift, which will be explained in the next article.

Related articles:

SwiftUI and Swift 5.1 New Features (1) Opaque Return Type Opaque Result Type

Scan the QR code below to follow “Interviewer Xiaojian”