Swift 5.1 @propertyWrapper - «self» используется в доступе к свойствам до инициализации всех сохраненных свойств - PullRequest
0 голосов
/ 06 октября 2019

Я пытаюсь использовать оболочки свойств 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 для наблюдаемых свойств или @Injected обертка, как в в этой статье и т. д.

И, к сожалению, создание свойства lay не будет работать: Property 'foo' with a wrapper cannot also be lazy.


InВ случае, когда приведенный выше пример был слишком упрощенным, я привел немного более реальный пример:

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 }
  }
}
...