Free AI web copilot to create summaries, insights and extended knowledge, download it at here
3869
Abstract
order="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="9286">The <code>MoviesUseCase</code> class consumes network and image loader service via initializer. Those are responsible for fetching data via network and image loading and caching. The <code>searchMovies</code> function could be implemented as following using Combine framework:</p>
<figure id="6f84">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/7ebf862dbdceb645a50ac3a280431b87.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="4b65">➊ <code>load</code> creates a publisher that delivers the results of performing URL session data tasks. It returns down the pipeline <code>Result<Movies, NetworkError></code> object.
➋ The map operator is used to transform the result object.
➌ Performs the work on the background queue.
➍ Switches to receive the result on the main queue.
➎ <code>eraseToAnyPublisher</code> does type erasure on the chain of operators so the <code>searchMovies(with:)</code> function returns an object of type <code>AnyPublisher<Result<[Movie], Error>, Never></code>.</p><h2 id="460d">ViewModel</h2><p id="b05b">With the above-mentioned code in place, we’re now ready to declare viewModel for the search screen. You might consider several options at this point. It should be a nice idea to expose <code>@Published</code> properties in the viewModel and observe changes from the view. A better solution would be defining a ViewModel, that transforms the input to the output:</p>
<figure id="97ce">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/ebd93a7ffd71467547546843205279e1.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="cf10">Where <code>MoviesSearchViewModelInput</code> is a struct that defines UI events to be used by the viewModel:</p>
<figure id="96a6">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/74398d9fe8e814c7f5c49f869af53c5c.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="8c89">And <code>MoviesSearchViewModelOuput</code> defines the view’s state via the type-erasing publisher:</p>
<figure id="0fdc">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/f8b3a6591342f922e20bb68bc6cc7700.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="05d6">It should be pointed out that you could have more complex output type in a real project. It can be declared as a struct then.</p><p id="25f8">Next, we have to declare the <code>MoviesSearchViewModel</code> class. It is initialized with <code>MoviesUseCaseType</code> and <code>MoviesSearchNavigator</code> objects, that define movies search business rules and screens navigation respectively.</p>
<figure id="4bce">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/06457db6a41ac133e88582a9de219d60.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="27b3">We’re now ready to implement the transform function. This is the most important
Options
and probably complex part of our project:</p>
<figure id="6b5f">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/9c99fc47803bfc239a43cc6557c5e968.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="7fdc">➊ Cancels current subscriptions.
➋ Adds a subscriber to show the details screen when a user taps on a movie from the list.
➌ Debounces search events and removes duplicates to create the <code>searchInput</code> object.
➍ The creation of the <code>movies</code> publisher, that starts search on user input and emits <code>MoviesSearchState</code> objects eventually.
➎ Defines <code>idle</code> state publisher, that emits value immediately(default state) and when the search string is empty.
➏ Merges <code>idle</code> and <code>movies</code> state publishers. Calls <code>eraseToAnyPublisher</code> that does type erasure on the chain of operators so the <code>transform(input:)</code> function returns an object of type <code>AnyPublisher<MoviesSearchState, Never></code>.</p><h2 id="9c1c">View</h2><p id="aa8b">Using the above setup we can implement the <code>MoviesSearchViewController</code>. It consumes a <code>MoviesSearchViewModelType</code> instance via initializer and binds one on <code>viewDidLoad</code>:</p>
<figure id="b076">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/bd51b18af1643be3e264d2ca52b47327.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="7d9d">Next, we need a way to declare UI events. This could be achieved with <code>PassthroughSubject</code> type, that provides a convenient way to adapt existing imperative code to the Combine model:</p>
<figure id="283f">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/af924c1c5be3bb8e0f9ec1e0772f92a3.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="dc20">We can use these events to declare the <code>bind</code> function which is called from <code>viewDidLoad</code>. It establishes a binding with the viewModel, subscribes on the output(state) changes and renders one when changed:</p>
<figure id="c95f">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sgl0v/e8c110bbfa9ba364a7c75758b1773789.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="24a1">Just like that, we’ve created the movies search screen that follows MVVM software design pattern and is built with Combine framework.</p><figure id="889a"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*akyu1bKXTuM6r-JKWB0boA.gif"><figcaption></figcaption></figure><h1 id="ed30">Conclusion</h1><p id="46c5">The Model-View-ViewModel pattern helps to neatly separate the application logic and UI. It results in having single-purpose components that are easier to test, maintain, and evolve. MVVM works greatly in conjunction with functional reactive frameworks like Combine, that encourage you to write clean, readable code.</p><p id="312d">You can find the project’s source code on <a href="https://github.com/sgl0v/TMDB">Github</a>. Feel free to play around and reach me out on <a href="https://twitter.com/sgl0v">Twitter</a> if you have any questions, suggestions or feedback.</p><p id="5b4c">Thanks for reading!</p></article></body>