我试图使用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
包装或像this article中的@Injected
包装等时,我遇到相同的问题。< / p>
不,可悲的是,将其设置为普通属性也不会起作用:Property 'foo' with a wrapper cannot also be lazy
。
如果上面的示例过于简单,这是我碰到的一个更真实的示例:
final class ContactUsVMWorks {
let subject = Observable<String?>("")
let replyToEmail = Observable<String?>("")
let message = Observable<String?>("")
let formValid: Signal<Bool, Never>
init() {
let subjectValid: Signal<Bool, Never> = subject.map { $0.nilIfEmpty != nil }
let emailValid: Signal<Bool, Never> = replyToEmail.map { $0?.isEmail() ?? false }
let messageValid: Signal<Bool, Never> = message.map { $0.nilIfEmpty != nil }
formValid = combineLatest(subjectValid, emailValid, messageValid) { subjectValid, emailValid, messageValid in
subjectValid && emailValid && messageValid
}
}
}
@propertyWrapper
struct Property<Value> {
private let property: ReactiveKit.Property<Value>
init(wrappedValue: Value) {
self.property = ReactiveKit.Property(wrappedValue)
}
var wrappedValue: Value {
get { property.value }
set { property.value = newValue }
}
public var projectedValue: ReactiveKit.Property<Value> {
return property
}
}
final class ContactUsVMBroken {
@Property var subject: String? = ""
@Property var replyToEmail: String? = ""
@Property var message: String? = ""
let formValid: Signal<Bool, Never>
init() {
let subjectValid: Signal<Bool, Never> = $subject.map { $0.nilIfEmpty != nil }
let emailValid: Signal<Bool, Never> = $replyToEmail.map { $0?.isEmail() ?? false }
let messageValid: Signal<Bool, Never> = $message.map { $0.nilIfEmpty != nil }
formValid = combineLatest(subjectValid, emailValid, messageValid) { subjectValid, emailValid, messageValid in
subjectValid && emailValid && messageValid
}
}
}
再举一个例子:
final class DependencyViewModelWorks {
private let dependencies: AppDependencies
let isLoggedIn: Signal<Bool, Never>
init(dependencies: AppDependencies) {
self.dependencies = dependencies
isLoggedIn = dependencies.userManager.observableUser.map { $0 != nil }
}
}
@propertyWrapper
struct Injected<Service> {
private var service: Service?
public var container: Resolver?
public var name: String?
public var wrappedValue: Service {
mutating get {
if service == nil {
service = (container ?? Resolver.root).resolve(
Service.self,
name: name
)
}
return service! // swiftlint:disable:this force_unwrapping
}
mutating set {
service = newValue
}
}
public var projectedValue: Injected<Service> {
get {
return self
}
mutating set {
self = newValue
}
}
}
final class DependencyViewModelBroken {
@Injected var dependencies: AppDependencies
let isLoggedIn: Signal<Bool, Never>
init() {
isLoggedIn = dependencies.userManager.observableUser.map { $0 != nil }
}
}
答案 0 :(得分: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()
或被包装的值为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:您找到其他方法了吗?
答案 1 :(得分:0)
最省力的解决方法是使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
。