What is ViewModels?
ViewModel is a layer between your view and data. ViewModels usually fetch the data using service objects, format it, and provide a formatted version to your view.
It is the middle layer of the view and the data. It is used to retrieve data by services, format the data and provide it to the view.
Recently Apple seems to be moving towards MVVM. In fact, I found something quite interesting from Apple, which is gradually moving the ObservableObject protocol to the Combine framework. Let’s see what Apple does with the MVVM pattern by looking at ObservableObject.
1 2 3 4 5 6 7 8 9 10 | /// A type of object with a publisher that emits before the object has changed. public protocol ObservableObject : AnyObject { /// The type of publisher that emits before the object has changed. associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never /// A publisher that emits before the object has changed. var objectWillChange: Self.ObjectWillChangePublisher { get } } |
The ObservableObject protocol has only one task: to emit (emits) events before the object changes. Now let’s write a ViewModel conforms to ObservableObject protocol.
1 2 3 4 5 6 7 8 9 10 11 12 | final class PostsViewModel: ObservableObject { let objectWillChange = PassthroughSubject<Void, Never>() private (set) var posts: [Post] = [] func fetch() { // fetch posts objectWillChange.send() // assign new data to the posts variable } } |
Here we have the ViewModel, which takes posts, stores them in a variable, and issues an objectWillChange message. Let’s see how to use it in ViewController.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | final class PostsViewController: UIViewController { let viewModel: PostsViewModel override func viewDidLoad() { super.viewDidLoad() bindViewModel() viewModel.fetch() } private func bindViewModel() { viewModel.objectWillChange.sink { [weak self] in guard let self = self else { return } self.renderPosts(self.viewModel.posts) } } } |
As you can see from the example above, PostsViewController will start observing from ViewModel, then ViewModel will retrieve data. As soon as there is data, it will generate (roughly, you understand that an event or something to identify the data will change). ViewController will render posts from func renderPosts and display them.
Published property wrapper
We can use the Published property, which allows us to wrap regardless of which attribute will generate the current value when changing. And of course then we no longer need to define objectWillChange . That work is now swift compiler.
Now ViewModel will be as follows:
1 2 3 4 5 6 7 8 | final class PostsViewModel: ObservableObject { @Published private(set) var posts: [Post] = [] func fetch() { // fetch posts and assign them to `posts` variable } } |
It will be much more compact. In the above example, we do not need to emits the value to try the same as above. The rest of the work is handled by the compiler. And of course PostViewController will remain the same (however we have another way to write and be more concise, let’s refer to it).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | final class PostsViewController: UIViewController { let viewModel: PostsViewModel override func viewDidLoad() { super.viewDidLoad() bindViewModel() viewModel.fetch() } private func bindViewModel() { viewModel.$posts.sink { [weak self] posts in self?.renderPosts(posts) } } } |
Conclude
We can easily implement the same logic using RxSwift, ReactiveSwift or any frameowork. But I feel like MVVM will be a default choice in the architecture of iOS applications. At least for now, when Apple gives us all the tools needed to build it. I hope you enjoyed the article.
Reference: https://swiftwithmajid.com/2020/02/05/building-viewmodels-with-combine-framework/