Swift 5.1 @属性Wrapper - 'self' 在所有存储属性初始化之前用于 属性 访问

Swift 5.1 @propertyWrapper - 'self' used in property access before all stored properties are initialized

我正在尝试使用 Swift 5.1 属性 包装器,但每次我认为我有一个很酷的用例时,我最终遇到了无法在内部使用它们的问题我的视图模型的初始化程序。

举这个极其简单的例子。

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

  init(dependencies: AppDependencies) {
    self.upperCased = foo.uppercased()
  }
}
@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()
  }
}

NoProblem 中,一切正常。但是在 OhNoes 中我得到这个错误:'self' used in property access 'foo' before all stored properties are initialized.

当然这是一个极其简化的示例,但是在为可观察属性做 @Property 包装器或 this article 中的 @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")
  }
}

编辑:

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

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

仔细想想,这绝对是预期的行为。

如果我没理解错的话,在OhNoes中,foo是

的缩写
var foo: String {
  get {
    return self._foo.wrappedValue
  }
  set {
    self._foo.wrappedValue = newValue
  }
}

所以这不可能以任何其他方式起作用。


我遇到了与您相同的问题,实际上我认为这是某种 bug/unwanted 行为。

不管怎样,我能出去的最好的是这个:

@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 : String
  let upperCased: String

  init() {
    let box = Box(wrappedValue: "ABC")
    _foo = box
    self.upperCased = box.wrappedValue.uppercased()
  }
}

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

这个解决方案的问题是,如果你的 属性 包装器有一个空的初始值设定项 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()
  }
}

我认为这绝对是我们不应该拥有的东西,(或者至少我们应该能够选择退出这种行为)。无论如何,我认为这与他们在 this pitch:

中所说的有关

When a property wrapper type has a no-parameter init(), properties that use that wrapper type will be implicitly initialized via init().

PS:您找到其他方法了吗?

最顺畅的解决方法是将 upperCased 设为 var 而不是 let。好的,这可能并不理想,但至少这意味着您可以保留所有代码并立即使用生成的 OhNoes 实例:

struct AppDependencies {}
@propertyWrapper struct Box<T> {
    private var boxed: T
    init(wrappedValue: T) {
        boxed = wrappedValue
    }
    var wrappedValue: T {
        get { boxed }
        set { boxed = newValue }
    }
}
class OhNoes {
    @Box var foo = "abc"
    var upperCased: String = "" // this is the only real change
    init(dependencies: AppDependencies) {
        self.upperCased = foo.uppercased()
    }
}

如果您真的不喜欢那样,请直接参考其他答案所建议的_foo.wrappedValue