Swift5(+RxSwift) 'self' captured by a closure before all members were initialized

Related searches

I'm learning MVVM + RxSwift. I would like to save a value to reuse it at the next flow. But I encountered the error above.

I assume I initialised the value which is "translatedText" but the error still comes up.

I have tried some ways that were to initialise with the declaration and so on...

import UIKit
import RxSwift

final class ViewModel {


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>
    var translatedText: String
    // var translatedText: String = "" //this case also dosn't work

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) {

        self.api = api
        self.translatedText = "" //I guess I initialised this here

        let event = inputObservable
            .flatMap { input -> Observable<Event<Void>> in
                if let text = input {
                    self.translatedText = text // error. i want to save "input" to use at "let tapEvent"
                }
                return model
                    .validate(text: input)
                    .materialize()
            }
            .share()

        self.validationText = event
            .flatMap { event -> Observable<String> in
                switch event {
                case .next:
                    return .just("")
                case let .error(error as ModelError):
                    return .just(error.errorLabel)
                case .error, .completed:
                    return .empty()
                }
            }
            .startWith(ModelError.invalidBlank.errorLabel)

        let tapEvent = changeButtonClicked
            .flatMap { (result) -> Observable<Event<String>> in
                return api
                    .fetch(text: self.translatedText) // I want use it here
                    .materialize()
        }
        .share()

        self.getObservable = tapEvent
            .flatMap { event -> Observable<String> in
                switch event {
                case .next:
                    return .just(event.element!)
                case let .error(error as ModelError):
                    return .just(error.errorLabel)
                case .error, .completed:
                    return .empty()
                }
            }
    }

}

Do you guys have any better solutions?

----- self solution -----

I just simply created a temp variable before let event and it works as I want.

final class ViewModel {


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) {

        self.api = api
        var temp = ""

        let event = inputObservable
            .flatMap { input -> Observable<Event<Void>> in
                if let text = input {
                    temp = text
                }
                return model
                    .validate(text: input)
                    .materialize()
            }
            .share()

You shouldn't tie two independent observable streams to each other with some random block of "temp" memory. The two streams depend on inputObservable and that should be made clear. Also, your view model can be greatly simplified...

final class ViewModel {
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) {

        let translatedText = inputObservable.share() // this line is unnecessary if `inputObservable` is already hot.

        self.validationText = translatedText
            .flatMap { input in
                return model
                    .validate(text: input)
                    .map { "" }
                    .catchError { error in .just((error as? ModelError)?.errorLabel ?? error.localizedDescription) }
            }
            .startWith(ModelError.invalidBlank.errorLabel)

        self.getObservable = changeButtonClicked
            .withLatestFrom(translatedText)
            .compactMap { $0 }
            .flatMap { translatedText in
                return api
                    .fetch(text: translatedText)
                    .catchError { error in .just((error as? ModelError)?.errorLabel ?? error.localizedDescription) }
            }
    }
}

What's New in Swift 5?, Style on demand. Get Mizuno Swift 5 delivered to your door. Don't wait to look great. Try Drive Up, Pick Up, or Same Day Delivery.

Your issue is that self is not completely initialized when you call flatMap. In your case, validationText and getObservable are in an undefined state.

The quickest way would be to make them implicitly unwrapped optionals, but you really have to guarantee that they are not being accessed before being initialized:

final class ViewModel {


    private let api: apiProtocol
    let validationText: Observable<String>!
    let getObservable: Observable<String>!
    var translatedText: String!

    // ...
}

Swift 5 | Laptops, Swift 5 Acer. Buy your SWIFT 5 Acer now. prices & deals subject to change.

---- self solution -----

I just simply created a temp variable before let event and it works as I want.

final class ViewModel {


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) {

        self.api = api
        var temp = ""

        let event = inputObservable
            .flatMap { input -> Observable<Event<Void>> in
                if let text = input {
                    temp = text
                }
                return model
                    .validate(text: input)
                    .materialize()
            }
            .share()

What's new in Swift 5.0 – Hacking with Swift, Swift 5 Designed to be carried around all-day for work and entertainment, the Swift 5 is the lightest 7 14” clamshell notebook that weighs just 990 grams and has a powerful NVIDIA ® graphics card. It also has the latest Intel ® Core™ CPU and is only 14.95mm thin, so you can be confident in its powerful design. Tough, Light and Thin

Swift 5 features, The Swift 5 is available with only one type of risers, made from 12mm black Cousin webbing. These are delivered standard with specially designed ‘Link Lite’ connectors to hold the riser lines. These are tested to over 500kg and save approximately 200gr in weight over conventional maillons.

With a slightly larger screen than other ultra lights and an impressively-light, 2.1-pound (0.95 kg) weight, the latest model of the Acer Swift 5 ($899.99 to start, $999.99 as tested) is for budget

The Swift 5 is a full-size (ish) laptop that weighs less than some tablet hybrids. That’s impressive—though unless weight is the only feature you care about, the Swift 5 can feel limiting in other

Comments
  • Thanks for the comment. these two are initialised by self.validationText = event and self.getObservable = tapEvent so that the problems was that self was used in a closure as self.translatedText before it is initialised. But I could solve it by simply doing code above. I found another way to use lazy but it is not for this time.
  • You should avoid doing this if you want your app to be crashfree.