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 asfileprivate
.
For the last two options, you will not have access to
self
since static methods cannot accessself
. And in the case of the function not living within the class you will not have access toself
.
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
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)
}
}
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.