swiftmvvmrx-swiftcoordinator-pattern

Get a "Variable 'self.xxx' used before being initialized" even after guard self and non optional properties


I am currently implementing a MVVM (using Rx) architecture for a new application written in Swift and I am struggling when writing a ViewModel.

This is the beginning of it:

class GameViewModel {
    public let input: Input
    public let output: Output

    public struct Input {
        let slider: AnyObserver<Float>
        let buttonCLicked: AnyObserver<Void>
    }

    public struct Output {
        let slider: Observable<Float>
        let target: Observable<Int>
        let score: Observable<Int>
        let round: Observable<Int>
        let buttonCLicked: Observable<AlertProperties>
    }

    private var slider = BehaviorSubject<Float>(value: 0)
    private var buttonCLicked = PublishSubject<Void>()
    private var target = BehaviorSubject<Int>(value: 0)
    private var score = BehaviorSubject<Int>(value: 0)
    private var round = BehaviorSubject<Int>(value: 0)

    private let disposebag: DisposeBag
    private var model: Game!

    init(game: Game?) {
        disposebag = DisposeBag()

        input = Input(slider: slider.asObserver(),
                      buttonCLicked: buttonCLicked.asObserver())

        output = Output(slider: slider.asObservable(),
                        target: target.asObservable(),
                        score: score.asObservable(),
                        round: round.asObservable(),
                        buttonCLicked: buttonCLicked.map { [weak self] _ in
                            guard let self = self else {
                                return AlertProperties(title: "Fail",
                                                       message: "!!!",
                                                       buttonTitle: "Ok",
                                                       handler: nil)
                            }
                            return self.getAlertProperties() })

        model = game ?? initDefaultGame()

        slider.onNext(Float(model.slider))
        target.onNext(model.target)
        score.onNext(model.score)
        round.onNext(model.round)
    }

and this is a basic function taking current values for creating an object to return:

    func getAlertProperties() -> AlertProperties {
        guard let targetValue = try? target.value(),
            let currentValue = try? slider.value() else {
            return AlertProperties(title: "Fail",
                                   message: "!!!",
                                   buttonTitle: "Ok",
                                   handler: nil)
        }

        // some code

        return AlertProperties(title: title,
                               message: "\(points) points: \(castCurrentValue)",
                               buttonTitle: "Ok",
                               handler: {
                                   self.target.onNext(self.getRandomValue())
                                   self.slider.onNext(50.0)
        })
    }

What I am trying to do here is register my inputs and outputs on my init function. And for the output part I would like to transmit the result of a function with some current values (getAlertProperties). Basically I have a trigger on my ViewController which need these values to show in a AlertController when the user click on a simple button.

I get the error message Variable 'self.xxx' used before being initialized" for the buttonClicked declaration in my Output. I get that I need to initialize every non optional properties before using self but I thought that maybe using guards would do the trick. Apparently not...

How can I fix this without making my properties input and output optionals ?

Thank you


Solution

  • Ok I made my properties input and output as lazy and it worked.

        static let errorAlertProperties = AlertProperties(title: "Fail",
                                                          message: "!!!",
                                                          buttonTitle: "Ok",
                                                          handler: nil)
    
        lazy var input = Input(slider: slider.asObserver(),
                               buttonCLicked: buttonCLicked.asObserver(),
                               resetCLicked: resetClicked.asObserver())
    
        lazy var output = Output(slider: slider.asObservable(),
                                 target: target.asObservable(),
                                 score: score.asObservable(),
                                 round: round.asObservable(),
                                 buttonCLicked: buttonCLicked.map { [weak self] _ in
                                     guard let self = self else {
                                         return GameViewModel.errorAlertProperties
                                     }
                                     return self.getAlertProperties() })
    

    I don't know if it's the "right" way to do it so please let me know if you have a better approach.

    Thank you