【问题标题】:Swift 5.1 @propertyWrapper - 'self' used in property access before all stored properties are initializedSwift 5.1 @propertyWrapper - 在初始化所有存储的属性之前在属性访问中使用“自我”
【发布时间】:2020-02-04 00:36:27
【问题描述】:

我正在尝试使用 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 包装器(如 this article 等)时,我遇到了同样的问题。

不,遗憾的是,将其作为非专业人士也行不通: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")
  }
}

【问题讨论】:

  • 您正在尝试在 upperCased 初始化之前访问 self.foo。这与属性包装无关。
  • @ClausJørgensen 好吧,正如您在 NoProblem 示例中看到的那样,当您不使用属性包装器时,同样可以正常工作。
  • @MumtazHussain 那是不可能的。当然,我可以将 upperCased 设为惰性 var,但随后它会抱怨 Lazy properties must have an initializer。在更多现实世界的例子中,这并不总是一种选择。
  • “你的整个方法都是错的”,谢谢你。同样,这是一个非常简化的示例,只是为了显示编译器错误。为什么在使用属性包装器时会抱怨,但在不使用属性包装器时却没有完成 - 这就是问题所在。就像我说的,当尝试将属性包装器用于可观察属性或注入属性或其他任何东西时,也会发生同样的问题。
  • 又添加了两个示例。

标签: swift swiftui swift5.1


【解决方案1】:

编辑:

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

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

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

如果我理解正确的话,在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() 或者如果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 中所说的话有关:

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

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

【讨论】:

  • 不,事实上这在 SwiftUI 中也是一个真正的问题,请参阅我编辑的问题以获取新示例。
  • @KevinRenskers 我已经用更好的解决方法更新了我的答案,也许你可以暂时使用它。如果您还没有,我想我也会向 swift 团队提出问题。
  • @KevinRenskers 实际上已重新编辑。当我写信给 swift 论坛时,我意识到这实际上是预期的行为。我认为这样做的唯一方法是按照我在编辑中建议的方式。 (反正我会用这个方法)
  • 使用下划线变量的 WrapdValue 的好方法。
【解决方案2】:

最顺畅的解决方法是将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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-01
    • 2020-11-23
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    相关资源
    最近更新 更多