Given the design pattern as described by this post, here is an example view model:
final class SayHelloViewModel: ViewModelType {
let input: Input
let output: Output
struct Input {
let name: AnyObserver<String>
let validate: AnyObserver<Void>
}
struct Output {
let greeting: Driver<String>
}
private let nameSubject = ReplaySubject<String>.create(bufferSize: 1)
private let validateSubject = PublishSubject<Void>()
init() {
let greeting = validateSubject
.withLatestFrom(nameSubject)
.map { name in
return "Hello \(name)!"
}
.asDriver(onErrorJustReturn: ":-(")
self.output = Output(greeting: greeting)
self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
}
}
The above seems like a perfectly good design pattern. My only issue is, what happens when your mapping function from nameSubject
-> greeting
is more complex than what is shown here and instead needs to be abstracted into it's own function?
In the below scenario, i've abstracted the mapping functionality into its own function called sayHello.
Of course, the issue now is that we're referencing self before self is initialised. How is it possible to maintain this design pattern across non-trivial examples?
final class SayHelloViewModel {
let input: Input
let output: Output
struct Input {
let name: AnyObserver<String>
let validate: AnyObserver<Void>
}
struct Output {
let greeting: Driver<String>
}
private let nameSubject = ReplaySubject<String>.create(bufferSize: 1)
private let validateSubject = PublishSubject<Void>()
init() {
let greeting = validateSubject
.withLatestFrom(nameSubject)
.map(sayHello)
.asDriver(onErrorJustReturn: ":-(")
self.output = Output(greeting: greeting)
self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
}
private func sayHello(name: String) -> String {
return "Hello \(name)!"
}
}
Just make your mapping function a private free function instead of a class member. It only needs to be a member itself if it needs access to members, which in this pattern is highly unlikely.
Edit: Also you could clean this up a lot by avoiding subjects and operate on the inputs/outputs directly like so:
final struct SayHelloViewModel {
struct Input {
let name: Observable<String>
let validate: Observable<Void>
}
// An output
let greeting: Driver<String>
init(inputs: Input) {
let greeting = input.validate
.withLatestFrom(input.name)
.map(sayHello)
.asDriver(onErrorJustReturn: ":-(")
}
}
private func sayHello(name: String) -> String {
return "Hello \(name)!"
}
You could take it even further and not use a struct/class at all and make it purely a function that returns a tuple/struct of outputs.