Skip to content

Instantly share code, notes, and snippets.

@pakirby1
Last active January 5, 2019 03:08
Show Gist options
  • Save pakirby1/421da206a94d3a8dd25a0065d06a87af to your computer and use it in GitHub Desktop.
Save pakirby1/421da206a94d3a8dd25a0065d06a87af to your computer and use it in GitHub Desktop.
Various ways to initialize stored properties

The initialization process has a few constraints on what can be done within an init function.
From StackOverflow

You can't call methods on self before all non-optional instance variables are initialized. There are several ways to go around that.

  • Change properties to optionals or implicitly unwrapped optionals (not recommended)
  • Make the methods static.
  • Make the methods as a fileprivate function in the file.
  • You just have to avoid calls to self before the class is initialized.
class ScheduledViewModel_PAK_Initial {
    let inputs: Inputs
    let outputs: Outputs
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()
        
        let dataSource = _reloadSubject.asObservable().map{ _ in return "Reload" }
        
        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

Instead of setting dataSource in the init() I would like to create a function that will set the values accordingly. My first thought is to just add the function to the view model:

class ScheduledViewModel_PAK_Initial2 {
    let inputs: Inputs
    let outputs: Outputs
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        return reload.map{ _ in return "Reload" }
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()
        
        let dataSource = buildDataSource(_reloadSubject.asObservable())
        
        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

Doing this results in a compilation error

'self' used in method call 'buildDataSource' before all stored properties are initialized

The problem is that in the init() we cannot refer to self before all stored properties are initialized (the input & output stored properties). So we can do the following :

  • Change properties to optionals or implicitly unwrapped optionals (not recommended)
  • Make the buildDataSource() method static
  • Pull the functions buildDataSource() out of the class yet still in the same file and mark it as fileprivate.

For the last two options, you will not have access to self since static methods cannot access self. And in the case of the function not living within the class you will not have access to self.

Using Implicitly Unwrapped Optionals

Here we simply make the input and output stored properties implicilty unwrapped optionals that are nil by default.

class ScheduledViewModel_PAK {
    let inputs: Inputs!
    let outputs: Outputs!
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        return reload.map{ _ in return "Reload" }
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()

        let dataSource = buildDataSource(_reloadSubject.asObservable())

        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

We still have the compilation error. Changing the input & output properties to var will fix the error.

class ScheduledViewModel_PAK {
    var inputs: Inputs!
    var outputs: Outputs!
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        return reload.map{ _ in return "Reload" }
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()

        let dataSource = buildDataSource(_reloadSubject.asObservable())

        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

We still have a problem, the original intent was to keep the inputs & outputs properties as constants (let) but now we've changed them to become var types. Since the properties are public this means that any consumer of the view model will be able to modify the properties. We can use private(set) to make it such that the properties can only be set within the view model.

class ScheduledViewModel_PAK {
    private(set) var inputs: Inputs!
    private(set) var outputs: Outputs!
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        return reload.map{ _ in return "Reload" }
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()

        let dataSource = buildDataSource(_reloadSubject.asObservable())

        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

This still poses a problem since the view model can mutate the properties. If the properties were marked as let then the view model couldn't mutate them.

Now that all stored properties input & output are initialized we no longer get a compilation error.

One problem with implicitly unwrapped optionals is that you may forget to explicitly set the property to a non-nil value and will receive a run-time error if the property is referenced since the value will remain nil

Using static functions

Make the functions static. You will need to pass in the values that the function requires since static functions cannot reference self

static func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        let x = reload.map{ _ in return "Reload" }
        self.outputs = Outputs(dataSourceOut: x)    // ERROR
    }

Instance member 'outputs' cannot be used on type 'ScheduledViewModel_PAK_Static

class ScheduledViewModel_PAK_Static {
    private(set) var inputs: Inputs
    private(set) var outputs: Outputs
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    static func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
        return reload.map{ _ in return "Reload" }
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()
        
        let dataSource = ScheduledViewModel_PAK_Static.buildDataSource(_reloadSubject.asObservable())
        
        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

Create Free Functions

Move the functions out of the class scope & mark them as fileprivate

fileprivate func buildDataSource(_ reload: Observable<Void>) -> Observable<String> {
    return reload.map{ _ in return "Reload" }
}

class ScheduledViewModel_PAK_Function {
    private(set) var inputs: Inputs
    private(set) var outputs: Outputs
    
    struct Inputs {
        let reloadTrigger: AnyObserver<Void>
    }
    
    struct Outputs {
        let dataSourceOut: Observable<String>
    }
    
    init() {
        let _reloadSubject = PublishSubject<Void>()
        
        let dataSource = buildDataSource(_reloadSubject.asObservable())
        
        self.inputs = Inputs(reloadTrigger: _reloadSubject.asObserver())
        self.outputs = Outputs(dataSourceOut: dataSource)
    }
}

In both the static & free function options you will not have access to self of the view model so you will not be able to call any methods on the view model. All dependencies will need to get passed into the static or free functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment