Swift 5.1 @propertyWrapper - 在初始化所有存储的属性之前在属性访问中使用“self”

Kev*_*ers 5 swift swiftui swift5.1

我正在尝试使用 Swift 5.1 属性包装器,但每次我认为我有一个很酷的用例时,我最终都会遇到无法在我的视图模型的初始值设定项中使用它们的问题。

以这个极其简单的例子为例。

class NoProblem {
  var foo = "ABC"
  let upperCased: String

  init(dependencies: AppDependencies) {
    self.upperCased = foo.uppercased()
  }
}
Run Code Online (Sandbox Code Playgroud)
@propertyWrapper
struct Box<Value> {
  private var box: Value

  init(wrappedValue: Value) {
    box = wrappedValue
  }

  var wrappedValue: Value {
    get { box }
    set { box = newValue }
  }
}

class OhNoes {
  @Box var foo = "ABC"
  let upperCased: String

  init(dependencies: AppDependencies) {
    self.upperCased = foo.uppercased()
  }
}
Run Code Online (Sandbox Code Playgroud)

在 中NoProblem,一切都按预期进行。然而,在OhNoes我得到这个错误:'self' used in property access 'foo' before all stored properties are initialized

当然,这是一个非常简单的例子,但在做的时候,我得到了同样的问题@Property可观察性包装,或@Injected像在包装这篇文章,等等。

没有,可悲的是使其成为一个世俗性质不会工作:Property 'foo' with a wrapper cannot also be lazy


这在 SwiftUI 中也是一个相当大的问题,看这个例子:

class AppStore: ObservableObject {
  let foo = "foo"
}

struct ContentView: View {
  @EnvironmentObject private var store: AppStore
  private let foo: String

  init() {
    foo = store.foo // error: 'self' used before all stored properties are initialized
  }

  var body: some View {
    Text("Hello world")
  }
}
Run Code Online (Sandbox Code Playgroud)

Enr*_*oza 3

编辑:

实际上,更好的解决方法是直接使用_foo.wrappedValue.uppercased()而不是foo.uppercased().

这也解决了双重初始化的另一个问题。

更深入地思考这一点,这绝对是预期的行为。

如果我理解正确的话,在 中OhNoes, foo 只是以下的缩写:

var foo: String {
  get {
    return self._foo.wrappedValue
  }
  set {
    self._foo.wrappedValue = newValue
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,这不可能以任何其他方式发挥作用。


我遇到了与您相同的问题,实际上我认为这是某种错误/不需要的行为。

无论如何,我能出去的最好的就是:

var foo: String {
  get {
    return self._foo.wrappedValue
  }
  set {
    self._foo.wrappedValue = newValue
  }
}
Run Code Online (Sandbox Code Playgroud)

这非常好(我的意思是,它没有副作用,但很丑)。

init()此解决方案的问题在于,如果您的属性包装器具有空的初始值设定项或wrappedValue 为 ,则它实际上不起作用(没有副作用)Optional

例如,如果您尝试使用下面的代码,您将发现 Box 被初始化了两次:一次在成员变量的定义中,一次在 OhNoes 的 init 中,并且将替换前者。


@propertyWrapper
struct Box<Value> {
  private var box: Value?

  init(wrappedValue: Value?) { // Actually called twice in this case
    box = wrappedValue 
  }

  var wrappedValue: Value? {
    get { box }
    set { box = newValue }
  }
}

class OhNoes {
  @Box var foo : String?
  let upperCased: String?

  init() {
    let box = Box(wrappedValue: "ABC")
    _foo = box
    self.upperCased = box.wrappedValue?.uppercased()
  }
}
Run Code Online (Sandbox Code Playgroud)

我认为这绝对是我们不应该发生的事情(或者至少我们应该能够选择不这样做)。不管怎样,我认为这与他们在这次演讲中所说的有关:

当属性包装类型具有无参数 init() 时,使用该包装类型的属性将通过 init() 隐式初始化。

PS:您是否找到了其他方法来做到这一点?