Because, Swift/Objective-C supports writing extensions, I write my "ViewModel" properties in an extension class on the model and use this extension in places where one normally uses a ViewModel.
I know extensions can't have stored properties. But most MVVM architectures don't recommend stored properties in ViewModel (except those used for caching) anyways.
My main trouble with maintaining separate ViewModel object is getting it in sync with the model. Yes, there are plenty of third-party frameworks to help sync using reactive programming techniques. But for what can be solved, by simply using an extension, why use a heavy framework achieve the same?
I've not hit a road block with my extensions based MVVM architecture. Have anyone of you tried this and moved on to a reactive programming architecture?
My bad experience with the extensions approach was the basis for the first and most important part of my March 2016 talk at iOSoHo in New York. Most of what I said about extensions is not in the slides. There are several issues with the extension approach:
name and then later adding firstNameOnly, etc. Definitely a model can only ever conform to a protocol one way, if you're tempted to go that way. With a ViewModel these important cases become trivial. (In the talk I call "commands" because they perfectly match the Gang of Four Design Patterns definition of command; Design Patterns notes also known as Action, i.e. UIAlertAction.)
I take the approach that a "ViewModel" should be lightweight, immutable, and distributed through unidirectional data flow. They have stored properties but but this is just to move data around, the VMs themselves are immutable and disposable. This is similar to the approach mentioned by Facebook at iOSoHo March 2017, though I think they don't call them View Models, just lightweight objects. You could put more complex logic in there but really best to, at most, call out to more complex logic handled elsewhere.
One addition I think is key, my rule is that a "ViewModel", whatever you call it, should have no properties that can't be written by a programmer looking at wireframes. So a var dateString : String not var date : NSDate. This is my cheeky term in the slides, "Modal Object Attribute Transformer". But it's mnemonic for a serious point: A castle needs to be able to raise the drawbridge and carry on when under siege; a view needs to be fully functional when the network is cut off (or doesn't yet exist). You will have much easier time in so many scenarios than if your view can do that.
Just a few weeks ago I took this approach and was able to create multiple complex screens in about 4 days, working only from designs, that will eventually be populated by data from the network but currently without any model or networking layer whatsoever. When the data layer is ready, I can simply write some creational methods init(with: Model) or better static func withModelforCase1(_ model: Model) -> ViewModel and write the mapping in the creation method, then when the model is connected, replace static func debugCase1() -> ViewModel with these creation methods. As the model evolves the compiler will throw errors here and only here; as the view evolves, adjust the ViewModel, and again the compiler will throw errors here and only here. No other code would need to be touched.
Extensions seem very elegant at the start, but actually create a tight coupling between models and views. Some kind of ViewModel approach is a robust system the needs of modern iOS apps--and modern iOS app development.
ViewModel is supposed to be a different entity from architectural POV, not from code placement POV. Your approach to MVVM with extensions to model makes model know too much and violates SRP e.g. if model has some date attribute, view model would usually be used for formatting it using current locale. If Model is data storage it should only contain relevant data structures and no methods. If Model is a service (e.g. networking client) it should contain only relevant methods with minimal data transform (e.g. json to struct).
Also in my practice I sometimes have more than one VM to a single Model. If, say, I have a User model with firstName, lastName and email attributes, I may need fullName property for displaying it in a table row (implemented in UserCellViewModel), but all three properties when displaying user information in detailed view (implemented in UserDetailViewModel). With extensions to model, all four properties (fullName being implemented in extension) would be accessible in all contexts. Making controller consuming the ViewModel know as little as possible is a good thing (citation needed). This may be achieved in Objective-C where your extensions can have their own header/interface files, but you don't get this in Swift.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With