6

我正在尝试使用 Swift 5.1 属性包装器,但每次我认为我有一个很酷的用例时,我最终都会遇到无法在 View Model 的初始化程序中使用它们的问题。

举这个极其简单的例子。

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为可观察属性或本文@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")
  }
}
4

2 回答 2

3

编辑:

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

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

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

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

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

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


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

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

@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()值设定项或者如果 WrapdValue 是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()
  }
}

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

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

PS:你有没有找到其他方法来做到这一点?

于 2020-02-14T15:12:19.057 回答
0

最无摩擦的解决方法是制作upperCasedavar而不是 a 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按照其他答案的建议直接参考。

于 2020-02-14T16:22:37.643 回答