Skip to content

Add fluent property wrapper initialization #17

@gohanlon

Description

@gohanlon

With #13's addition of @Init(assignee:type:), MemberwiseInit's @Init became expressive enough to support property-wrapped members1.

But, MemberwiseInit should provide a cleaner way to initialize properties wrapped by property wrappers. Further, @Init has become burdened by some seldomly needed parameters and is now Xcode autocomplete-unfriendly. We can improve the situation somewhat.

Here are the new macro definitions:

  • @Init for standard property initialization behavior.
  • @InitWrapper to initialize the wrapper of a property-wrapped property.
  • @InitRaw directly exposes the full configurability of the macro.
// An escape hatch that embraces the "template" nature of the macro and directly exposes it's configuration.
// 6 arguments
public macro InitRaw(
  _ accessLevel: AccessLevelConfig? = nil,
  assignee: String? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,
  label: String? = nil,
  type: Any.Type? = nil
) = 

// To simplify common usage, forgo 'assignee' and 'type'.
// 4 arguments
public macro Init(
  _ accessLevel: AccessLevelConfig? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,
  label: String? = nil
) = 

// Use the 'assignee' of 'self._\(#propertyName)'.
// 5 arguments
public macro InitWrapper(
  _ accessLevel: AccessLevelConfig? = nil,
  default: Any? = nil,  // forward looking
  escaping: Bool? = nil,,
  label: String? = nil,
  type: Any.Type  // NB: 'type' is required because it can't be inferred, and will always be different than that of the wrapped property
) = 

Example:

import SwiftUI

@propertyWrapper
struct Logged<Value> {
  var wrappedValue: Value {
    didSet {
      print("Logged: \(wrappedValue)")
    }
  }

  init(wrappedValue: Value) {
    self.wrappedValue = wrappedValue
  }
}

// NB: Some property wrappers require initialization of the property
// wrapper itself, hence `@InitWrapper`. Here, we want Logged to be
// initialized without triggering its side effects (logging).

@MemberwiseInit(.public)
public struct CounterView: View {
  // @Logged: InitWrapper
  @InitWrapper(.public, default: "Blob", type: Logged<String>)
  @Logged
  var name1: String

  // @Binding: InitWrapper
  @InitWrapper(.public, type: Binding<Int>)
  @Binding
  var count1: Int


  // @Logged: InitRaw
  @InitRaw(.public, assignee: "self._name1", default: "Blob", type: Logged<String>)
  @Logged
  var name2: String

  // @Binding: InitRaw
  @InitRaw(.public, assignee: "self._count1", type: Binding<Int>)
  @Binding
  var count2: Int


  // @State
  @Init(.public)
  @State var isOn = false

  var body: some View {  }
}

swift-memberwise-init-macro version information

0.2.0

Footnotes

  1. I think @Init(assignee:type:) provides complete (if awkward) support for initializing property wrappers. If I'm wrong, I'd love to hear about it.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions