<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Luis Recuenco on Medium]]></title>
        <description><![CDATA[Stories by Luis Recuenco on Medium]]></description>
        <link>https://medium.com/@recuenco?source=rss-c3e9dcf69985------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*7JX8K5upUCRcXQqCTn_qIA.png</url>
            <title>Stories by Luis Recuenco on Medium</title>
            <link>https://medium.com/@recuenco?source=rss-c3e9dcf69985------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 29 Apr 2026 17:26:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@recuenco/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[The Expression Problem in Swift]]></title>
            <link>https://medium.com/the-swift-cooperative/the-expression-problem-in-swift-248acd53dc5c?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/248acd53dc5c</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Fri, 20 Dec 2024 22:19:13 GMT</pubDate>
            <atom:updated>2024-12-30T20:52:39.452Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0JoV7SZ3TdQxYYCF" /><figcaption>Photo by <a href="https://unsplash.com/@fairfilter?utm_source=medium&amp;utm_medium=referral">Oliver Roos</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>The <a href="https://wiki.c2.com/?ExpressionProblem">expression problem</a> is a well-known programming challenge. Let’s explore it in Swift by trying to model an arithmetic operation like this one:</p><blockquote><strong><em>3 + 5 * (8–7) / 6</em></strong></blockquote><h3>The object-oriented approach</h3><p>Most people approach this by creating a base class or protocol that defines what an operation means.</p><pre>protocol Operation {<br>    var compute: Double { get }<br>}</pre><p>But what is an operation? Well, it can be just a number:</p><pre>struct Number: Operation {<br>    var value: Double<br>    var compute: Double { value }<br>}</pre><p>Or, following the <a href="https://en.wikipedia.org/wiki/Composite_pattern">composite pattern</a>, it can be a combination of other Operation types:</p><pre>struct Add: Operation {<br>    var lhs: Operation<br>    var rhs: Operation<br><br>    var compute: Double { lhs.compute + rhs.compute }<br>}<br><br>struct Subs: Operation {<br>    var lhs: Operation<br>    var rhs: Operation<br><br>    var compute: Double { lhs.compute - rhs.compute }<br>}<br><br>struct Mult: Operation {<br>    var lhs: Operation<br>    var rhs: Operation<br><br>    var compute: Double { lhs.compute * rhs.compute }<br>}<br><br>struct Div: Operation {<br>    var lhs: Operation<br>    var rhs: Operation<br><br>    var compute: Double { lhs.compute / rhs.compute }<br>}</pre><p>As you can see, it’s a simple recursive pattern where the Number type is our base case.</p><p>Once we have all these types in place, we can build the arithmetic operation we defined at the beginning of the article:</p><pre>let operation = Add(<br>    lhs: Number(value: 3),<br>    rhs: Mult(<br>        lhs: Number(value: 5),<br>        rhs: Div(<br>            lhs: Subs(<br>                lhs: Number(value: 8),<br>                rhs: Number(value: 7)<br>            ),<br>            rhs: Number(value: 6)<br>        )<br>    )<br>)<br><br>operation.compute // 3.833333333333333</pre><p>This approach makes it extremely easy to add new arithmetic operations just by having a new type conform to the Operation protocol.</p><pre>struct Pow: Operation {<br>    var base: Operation<br>    var exp: Int<br><br>    var compute: Double { pow(base.compute, Double(exp)) }<br>}<br><br>Pow(base: operation, exp: 3).compute // 56.328</pre><p>But what about trying to add a new “interpretation” for that arithmetic operation? Right now, we only compute the final number after evaluating the operation, but we might want to “interpret” the operation differently. For instance, we might want to present the user with the string representation of the operation, that is, something like 3+5*(8-7)/6.</p><p>Well, that’s slightly trickier. If we had access to the Operation type, we might simply add a new asString property and have all the conformant types implement that new method. But Operation could be defined in an external library we don’t have access to. What options do we have then? Well, we have extensions…</p><pre>extension Operation {<br>    var asString: String { fatalError() }<br>}<br><br>extension Number {<br>    var asString: String { &quot;\(value)&quot; }<br>}<br><br>extension Add {<br>    var asString: String { &quot;(\(lhs.asString) + \(rhs.asString))&quot; }<br>}<br><br>Add(lhs: Number(value: 5), rhs: Number(value: 3)).asString // ???</pre><p>Of course, that doesn’t work and the last line ends up hitting the fatalError. Even if the asString method inside the Add type is called, the lhs and rhs variables are Operation types. As asString method is only defined in a protocol extension and not as a required method in the protocol definition, the dispatch of the asString method for lhs and rhs will be static, not dynamic. What that means is that the underlying runtime type for lhs and rhs won’t matter and Number‘s asString implementation won’t be ever called. Rather, the fatalError will.</p><p>Let’s try another solution.</p><h3>The functional approach</h3><p>Let’s try defining our operation as a recursive sum type:</p><pre>indirect enum Operation {<br>    case add(lhs: Self, rhs: Self)<br>    case subs(lhs: Self, rhs: Self)<br>    case mult(lhs: Self, rhs: Self)<br>    case division(lhs: Self, rhs: Self)<br>    case number(value: Double)<br>}<br><br>extension Operation {<br>    var compute: Double {<br>        switch self {<br>        case let .add(lhs: lhs, rhs: rhs): lhs.compute + rhs.compute<br>        case let .subs(lhs: lhs, rhs: rhs): lhs.compute - rhs.compute<br>        case let .mult(lhs: lhs, rhs: rhs): lhs.compute * rhs.compute<br>        case let .division(lhs: lhs, rhs: rhs): lhs.compute / rhs.compute<br>        case let .number(value): value<br>        }<br>    }<br>}<br><br>let operation: Operation = .add(<br>    lhs: .number(value: 3),<br>    rhs: .mult(<br>        lhs: .number(value: 5),<br>        rhs: .division(<br>            lhs: .subs(<br>                lhs: .number(value: 8),<br>                rhs: .number(value: 7)<br>            ),<br>            rhs: .number(value: 6)<br>        )<br>    )<br>)<br><br>operation.compute // 3.833333333333333</pre><p>With this approach, interpreting the Operation type as a String becomes trivial.</p><pre>extension Operation {<br>    var asString: String {<br>        switch self {<br>        case let .add(lhs: lhs, rhs: rhs): &quot;(\(lhs.asString) + \(rhs.asString))&quot;<br>        case let .subs(lhs: lhs, rhs: rhs): &quot;(\(lhs.asString) - \(rhs.asString))&quot;<br>        case let .mult(lhs: lhs, rhs: rhs): &quot;(\(lhs.asString) * \(rhs.asString))&quot;<br>        case let .division(lhs: lhs, rhs: rhs): &quot;(\(lhs.asString) / \(rhs.asString))&quot;<br>        case let .number(value): &quot;\(value)&quot;<br>        }<br>    }<br>}<br><br>operation.asString // (3.0 + (5.0 * ((8.0 - 7.0) / 6.0)))</pre><p>But… of course, the other type of extensibility won’t be that easy! Adding new arithmetic operations will be impossible if the enum is in a library we can’t control. We can’t simply add a new case!</p><p>Is there any hope for both kinds of extensibility? Well, turns out there is.</p><h3>The Tagless Final approach</h3><p>What would happen if our Operation was a protocol like in the first approach, but instead of conformant types per arithmetic operation, we had static functions returning Self? Something like this:</p><pre>protocol Operation {<br>    static func add(lhs: Self, rhs: Self) -&gt; Self<br>    static func subs(lhs: Self, rhs: Self) -&gt; Self<br>    static func mult(lhs: Self, rhs: Self) -&gt; Self<br>    static func division(lhs: Self, rhs: Self) -&gt; Self<br>    static func number(value: Double) -&gt; Self<br>}</pre><p>Now, for each interpretation we need, we can simply have a type:</p><pre>struct DoubleInterpreter {<br>    var compute: Double<br>}<br><br>extension DoubleInterpreter: Operation {<br>    static func add(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(compute: lhs.compute + rhs.compute)<br>    }<br><br>    static func subs(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(compute: lhs.compute - rhs.compute)<br>    }<br><br>    static func mult(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(compute: lhs.compute * rhs.compute)<br>    }<br><br>    static func division(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(compute: lhs.compute / rhs.compute)<br>    }<br><br>    static func number(value: Double) -&gt; Self {<br>        .init(compute: value)<br>    }<br>}</pre><p>Then, we create the value and compute it.</p><pre>let doubleInterpreter: DoubleInterpreter = .add(<br>    lhs: .number(value: 3),<br>    rhs: .mult(<br>        lhs: .number(value: 5),<br>        rhs: .division(<br>            lhs: .subs(<br>                lhs: .number(value: 8),<br>                rhs: .number(value: 7)<br>            ),<br>            rhs: .number(value: 6)<br>        )<br>    )<br>)<br><br>doubleInterpreter.compute // 3.833333333333333</pre><p>Now, creating a new operation is as easy as creating a new protocol that conforms to the previous Operation type:</p><pre>protocol OperationWithPow: Operation {<br>    associatedtype Op: Operation<br>    static func pow(base: Op, exp: Int) -&gt; Self<br>}<br><br>extension DoubleInterpreter: OperationWithPow {<br>    static func pow(base: Self, exp: Int) -&gt; Self {<br>        .init(compute: Foundation.pow(base.compute, Double(exp)))<br>    }<br>}<br><br>DoubleInterpreter.pow(base: doubleInterpreter, exp: 3).compute // 56.328</pre><p>Finally, let’s try to interpret the operation as a String value rather than a Double:</p><pre>struct StringInterpreter {<br>    var asString: String<br>}<br><br>extension StringInterpreter: OperationWithPow {<br>    static func pow(base: Self, exp: Int) -&gt; Self {<br>        .init(asString: &quot;(\(base.asString) ^ \(exp))&quot;)<br>    }<br><br>    static func add(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(asString: &quot;(\(lhs.asString) + \(rhs.asString))&quot;)<br>    }<br><br>    static func subs(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(asString: &quot;(\(lhs.asString) - \(rhs.asString))&quot;)<br>    }<br><br>    static func mult(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(asString: &quot;(\(lhs.asString) * \(rhs.asString))&quot;)<br>    }<br><br>    static func division(lhs: Self, rhs: Self) -&gt; Self {<br>        .init(asString: &quot;(\(lhs.asString) / \(rhs.asString))&quot;)<br>    }<br><br>    static func number(value: Double) -&gt; Self {<br>        .init(asString: &quot;\(value)&quot;)<br>    }<br>}<br><br>let stringInterpreter = StringInterpreter.pow(<br>    base: .add(<br>        lhs: .number(value: 3),<br>        rhs: .mult(<br>            lhs: .number(value: 5),<br>            rhs: .division(<br>                lhs: .subs(<br>                    lhs: .number(value: 8),<br>                    rhs: .number(value: 7)<br>                ),<br>                rhs: .number(value: 6)<br>            )<br>        )<br>    ),<br>    exp: 3<br>)<br><br>stringInterpreter.asString // ((3.0 + (5.0 * ((8.0 - 7.0) / 6.0))) ^ 3)</pre><p>In case we want to reuse the same operation amongst different interpreters, we can leverage generic functions like this:</p><pre>func operation&lt;Op: OperationWithPow&gt;() -&gt; Op {<br>    .pow(<br>        base: .add(<br>            lhs: .number(value: 3),<br>            rhs: .mult(<br>                lhs: .number(value: 5),<br>                rhs: .division(<br>                    lhs: .subs(<br>                        lhs: .number(value: 8),<br>                        rhs: .number(value: 7)<br>                    ),<br>                    rhs: .number(value: 6)<br>                )<br>            )<br>        )<br>        ,<br>        exp: 3<br>    )<br>}<br><br>(operation() as DoubleInterpreter).compute // 56.32870370370369<br>(operation() as StringInterpreter).asString // ((3.0 + (5.0 * ((8.0 - 7.0) / 6.0))) ^ 3)</pre><h3>Conclusion</h3><p>As you can see, the expression problem is a fascinating problem about extensibility and the <a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle">Open-closed principle</a> that neither the “object-oriented approach” nor the “functional approach” fully addresses.</p><p>Interestingly, a rather “not so common” way of using protocols with static functions returning Self “solves” the problem, not without introducing a slight amount of complexity in terms of generic functions for reusability.</p><p>If you want to know more, make sure to check out <a href="https://www.youtube.com/watch?v=EsanJ7_U89A">this great talk by Brandon Kase</a> about this topic.</p><p>As always, the right approach depends on your actual needs for extensibility. Choosing the right balance between extensibility and complexity is hard. Choose wisely.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=248acd53dc5c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/the-expression-problem-in-swift-248acd53dc5c">The Expression Problem in Swift</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Tale of Two Error Types in Swift]]></title>
            <link>https://medium.com/the-swift-cooperative/a-tale-of-two-error-types-in-swift-e157f272bbd1?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/e157f272bbd1</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Thu, 18 Jul 2024 13:52:33 GMT</pubDate>
            <atom:updated>2024-07-18T20:07:35.195Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FHFDQgc93lrGYfEg" /><figcaption>Photo by <a href="https://unsplash.com/@davfts?utm_source=medium&amp;utm_medium=referral">David Pupăză</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>I remember the first time I read about <a href="https://fsharpforfunandprofit.com/rop/">Railway Oriented Programming</a>. It was back in September 2015. We already had Swift 2.0 with its new error-handling model, allowing us to annotate any function as throws. That new way of handling errors had some issues though:</p><ol><li>It was impossible to use with asynchronous functions and callbacks.</li><li>It was untyped. When I started learning Swift, I wanted to type everything strongly! I wanted the compiler to prevent as many errors as possible, have exhaustive error checking, etc. Years later, I realized that throwing untyped errors is usually the most ergonomic way to handle errors in most scenarios.</li><li>Coming from Objective-C, where exceptions <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html">had different semantics</a>, and weren’t used for normal error control flow (but for exceptional fatal programming errors at runtime), it was hard to embrace this new way of thinking about errors. We no longer throw Exceptions, we throw Errors now. And we are talking about “normal” errors <a href="https://developer.apple.com/swift/blog/?id=29">as we can read in the Swift blog</a>.</li><li>The impact of marking a function as throws is not negligible and will force you to either handle it in every usage of that function or mark the calling function as throws. This means that refactoring code to start throwing errors might require quite a lot of changes across your code base. If you come from the Java world, this is similar to checked exceptions, but without the specific error type. And well, in the Java community, these kinds of exceptions <a href="https://www.artima.com/articles/the-trouble-with-checked-exceptions">are considered a bad practice</a>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*x05aBVKIV32hQFuZap8hpg.jpeg" /></figure><p>The Result type was embraced by the community back then as the idiomatic way to solve the first two problems. It could be used in asynchronous contexts, it avoided impossible states (like having both an error and a success value) and we could decide to type the error if needed.</p><p>Even if we didn’t have the Result type yet (it wouldn’t be included in the standard library until 2019 with Swift 5.0), we could very easily build it ourselves. Enums with associated values were available in the first version of Swift, and they were the fundamental building block for a lot of the new type safety possibilities that Swift brought us:</p><pre>enum Result&lt;Value, Failure&gt; where Failure: Error {<br>    case success(Value)<br>    case failure(Failure)<br>}</pre><p>Throughout the article, we’ll develop a very simple example to see the pros and cons of different error mechanisms available in Swift. From the previous Result type, to the newer typed throws available in Swift 6.</p><h3>Result type</h3><p>Let’s imagine that we are developing a function that:</p><ul><li>Downloads some integer value from an API.</li><li>Sends an email using that previous integer value.</li></ul><p>Both steps can error of course.</p><pre>enum APIError: Error {<br>    case unknown<br>}<br><br>enum EmailError: Error {<br>    case unknown<br>}<br><br>enum APIOrEmailError: Error {<br>    case api(APIError)<br>    case email(EmailError)<br>}<br><br>func downloadData(callback: (Result&lt;Int, APIError&gt;) -&gt; Void) {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func sendEmail(value: Int, callback: (Result&lt;Void, EmailError&gt;) -&gt; Void) {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func downloadDataAndSendEmail(callback: (Result&lt;Void, APIOrEmailError&gt;) -&gt; Void) {<br>    downloadData { result in<br>        switch result {<br>        case .success(let value):<br>            sendEmail(value: value) { result in<br>                switch result {<br>                case .success:<br>                    callback(.success(()))<br>                case .failure(let error):<br>                    callback(.failure(.email(error)))<br>                }<br>            }<br><br>        case .failure(let error):<br>            callback(.failure(.api(error)))<br>        }<br>    }<br>}</pre><p>As you can see, we have two main issues:</p><ul><li><strong>Callbacks don’t compose nicely</strong>, so code gets ugly very easily, leading us to a hard-to-read-and-maintain pyramid of doom with lots of nested levels of indentation.</li><li>We need to interpret explicitly the errors from the different layers and decide how we want to expose them upwards. In this case, we simply forwarded both the API and email errors as part of the associated values in the compound error. Take into account that <strong>any time we introduce new errors, all usages of the <em>downloadDataAndSendEmail</em> function</strong> <strong>will break</strong>. That’s the price to pay for the error type safety and that’s why library authors should prefer untyped errors in most cases.</li></ul><p>But even if we remove the explicit types and simply have Error in the Result types, the implementation is still hard to read and doesn’t compose very well.</p><p>Let’s look for some alternatives.</p><h3>Combine</h3><p>The aforementioned Railway Oriented Programming was the solution to the first of our problems. It was a solution to compose functions that can fail in a nice way. In order to do so, we need to two pieces:</p><ol><li>A type that represents an asynchronous computation that can fail.</li><li>A flatMap and mapError combinators to chain functions returning those types.</li></ol><p>At that time, we didn’t have Combine yet (it would be introduced in 2019), but we could very easily create an asynchronous version of that result type like this:</p><pre>struct AsyncResult&lt;Value, Failure&gt; where Failure: Error {<br>    var run: ((Result&lt;Value, Failure&gt;) -&gt; Void) -&gt; Void<br><br>    // Some combinators like flatMap, mapError, etc<br>}</pre><p>For simplicity, let’s just use Combine for the example</p><pre>func downloadData() -&gt; AnyPublisher&lt;Int, APIError&gt; {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func sendEmail(value: Int) -&gt; AnyPublisher&lt;Void, EmailError&gt; {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func downloadDataAndSendEmail() -&gt; AnyPublisher&lt;Void, APIOrEmailError&gt; {<br>    downloadData()<br>        .mapError(APIOrEmailError.api)<br>        .flatMap { sendEmail(value: $0).mapError(APIOrEmailError.email) }<br>        .eraseToAnyPublisher()<br>}</pre><p>Things look much better! Well… it depends 😅. Even if the code is succinct, there are several underlying Combine’s operators that we needed to use: mapError, flatMap. Even if that code might read well if you are used to Combine and reactive paradigms, writing that code is not straightforward.</p><p>What happens if we remove the typed errors? If both downloadData and sendEmail still return APIError and EmailError, the implementation is still quite complex:</p><pre>func downloadDataAndSendEmail() -&gt; AnyPublisher&lt;Void, any Error&gt; {<br>    downloadData()<br>        .mapError { $0 }<br>        .flatMap { sendEmail(value: $0).mapError { $0 } }<br>        .eraseToAnyPublisher()<br>}</pre><p>Fortunately, if we are lucky enough to control downloadData and sendEmail functions to change their signatures to use any Error , things get much better:</p><pre>func downloadDataAndSendEmail() -&gt; AnyPublisher&lt;Void, any Error&gt; {<br>    downloadData()<br>        .flatMap(sendEmail)<br>        .eraseToAnyPublisher()<br>}</pre><p>No need for ugly mapError functions and things compose much nicer. Just a single flatMap function.</p><p>We can even simplify things further by removing AnyPublisher and using the Publisher existential type directly.</p><pre>func downloadDataAndSendEmail() -&gt; any Publisher&lt;Void, any Error&gt; {<br>    downloadData().flatMap(sendEmail)<br>}</pre><p>As you can see. Removing the specific error type from all the steps in the chain had huge implications for the composability and readability of the code.</p><p>Let’s compare this implementation with a final one. Arguably, the most idiomatic way of handling errors in Swift, via throwing functions.</p><h3>Async throwing functions</h3><pre>func downloadData() async throws(APIError) -&gt; Int {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func sendEmail(value: Int) async throws(EmailError) {<br>    fatalError(&quot;Implementation is not important&quot;)<br>}<br><br>func downloadDataAndSendEmail() async throws(APIOrEmailError) {<br>    let value: Int<br>    do {<br>        value = try await downloadData()<br>    } catch {<br>        throw .api(error)<br>    }<br><br>    do {<br>        try await sendEmail(value: value)<br>    } catch {<br>        throw .email(error)<br>    }<br>}</pre><p>Even if there are quite a few mappings of errors via the do/catch clauses, I read that much better than this other version:</p><pre>downloadData()<br>    .mapError(APIOrEmailError.api)<br>    .flatMap { sendEmail(value: $0).mapError(APIOrEmailError.email) }<br>    .eraseToAnyPublisher()</pre><p>But readability is quite subjective and depends a lot on people’s experience and preferences…</p><p>Let’s see what happens now if we decide to remove the typed error just from the downloadDataAndSendEmail function.</p><pre>func downloadDataAndSendEmail() async throws {<br>    let value = try await downloadData()<br>    try await sendEmail(value: value)<br>}</pre><p>You can see here the great ergonomics that Typed throws provides in Swift. Even if downloadData and sendEmail still throw typed errors, having several functions throwing different typed errors means that the enclosing function throws any Error, which is great and makes a lot of sense. No need to handle ugly error mapping!</p><p>This solves one of the main problems of checked exceptions in Java. Each time you use a function that throws a checked exception, you are forced to either change your signature to account for that new exception or just catch it and rethrow it in a way that you don’t change your function’s signature. Swift handles this scenario much better.</p><pre>func sendEmail(value: Int) async throws(EmailError) {<br>    fatalError()<br>}<br><br>func downloadDataAndSendEmail() async throws {<br>    // It doesn&#39;t matter if sendEmail throws a typed error.<br>    // As `downloadDataAndSendEmail` throws any Error, this typed<br>    // error is automatically converted to `any Error`<br>    try await sendEmail(value: 3)<br>}</pre><h3>Better error semantics</h3><p>Even if you might think that you won’t use typed throws in your code base much, which might be true for app development to be honest, typed throws allows Swift to have much better error semantics.</p><p>Let’s imagine we want to model a repository generic over a Model type, with an async sequence that returns those types over time. In Swift 5.10, with untyped throws, this is the best we can do.</p><pre>protocol Repository&lt;Model&gt; {<br>    associatedtype Model: Equatable<br>    associatedtype ModelSequence: AsyncSequence where ModelSequence.Element == Model<br><br>    var modelSequence: ModelSequence { get }<br>}<br><br>extension Repository {<br>    func onModel(action: @escaping (Model) -&gt; Void) {<br>        Task {<br>            for try await model in modelSequence {<br>                action(model)<br>            }<br>        }<br>    }<br>}</pre><p>There’s not a big problem with that code, but there’s no way to convey that the async sequence never fails. That means that iterating over that sequence in the onModel function will always force us to use for try await, even for cases where that sequence would never fail.</p><p>And this is because of how AsyncIteratorProtocol is defined:</p><pre>protocol AsyncIteratorProtocol {<br>    associatedtype Element<br><br>    mutating func next() async throws -&gt; Self.Element?<br>}</pre><p>As you can see, the next function is async throws and the only way for Swift to know that an async iterator won’t throw is to know the specific implementation of AsyncIteratorProtocol with a non-throwing next function.</p><p>In Swift 6, things get much better:</p><pre>protocol Repository&lt;Model&gt; {<br>    associatedtype Model: Equatable<br>    associatedtype ModelSequence: AsyncSequence&lt;Model, Never&gt;<br><br>    var modelSequence: ModelSequence { get }<br>}<br><br>extension Repository {<br>    func onModel(action: @escaping (Model) -&gt; Void) {<br>        Task {<br>            for await model in modelSequence {<br>                action(model)<br>            }<br>        }<br>    }<br>}</pre><p>We can now leverage the primary associated types in AsyncSequence and use the Never type to ensure that the async sequence will never fail. That means that the onModel function can iterate over the sequence with just for await instead of for try await. And that’s because AsyncIteratorProtocol is defined as follows:</p><pre>protocol AsyncIteratorProtocol&lt;Element, Failure&gt; {<br>    associatedtype Element<br><br>    mutating func next(isolation actor: isolated (any Actor)?) async throws(Self.Failure) -&gt; Self.Element?<br>}</pre><p>As you can see, we can use the Failure primary associated type in the function signature via throws Failure. And throws Never will be the same as not having the throws word at all.</p><p>This might seem like a negligible small thing that won’t have any impact in your code. That might be true, but Swift getting smarter about how to handle errors won’t hurt 😅.</p><h3>Conclusion</h3><p>In general, I think typed throws is a very welcome addition to the Swift language. It doesn’t break existence code bases and allows developers to choose the best way to model errors (either typed or untyped) while keeping great ergonomics and avoiding the problems we had in other languages like Java with checked exceptions.</p><p>Library authors would still need to be wary about using typed errors because of the evolution problems, but for other type of code, it gives us some great flexibility and would allow the compiler to force several error code paths for specific, important domain errors where we want that extra type safety.</p><p>Looking forward to Swift 6.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e157f272bbd1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/a-tale-of-two-error-types-in-swift-e157f272bbd1">A Tale of Two Error Types in Swift</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Dark Side of Unidirectional Architectures in Swift]]></title>
            <link>https://medium.com/the-swift-cooperative/the-dark-side-of-unidirectional-architectures-in-swift-e4acf243ff1c?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/e4acf243ff1c</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Fri, 10 May 2024 23:36:40 GMT</pubDate>
            <atom:updated>2024-05-13T07:31:54.245Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*x144kA4zT8e7-2Uj" /><figcaption>Photo by <a href="https://unsplash.com/@sseeker?utm_source=medium&amp;utm_medium=referral">Stormseeker</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>A year ago, I wrote a long article comparing <a href="https://betterprogramming.pub/different-flavors-of-unidirectional-architectures-in-swift-781a01380ef6">different flavors of unidirectional architectures in Swift</a>. Here, I will present what I consider to be the main problem of them all. Well, it’s not a problem of unidirectional architectures per se. Rather, it’s a problem of modeling the actions or events as values. I usually call it the<strong> “ping-pong problem”</strong>. And it’s all about the “jumps” we have to do between different places of the code to have a cohesive understanding of the whole flow. Let’s see a simple example first.</p><pre>func handle(event: Event) {<br>    switch event {<br>    case .onAppear:<br>        state = .loading<br>        return .task {<br>            let numbers = try await apiClient.numbers()<br>            await send(.numbersDownloaded(numbers))<br>        }<br>    <br>    case .numbersDownloaded(let values):<br>        state = .loaded(values)<br>        return .none<br>    }<br>}</pre><p>Even if that code is pretty easy to read, there’s an even simpler version of that:</p><pre>func onAppear() {<br>    Task {<br>        state = .loading<br>        let numbers = try await apiClient.numbers()<br>        state = .loaded(numbers)<br>    }<br>}</pre><p>It’s not only less code. It’s more cohesive and understandable code.</p><p>When modeling events as values, we lose the ability to read all the code from top to bottom as a cohesive piece of behavior. We now have to go event by event to form the whole understanding of a particular flow: some input event triggers some feedback event, which might trigger another feedback event, etc… There can be lots of different back and forths between those events, which makes it harder to understand the code.</p><p>But as always, applying any architecture to any trivial example will look like overengineering.</p><p>Imagine using something like CLEAN architecture in the previous code.</p><pre>func onAppear() {<br>    Task {<br>        state = .loading<br>        let numbers = try await usecase.numbers()<br>        state = .loaded(values)<br>    }<br>}<br><br>class UseCase {<br>    let repository: RepositoryContract<br><br>    func numbers() async throws -&gt; [Int] {<br>        repository.numbers()<br>    }<br>}<br><br>protocol RepositoryContract {<br>    func numbers() async throws -&gt; [Int]<br>}<br><br>class Repository: RepositoryContract {<br>    let apiClient: APIClient<br><br>    func numbers() async throws -&gt; [Int] {<br>        apiClient.numbers()<br>    }<br>}</pre><p>In both cases, we’ve introduced unneeded indirection. We have to jump to lots of different places to understand the code.</p><p>Even if we need layers and abstractions to manage complexity, allow flexibility, and facilitate testing, we shouldn’t forget that we are also losing an important trait in software: <a href="https://htmx.org/essays/locality-of-behaviour/">“locality of behavior”</a> (also called <em>local reasoning</em>). As always, everything is a trade-off.</p><p>Modeling events as values has great advantages, like having full traceability of the state changes and what events produced them. But allowing that comes with risks in readability. And remember, code should always be optimized for readability.</p><p>Let’s see now a more complex example, inspired by the <a href="https://github.com/spotify/mobius/wiki/The-Mobius-Workflow">Mobius Workflow</a> from Spotify.</p><h3>The ping-pong problem in practice</h3><p>Taking the architecture I proposed in <a href="https://betterprogramming.pub/different-flavors-of-unidirectional-architectures-in-swift-781a01380ef6">my previous blog post about unidirectional architectures</a>, let’s implement a simple login screen with the following requirements:</p><ul><li>The user can enter their email and password to log in.</li><li>In case of no internet, the user won’t be allowed to log in.</li><li>The email has both a local validation first and a remote validation after that.</li><li>The password has no validation.</li><li>Before attempting to log in, the user will be prompted for confirmation.</li></ul><p>We’ll have three different implementations, but first, let’s have the two common pieces: the state and the effects.</p><pre>struct State: Equatable {<br>    var online: Bool = true<br>    var email: Email = .init()<br>    var password: String = &quot;&quot;<br>    var loggingIn: Bool = false<br><br>    var canLogin: Bool {<br>        online &amp;&amp; email.valid &amp;&amp; password.count &gt; 8<br>    }<br><br>    struct Email: Equatable {<br>        var rawValue: String = &quot;&quot;<br>        var valid: Bool = false<br>        var currentValidation: Validation? = nil<br><br>        enum Validation {<br>            case local<br>            case remote<br>        }<br>    }<br>}<br><br>enum Effects {<br>    static func login() async throws -&gt; String {<br>        &quot;dummy token&quot;<br>    }<br><br>    static func localEmailValidation(_ email: String) -&gt; Bool {<br>        email.contains(&quot;@&quot;)<br>    }<br><br>    static func remoteEmailValidation(_ email: String) async -&gt; Bool {<br>        return Bool.random()<br>    }<br><br>    static func showConfirmation(text: String, yes: () async -&gt; Void, no: () async -&gt; Void) async {}<br>    static func onlineStream() -&gt; AsyncStream&lt;Bool&gt; {}<br>}</pre><h4>Case 1: Complete unidirectional architecture</h4><pre>class ViewReducer: Reducer {<br>    enum Input {<br>        case onAppear<br>        case emailInputChanged(String)<br>        case passwordInputChanged(String)<br>        case loginButtonClicked<br>    }<br><br>    enum Feedback {<br>        case internetStateChanged(online: Bool)<br>        case loginSuccessful(token: String)<br>        case loginFailed<br>        case emailLocalValidation(valid: Bool)<br>        case emailRemoteValidation(valid: Bool)<br>        case loginAlertConfirmation(confirm: Bool)<br>    }<br><br>    enum Output {<br>        case showErrorToast<br>        case loginFinished(_ token: String)<br>    }<br><br>    func reduce(message: Message&lt;Input, Feedback&gt;, into state: inout State) -&gt; Effect&lt;Feedback, Output&gt; {<br>        switch message {<br>        // MARK: - Input events<br>        case .input(.onAppear):<br>            return .run { send in<br>                for await online in Effects.onlineStream() {<br>                    await send(.internetStateChanged(online: online))  <br>                }<br>            }<br><br>        case .input(.emailInputChanged(let value)):<br>            state.email.rawValue = value<br>            state.email.valid = false<br>            state.email.currentValidation = .local<br><br>            let email = state.email.rawValue<br>            return .run { send in<br>                let valid = Effects.localEmailValidation(email)<br>                await send(.emailLocalValidation(valid: valid))<br>            }<br><br>        case .input(.passwordInputChanged(let value)):<br>            state.password = value<br>            return .none<br><br>        case .input(.loginButtonClicked):<br>            guard state.canLogin else {<br>                fatalError(&quot;Shouldn&#39;t be here&quot;)<br>            }<br><br>            return .run { send in<br>                await Effects.showConfirmation(text: &quot;Are you sure?&quot;) {<br>                    await send(.loginAlertConfirmation(confirm: true))<br>                } no: {<br>                    await send(.loginAlertConfirmation(confirm: false))<br>                }<br>            }<br><br>        // MARK: - Feedback events<br>        case .feedback(.emailLocalValidation(valid: let valid)):<br>            guard valid else {<br>                state.email.currentValidation = nil<br>                return .none<br>            }<br>            state.email.currentValidation = .remote<br>            let email = state.email.rawValue<br>            return .run { send in<br>                let valid = await Effects.remoteEmailValidation(email)<br>                await send(.emailRemoteValidation(valid: valid))<br>            }<br><br>        case .feedback(.emailRemoteValidation(valid: let valid)):<br>            state.email.valid = valid<br>            state.email.currentValidation = nil<br>            return .none<br><br>        case .feedback(.loginAlertConfirmation(true)):<br>            state.loggingIn = true<br>            return .run { send in<br>                do {<br>                    let token = try await Effects.login()<br>                    await send(.loginSuccessful(token: token))<br>                } catch {<br>                    await send(.loginFailed)<br>                }<br>            }<br><br>        case .feedback(.loginAlertConfirmation(false)):<br>            return .none<br><br>        case .feedback(.loginSuccessful(token: let token)):<br>            state.loggingIn = false<br>            return .output(.loginFinished(token))<br><br>        case .feedback(.loginFailed):<br>            state.loggingIn = false<br>            return .output(.showErrorToast)<br><br>        case .feedback(.internetStateChanged(let online)):<br>            state.online = online<br>            return .none<br>        }<br>    }<br>}</pre><p>As you can see, to figure out all the login flow, we need to understand how all these different events are handled:</p><ul><li><em>emailInputChanged</em> input -&gt; <em>emailLocalValidation</em> feedback -&gt; <em>emailRemoteValidation </em>feedback<em>.</em></li><li><em>loginButtonClicked</em> input -&gt; <em>loginAlertConfirmation</em> feedback -&gt; <em>loginSuccessful</em> feedback.</li></ul><p>There are quite a few events and back and forth between them to understand what happens when the user enters their email and when the login button is tapped. Even if we put together the related events in the switch statement to minimize the pain of jumping between them, it’s still very cumbersome and requires painful jumps and indirections.</p><p>The feedback events are the ones producing all this back and forth. 🤔 What would happen if we remove them? Let’s see.</p><h4>Case 2: Removing feedback events</h4><p>Let’s now write a view model from scratch. This time, only modeling the input and output events as values.</p><pre>class ViewModel {<br>    enum Input {<br>        case onAppear<br>        case emailInputChanged(String)<br>        case passwordInputChanged(String)<br>        case loginButtonClicked<br>    }<br><br>    enum Output {<br>        case showErrorToast<br>        case loginFinished(_ token: String)<br>    }<br><br>    private(set) var state: State<br>    let stream: AsyncStream&lt;Output&gt;<br>    private let continuation: AsyncStream&lt;Output&gt;.Continuation<br><br>    init() {<br>        let (stream, continuation) = AsyncStream.makeStream(of: Output.self)<br>        self.stream = stream<br>        self.continuation = continuation<br>        self.state = .init()<br>    }<br><br>    func send(_ input: Input) {<br>        switch input {<br>        case .onAppear:<br>            Task {<br>                for await online in Effects.onlineStream() {<br>                    self.state.online = online<br>                }<br>            }<br><br>        case .emailInputChanged(let value):<br>            state.email.rawValue = value<br>            state.email.valid = false<br>            state.email.currentValidation = .local<br>            let valid = Effects.localEmailValidation(value)<br><br>            guard valid else {<br>                state.email.currentValidation = nil<br>                return<br>            }<br><br>            state.email.currentValidation = .remote<br>            Task {<br>                let valid = await Effects.remoteEmailValidation(value)<br>                state.email.valid = valid<br>                state.email.currentValidation = nil<br>            }<br><br>        case .passwordInputChanged(let value):<br>            state.password = value<br><br>        case .loginButtonClicked:<br>            guard state.canLogin else {<br>                fatalError(&quot;Shouldn&#39;t be here&quot;)<br>            }<br><br>            Task {<br>                await Effects.showConfirmation(text: &quot;Are you sure?&quot;) {<br>                    state.loggingIn = true<br>                    defer {<br>                        state.loggingIn = false<br>                    }<br>                    do {<br>                        let token = try await Effects.login()<br>                        continuation.yield(.loginFinished(token))<br>                    } catch {<br>                        continuation.yield(.showErrorToast)<br>                    }<br>                } no: {<br>                    // Nothing<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Removing the feedback events has simplified the code quite a bit. We can now read top to bottom again and the related parts are together, having a much more cohesive code than before.</p><h4>Case 3: Removing events as values</h4><p>But, to be honest… unless we need to log or have traceability with those input events, it’s much simpler to avoid that massive switch and rely on plain, simple functions to replace them.</p><pre>class ViewModel {<br>    enum Output {<br>        case showErrorToast<br>        case loginFinished(_ token: String)<br>    }<br><br>    private(set) var state: State<br>    let stream: AsyncStream&lt;Output&gt;<br>    private let continuation: AsyncStream&lt;Output&gt;.Continuation<br><br>    init() {<br>        let (stream, continuation) = AsyncStream.makeStream(of: Output.self)<br>        self.stream = stream<br>        self.continuation = continuation<br>        self.state = .init()<br>    }<br><br>    func onAppear() {<br>        Task {<br>            for await online in Effects.onlineStream() {<br>                self.state.online = online<br>            }<br>        }<br>    }<br><br>    func emailInputChanged(value: String) {<br>        state.email.rawValue = value<br>        state.email.valid = false<br>        state.email.currentValidation = .local<br><br>        let valid = Effects.localEmailValidation(value)<br><br>        guard valid else {<br>            state.email.currentValidation = nil<br>            return<br>        }<br><br>        state.email.currentValidation = .remote<br>        Task {<br>            let valid = await Effects.remoteEmailValidation(value)<br>            state.email.valid = valid<br>            state.email.currentValidation = nil<br>        }<br>    }<br><br>    func passwordInputChanged(value: String) {<br>        state.password = value<br>    }<br><br>    func loginButtonClicked() {<br>        guard state.canLogin else {<br>            fatalError(&quot;Shouldn&#39;t be here&quot;)<br>        }<br><br>        Task {<br>            await Effects.showConfirmation(text: &quot;Are you sure?&quot;) {<br>                state.loggingIn = true<br>                defer {<br>                    state.loggingIn = false<br>                }<br>                do {<br>                    let token = try await Effects.login()<br>                    continuation.yield(.loginFinished(token))<br>                } catch {<br>                    continuation.yield(.showErrorToast)<br>                }<br>            } no: {<br>                // Nothing<br>            }<br>        }<br>    }<br>}</pre><p>In my opinion, this last code reads much better than any other.</p><h3>Conclusion</h3><p>As you can see, feedback events and <a href="https://dev.to/feresr/a-case-against-the-mvi-architecture-pattern-1add">the back-and-forth between them can impact the readability of your code</a>, making it quite hard to understand and build a cohesive understanding of the whole functionality.</p><p>Take into account that the example is thoroughly presented to maximize these problems. We could have other examples where having a more constrained way of controlling our state, by only allowing it to mutate via events, results in a much more understandable code.</p><p>This article only wanted to highlight the price we are paying when adopting these kinds of architectures. Not only because of the ping-pong problem but also because of the impedance mismatch when working with SwiftUI, where bindings and other property wrappers don’t fit naturally and require extra boilerplate.</p><p>Not only that, the impedance mismatch happens as well in terms of Xcode and the static analysis and navigation ergonomics it has. We lose the ability to jump directly to the definition of the function (we now jump to the case in the enum, having to search that case in the switch statement to see the implementation of that particular event), or use handy features like “call hierarchy” in Xcode.</p><p>In my opinion, a safer approach for most people would be to rely on a <a href="https://martinfowler.com/bliki/HumbleObject.html">humble view model</a> (or some other kind of thin controller/coordinator layer) and move as much logic as possible to <a href="https://swiftology.io/articles/tydd-part-4/">a properly modeled domain</a>. Interestingly enough, this was what <a href="https://medium.com/jobandtalenteng/ios-architecture-an-state-container-based-approach-4f1a9b00b82e">I proposed back in 2017, in my first blog post about architecture</a> 😅. But that “loose architecture” could very easily get out of hand if not careful, whereas unidirectional architectures usually have much stronger constraints and are more opinionated about where and how state and effects are handled. As always, everything has its pros and cons. Choose wisely.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4acf243ff1c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/the-dark-side-of-unidirectional-architectures-in-swift-e4acf243ff1c">The Dark Side of Unidirectional Architectures in Swift</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Aspect-Oriented Programming in Swift]]></title>
            <link>https://medium.com/the-swift-cooperative/aspect-oriented-programming-in-swift-f2366350c527?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/f2366350c527</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Mon, 04 Mar 2024 14:29:39 GMT</pubDate>
            <atom:updated>2024-03-06T20:05:46.880Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NzChU9-d3taPXvl2" /><figcaption>Photo by <a href="https://unsplash.com/@miracleday?utm_source=medium&amp;utm_medium=referral">Elena Mozhvilo</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>Aspect-oriented programming (AOP from now on) is all about adding cross-cutting concerns to our code base in a scalable manner.</p><p>I still remember the first time I came across the term “Aspect Oriented Programming”. It was quite a long time ago, Swift didn’t exist yet, and we were happily writing Objective-C code. <a href="https://github.com/orta/ARAnalytics">ARAnalytics</a> was one of the first libraries to adopt the AOP paradigm to simplify adding analytics across our code base.</p><p>Taking a <a href="https://github.com/orta/ARAnalytics/blob/888744016bb4f53ad5b652819406645bd21f80c7/ARDSL.m#L104">closer look at the code</a>, we can see that implementing AOP in Objective-C is just implementing method swizzling, as <a href="https://twitter.com/steipete">Peter Steinberger</a> stated in his <a href="https://github.com/steipete/Aspects">Aspects</a> library.</p><blockquote>Think of Aspects as method swizzling on steroids.</blockquote><p>Unlike Objective-C, Swift is a really strict, statically-typed language with not many capabilities to intercept messages and change behavior at runtime. In practice, what this means is that we have to set some foundations of good design in our code so the impact of applying cross-cutting concerns to our codebase is minimal. We are going to need two main ingredients:</p><ul><li>Some sort of <strong>dependency injection</strong> capabilities so we can apply late-binding and change the underlying implementation at will.</li><li>The <strong>decorator pattern</strong>, which is the interception design pattern that allows us to change the behavior that we want at runtime.</li></ul><p>Let’s start!</p><h3>AOP in Swift: towards a good design</h3><p>A very common approach to model our application is to have a single “store/repository/service/aggregate” that bundles all the capabilities around a specific domain model in our application. If our application is about managing todos, we’ll have some sort of “TodoService”. If our app is about managing TV Shows, we’ll have a “ShowService”. You get the idea. While that approach is a perfectly valid one for lots of medium-sized apps, it can be problematic for other, more complex applications. As with most things in software, everything depends.</p><p>Imagine a ShowService that looks like this:</p><pre>protocol ShowService {<br>    func allShows() async -&gt; [Show]<br>    func allEpisodes(for show: Show) async -&gt; [Episode]<br>    func markEpisodeAsWatched(episode: Episode) async<br>    func markShowAsWatched(_ show: Show, until episode: Episode?) async<br>}</pre><p>As we said, even if most things in software are subjective and open to discussion depending on the specific context, it’s nice to have some “principles” that guide us. And those principles are usually the <a href="https://en.wikipedia.org/wiki/SOLID">SOLID</a> ones.</p><h4>ISP violation</h4><p>As most big interfaces, it&#39;s very likely that ShowService will break the Interface Segregation Principle, by forcing clients to implement methods that they don’t need. It’s very common to see this when implementing mocks for instance, with methods like XCTFail to verify that they aren’t called.</p><pre>struct ShowServiceMock: ShowService {<br>    var shows: [Show]<br><br>    func allShows() -&gt; [Show] {<br>        shows<br>    }<br>    <br>    func allEpisodes(for show: Show) -&gt; [Episode] {<br>        XCTFail(&quot;Shouldn&#39;t be called&quot;)<br>        return []<br>    }<br><br>    func markEpisodeAsWatched(episode: Episode) {<br>        XCTFail(&quot;Shouldn&#39;t be called&quot;)<br>    }<br><br>    func markShowAsWatched(_ show: Show, until episode: Episode?) {<br>        XCTFail(&quot;Shouldn&#39;t be called&quot;)<br>    }<br>}</pre><h4>SRP violation</h4><p>When violating ISP, it’s quite common to violate SRP as well. SRP is all about cohesion and having to change for just one reason. As the number of methods grow in our ShowService interface, it’ll be harder and harder to keep that cohesion and SRP.</p><h4>OCP violation</h4><p>Any new feature related to TV Shows will require adding a new method to ShowService, forcing every implementation of the service to accommodate that change and fix the compilation error.</p><h4>Big interfaces tend to be bottlenecks</h4><p>At the end of the day, having big interfaces like this will lead to bottlenecks when adding new behavior related to that domain model. Any new change will lead to compilation errors and recompilations all across our app. One of the mantras of good software design is to depend on modules that are more stable than you (they change less often). In our dependency graph, the leaf modules should be the most stable ones, as they will force recompilation of the whole graph whenever they change. If we take a closer look at ShowService, we are making the whole app depend on a highly unstable module that changes very often. So yes, a recipe for disaster 😅. FWIW, this kind of bottleneck it’s also very common in backend and microservices architecture, and it’s called <a href="https://www.michaelnygard.com/blog/2017/12/the-entity-service-antipattern/">“The Entity Service Antipattern”</a>.</p><h4>First approach: CQRS</h4><p>A first approach would be to separate our big interface into two, distinct interfaces: one for reading, and one for writing. This is called <a href="https://martinfowler.com/bliki/CQRS.html">Command Query Responsibility Segregation</a>.</p><pre>// Reads<br>protocol ShowQueryService {<br>    func allShows() async -&gt; [Show]<br>    func allEpisodes(for show: Show) async -&gt; [Episode]<br>}<br><br>// Writes<br>protocol ShowCommandService {<br>    func markEpisodeAsWatched(episode: Episode) async<br>    func markShowAsWatched(_ show: Show, until episode: Episode?) async<br>}</pre><p>This separation usually makes sense because the needs we are going to have for reading are quite different from the ones we are going to have for writing. For instance, some security policies will only apply when changing state (writing) and not when just reading.</p><p>But as you can imagine, this is not a big improvement. We’ve scoped better the impact of change, but we still have two “big interfaces” that will change often. We can do better.</p><h4>Second approach: small services</h4><p>An obvious approach would be to “go extreme” and have all the different capabilities as different protocols.</p><pre>protocol AllShowsService {<br>    func allShows() async -&gt; [Show]<br>}<br><br>protocol AllEpisodesService {<br>    func allEpisodes(for show: Show) async -&gt; [Episode]<br>}<br><br>protocol MarkEpisodeAsWatchedService {<br>    func markEpisodeAsWatched(episode: Episode) async<br>}<br><br>protocol MarkShowAsWatchedService {<br>    func markShowAsWatched(_ show: Show, until episode: Episode?) async<br>}</pre><p>If you are used to working with clean architecture and use cases, these are exactly that.</p><p>It has some pros and cons. Having a protocol per method could lead to an explosion of interfaces. But it also has the main advantage of complying with some of the SOLID principles we talked about previously. We now comply with ISP (and most likely SRP as well). Also, when adding a new capability, we only have to create a new tuple (interface, implementation), without breaking a lot of the code we have and minimizing the impact of recompilations. So it’s not all bad. Remember, it all depends. Software is all about trade-offs.</p><p>But thinking about “Aspects”, we’ll have to implement one implementation per protocol for each aspect we need. For instance, if we want to implement a “Logging Aspect”, we’ll need a AllShowsLoggingService and another MarkShowAsWatchedLoggingService, etc… So, it doesn’t seem like an approach that scales nicely to implement cross-cutting concerns in our app.</p><p>Let’s go with the third, and final approach.</p><h4>Third approach: a unified Service interface</h4><p>If we want to implement AOP in a scalable manner, we need to have a single interface/seam for all our services that we want to apply “aspects” to.</p><pre>protocol Service&lt;Input, Output&gt; {<br>    associatedtype Input<br>    associatedtype Output<br><br>    func callAsFunction(input: Input) async throws -&gt; Output<br>}</pre><p>Just for ergonomics reasons, let’s also have this.</p><pre>extension Service where Input == Void{<br>    func callAsFunction() async throws -&gt; Output {<br>        try await callAsFunction(input: ())<br>    }<br>}</pre><p>Having a single interface, we can have something like this.</p><pre>class AllShowsService: Service {<br>    func callAsFunction(input: Void) async throws -&gt; [Show] {<br>        // Implementation...<br>    }<br>}<br><br>class MarkShowAsWatchedService: Service {<br>    func callAsFunction(input: (show: Show, episode: Episode?)) async throws {<br>        // Implementation...<br>    }<br>}<br><br>typealias AllShowsServiceType = Service&lt;Void, [Show]&gt;<br>typealias MarkShowAsWatchedServiceType = Service&lt;(show: Show, episode: Episode?), Void&gt;<br><br>class ViewModel&lt;AllShowsService: AllShowsServiceType, MarkShowAsWatchedService: MarkShowAsWatchedServiceType&gt; {<br>    private let allShowsService: AllShowsService<br>    private let markShowAsWatchedService: MarkShowAsWatchedService<br><br>    init(allShowsService: AllShowsService, markShowAsWatchedService: MarkShowAsWatchedService) {<br>        self.allShowsService = allShowsService<br>        self.markShowAsWatchedService = markShowAsWatchedService<br>    }<br><br>    func markAllShowsAsWatchedButtonTapped() async {<br>        do {<br>            let allShows = try await allShowsService()<br>            await withThrowingTaskGroup(of: Void.self) { group in<br>                for show in allShows {<br>                    group.addTask {<br>                        try await self.markShowAsWatchedService(input: (show, nil))<br>                    }<br>                }<br>            }<br>        } catch {<br>            print(&quot;Some error happened&quot;, error)<br>        }<br>    }<br>}</pre><p>Now, our ViewModel can be created as long as we inject two inputs:</p><ul><li>Service&lt;Void, [Show]&gt;</li><li>Service&lt;(show: Show, episode: Episode?), Void&gt;</li></ul><p>As Service is a single interface, we can have just an implementation of that interface per aspect to apply it to any service across our app.</p><p>Let’s implement some aspects then! As you will see, all aspects will be very similar, following the decorator pattern</p><h4>Logging aspect</h4><p>A very normal and useful aspect is simply to add some logging capabilities to our services. It can be implemented as simply as follows:</p><pre>class LoggingService&lt;Decoratee: Service&gt;: Service {<br>    typealias Input = Decoratee.Input<br>    typealias Output = Decoratee.Output<br><br>    let decoratee: Decoratee<br>    <br>    init(decoratee: Decoratee) {<br>        self.decoratee = decoratee<br>    }<br><br>    func callAsFunction(input: Input) async throws -&gt; Output {<br>        let output = try await decoratee(input: input)<br>        dump(output)<br>        return output<br>    }<br>}</pre><h4>Premium user aspect</h4><p>Another, very useful cross-cutting concern in the application could be to only allow to perform some actions in case the user is premium. By injecting a simple () async -&gt; Bool function to abstract the way we check whether the user is premium or not, we can have our PremiumService decorator like this:</p><pre>class PremiumService&lt;Decoratee: Service&gt;: Service {<br>    struct Error: Swift.Error {}<br><br>    typealias Input = Decoratee.Input<br>    typealias Output = Decoratee.Output<br><br>    let decoratee: Decoratee<br>    let isPremiumUser: () async -&gt; Bool<br><br>    init(decoratee: Decoratee, isPremiumUser: @escaping () async -&gt; Bool) {<br>        self.decoratee = decoratee<br>        self.isPremiumUser = isPremiumUser<br>    }<br><br>    func callAsFunction(input: Input) async throws -&gt; Output {<br>        guard await isPremiumUser() else {<br>            throw Error()<br>        }<br>        return try await decoratee(input: input)<br>    }<br>}</pre><h4>Caching aspect</h4><p>Another common aspect is caching or memoization. By simply constraining the Service Input to be Hashable and the Output to be Codable, we can implement it for all our services.</p><pre>private(set) var cache: [AnyHashable: Data] = [:]<br><br>class CachingService&lt;Decoratee: Service&gt;: Service where Decoratee.Output: Codable, Decoratee.Input: Hashable {<br>    typealias Input = Decoratee.Input<br>    typealias Output = Decoratee.Output<br><br>    let decoratee: Decoratee<br><br>    init(decoratee: Decoratee) {<br>        self.decoratee = decoratee<br>    }<br><br>    func callAsFunction(input: Input) async throws -&gt; Output {<br>        if let cachedData = cache[input] {<br>            return try JSONDecoder().decode(Output.self, from: cachedData)<br>        }<br><br>        let output = try await decoratee(input: input)<br>        cache[input] = try JSONEncoder().encode(output)<br>        return output<br>    }<br>}</pre><h4>Delay Aspect</h4><p>Network Link Conditioner is a very useful tool to simulate different types of network conditions and force our internet connection to be very bad. We could leverage aspects to do exactly that.</p><pre>class DelayService&lt;Decoratee: Service&gt;: Service {<br>    typealias Input = Decoratee.Input<br>    typealias Output = Decoratee.Output<br><br>    let decoratee: Decoratee<br>    let duration: UInt64<br><br>    init(decoratee: Decoratee, nanoseconds duration: UInt64) {<br>        self.decoratee = decoratee<br>        self.duration = duration<br>    }<br><br>    func callAsFunction(input: Input) async throws -&gt; Output {<br>        try await Task.sleep(nanoseconds: duration)<br>        return try await decoratee(input: input)<br>    }<br>}</pre><p>Or we could have another aspect to always force an error, for instance.</p><h4>Aspects are composable</h4><p>Once we have all our aspects in place, we can simply modify our view model to inject the type of service we want, decorated by any number of aspects.</p><pre>let viewModel = ViewModel(<br>    allShowsService: DelayService(decoratee: LoggingService(decoratee: AllShowsService()), nanoseconds: 5 * NSEC_PER_SEC),<br>    markShowAsWatchedService: LoggingService(decoratee: MarkShowAsWatchedService())<br>)</pre><p>As you can see, we have chained different aspects together, as they are composable.</p><p>But the important thing is that adding decorators is something that we can do from the main module of the app, where we presumably build the whole object graph, via some sort of composition root pattern. That way, we can change the behavior of our services by adding new cross-cutting concerns without touching and recompiling any of those modules.</p><h4>Ergonomics</h4><p>While chaining decorators can be a little bit cumbersome, we can leverage extensions to improve ergonomics and have a more idiomatic way of composing them.</p><pre>extension Service {<br>    var withLogging: LoggingService&lt;Self&gt; {<br>        LoggingService(decoratee: self)<br>    }<br><br>    func withDelay(nanoseconds duration: UInt64) -&gt; DelayService&lt;Self&gt; {<br>        DelayService(decoratee: self, nanoseconds: duration)<br>    }<br>}<br><br>// Instead of this<br>DelayService(decoratee: LoggingService(decoratee: AllShowsService()), nanoseconds: 5 * NSEC_PER_SEC),<br><br>// We can now do this<br>AllShowsService()<br>    .withLogging<br>    .withDelay(nanoseconds: 5 * NSEC_PER_SEC)</pre><h4>Automating decorators</h4><p><a href="https://github.com/krzysztofzablocki/Sourcery">Sourcery</a> is a great meta-programming tool to automate a lot of boilerplate code. As you can imagine, decorators are a great candidate to apply this tool, so we have some sort of “template” for each aspect and let Sourcery generate the different implementations. This is especially useful when we want to apply the very same aspect to lots of different interfaces. Take a look <a href="https://github.com/krzysztofzablocki/Sourcery/blob/master/guides/Decorator.md">here</a>.</p><h3>Conclusion</h3><p>While it’s true that I miss Objective-C dynamism for these kinds of tasks, I see the lack of runtime support to allow easy AOP in Swift as an opportunity to apply good design principles to unlock it.</p><p>Enabling AOP doesn’t necessarily make your code better, the same way that having testable code doesn’t mean it’s good code. It’s more the other way around. Not being able to test our code is a problem. Being able to test it doesn’t mean anything. We can see AOP in a similar way.</p><p>We did an interesting exercise to unlock AOP capabilities in our code. But let’s not forget the complexity we’ve added. Finding the balance between added complexity and loose coupling to adapt better to the future is one of the most difficult things we have to do as developers. This post is simply a tool. As always, use the right tool for the job.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f2366350c527" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/aspect-oriented-programming-in-swift-f2366350c527">Aspect-Oriented Programming in Swift</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SwiftUI View Models: A Polymorphic Approach]]></title>
            <link>https://medium.com/the-swift-cooperative/swiftui-view-models-a-polymorphic-approach-8911a992892b?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/8911a992892b</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Thu, 18 Jan 2024 16:38:24 GMT</pubDate>
            <atom:updated>2024-01-19T22:56:07.546Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AApJSJU3AlbEsG8o" /><figcaption>Photo by <a href="https://unsplash.com/@possessedphotography?utm_source=medium&amp;utm_medium=referral">Possessed Photography</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p><a href="https://en.wikipedia.org/wiki/Polymorphism_(computer_science)">Polymorphism</a> is one of the most powerful tools we have at our disposal. Coming from Objective-C, Swift exposed us to <strong>parametric polymorphism </strong>via generics, but this article is all about the other type of polymorphism, arguably the most common one: <strong>subtype polymorphism. </strong>Let’s see how it works with view models and more generally, SwiftUI observable objects.</p><h3>iOS 17 and the new Observation framework</h3><p>I wrote about the new Observation framework <a href="https://medium.com/the-swift-cooperative/swiftui-observation-framework-state-containers-56133d8a8751">here</a>. It’s one of the best additions we’ve recently had in SwiftUI. If you are lucky enough to target the latest iOS 17 in your apps, things are quite straightforward.</p><p>First, let’s introduce the example we want to build: <strong>a counter screen that increments or decrements a value differently: synchronously or asynchronously</strong>. First, let’s define the view model abstraction.</p><pre>@MainActor<br>protocol CounterViewModelType: Observable {<br>    var count: Int { get }<br>    var isLoading: Bool { get }<br><br>    func increment()<br>    func decrement()<br>}</pre><p>Simple enough. Just a number that will be shown on screen, a flag indicating if it’s loading the number and two actions to increment and decrement.</p><p>Underneath you can see the app working with the synchronous implementation of the CounterViewModelType.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/295/1*GFpKOdLdKiS3XkN46QPtUg.gif" /></figure><p>And here’s the asynchronous one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/295/1*c0ht17R9qtmN5tlAk6dWxw.gif" /></figure><p>The simplest app ever! But as always, foundational concepts are better explained with simple, naive examples to avoid unnecessary noise around the important concepts.</p><p>Let’s start with the synchronous implementation:</p><pre>@Observable<br>class SyncCounterViewModel: CounterViewModelType {<br>    private(set) var count = 0<br><br>    func increment() {<br>        count += 1<br>    }<br><br>    func decrement() {<br>        count -= 1<br>    }<br><br>    var isLoading: Bool { false }<br>}</pre><p>And now, the asynchronous one:</p><pre>@Observable<br>class AsyncCounterViewModel: CounterViewModelType {<br>    private(set) var count = 0<br>    private(set) var isLoading = false<br><br>    func increment() {<br>        afterOneSecond { [weak self] in self?.count += 1 }<br>    }<br><br>    func decrement() {<br>        afterOneSecond { [weak self] in self?.count -= 1 }<br>    }<br><br>    private func afterOneSecond(_ callback: @escaping () -&gt; Void) {<br>        Task {<br>            isLoading = true<br>            try! await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)<br>            isLoading = false<br>            callback()<br>        }<br>    }<br>}</pre><p>And finally, the view layer.</p><pre>@MainActor<br>struct CounterView: View {<br>    let viewModel: any CounterViewModelType<br><br>    var body: some SwiftUI.View {<br>        VStack {<br>            Button(&quot;Increment&quot;) { viewModel.increment() }<br>                .disabled(viewModel.isLoading)<br><br>            Text(viewModel.isLoading ? &quot;Loading…&quot; : &quot;\(viewModel.count)&quot;)<br><br>            Button(&quot;Decrement&quot;) { viewModel.decrement() }<br>                .disabled(viewModel.isLoading)<br>        }<br>    }<br>}<br><br>CounterView(viewModel: {<br>    if Int.random(in: 0..&lt;100) % 2 == 0 {<br>        SyncCounterViewModel()<br>    } else {<br>        AsyncCounterViewModel()<br>    }<br>}())</pre><p>As you can see, passing a different implementation of the view model at runtime works as expected when working with the new Observation framework. Unfortunately, if you aren’t as lucky as to use iOS 17 already (like most of us I guess), things get much more interesting…</p><h3>Pre-iOS 17 and the old ObservableObject</h3><p>Let’s refactor our previous code to work with ObservableObject. The first step is to modify our view model abstraction to conform to it.</p><pre>@MainActor<br>protocol CounterViewModelType: ObservableObject {<br>    var count: Int { get }<br>    var isLoading: Bool { get }<br><br>    func increment()<br>    func decrement()<br>}</pre><p>We also need to modify both sync and async implementations to publish their properties via @Published.</p><pre>class SyncCounterViewModel: CounterViewModelType {<br>    @Published private(set) var count = 0<br>    …<br>}<br><br>class AsyncCounterViewModel: CounterViewModelType {<br>    @Published private(set) var count = 0<br>    @Published private(set) var isLoading = false<br>    …<br>}</pre><p>And finally, we have to add the @ObservedObject property wrapper on the view layer.</p><pre>struct CounterView: View {<br>    @ObservedObject var viewModel: any CounterViewModelType<br>    …<br>}</pre><p>And… we get our first error.</p><blockquote>Type ‘any CounterViewModelType’ cannot conform to ‘ObservableObject’</blockquote><p>This is interesting. CounterViewModelType is an ObservableObject , so it’d be sensible to assume that any CounterViewModelType would also conform to ObservableObject . Unfortunately, that’s not how Swift works when we use protocols as existential types (unlike using protocols as generic constraints). As meta as this sounds, protocols do not conform to themselves in Swift. Take a look at this.</p><pre>// Using `Codable` as type -&gt; compiler error<br>struct Container: Codable {<br>    let data: Codable<br>}<br><br>// Using `Codable` as generic constraint -&gt; it works<br>struct Container&lt;Data: Codable&gt;: Codable {<br>    let data: Data<br>}</pre><p>So, let’s do that, let’s change our view to use our CounterViewModelType as a generic constraint.</p><pre>@MainActor<br>struct CounterView&lt;ViewModel: CounterViewModelType&gt;: View {<br>    @ObservedObject var viewModel: ViewModel<br>}</pre><p>But as expected, this no longer compiles</p><pre>CounterView(viewModel: {  () -&gt; any CounterViewModelType in<br>    if Int.random(in: 0..&lt;100) % 2 == 0 {<br>        SyncCounterViewModel()<br>    } else {<br>        AsyncCounterViewModel()<br>    }<br>}())</pre><blockquote>Type ‘any CounterViewModelType’ cannot conform to ‘CounterViewModelType’</blockquote><p>This was expected. As CounterView is now generic over a specific CounterViewModelType, the compiler needs to disambiguate the concrete type when creating the instance. The previous code would make ViewModel == any CounterViewModelType , and, if you remember… <em>protocols do not conform to themselves</em>! 😅</p><p>So, we have to change things so the view model is not given to us. Rather, we create the specific view model explicitly, so the compiler has a specific type, conforming to the CounterViewModelType.</p><pre>if Int.random(in: 0..&lt;100) % 2 == 0 {<br>    CounterView(viewModel: SyncCounterViewModel()) // CounterView&lt;SyncCounterViewModel&gt;<br>} else {<br>    CounterView(viewModel: AsyncCounterViewModel()) // CounterView&lt;AsyncCounterViewModel&gt;<br>}</pre><p>This works. But we can’t always afford the privilege of creating the view model ourselves. Sometimes, for whatever reason, the view model is just given to us. We want a more dynamic approach to provide this view model dependency to our view. And in those cases… Well, we need a different approach.</p><h4>Leaving ObservableObject behind</h4><p>It seems that ObservableObject and polymorphism don’t get along very well… Let’s try something different. Let’s forget about ObservableObject and let’s declare our view model like a container that emits states over time.</p><pre>@MainActor<br>protocol ViewModelType&lt;State&gt; {<br>    associatedtype State<br>    <br>    var statePublisher: Published&lt;State&gt;.Publisher { get }<br>}</pre><p>Now, let’s define our new state and protocols for the counter screen:</p><pre>struct CounterState {<br>    var count: Int<br>    var isLoading: Bool<br>}<br><br>@MainActor<br>protocol CounterViewModelType: ViewModelType&lt;CounterState&gt; {<br>    func increment()<br>    func decrement()<br>}</pre><p>Having that in place, our two view model implementations would be:</p><pre>class SyncCounterViewModel: CounterViewModelType {<br>    var statePublisher: Published&lt;CounterState&gt;.Publisher { $state }<br>    @Published private var state = CounterState(count: 0, isLoading: false)<br><br>    func increment() {<br>        state.count += 1<br>    }<br><br>    func decrement() {<br>        state.count -= 1<br>    }<br>}<br><br>class AsyncCounterViewModel: CounterViewModelType {<br>    var statePublisher: Published&lt;CounterState&gt;.Publisher { $state }<br>    @Published private var state = CounterState(count: 0, isLoading: false)<br><br>    func increment() {<br>        afterOneSecond { [weak self] in self?.state.count += 1 }<br>    }<br><br>    func decrement() {<br>        afterOneSecond { [weak self] in self?.state.count -= 1 }<br>    }<br><br>    private func afterOneSecond(_ callback: @escaping () -&gt; Void) {<br>        Task {<br>            state.isLoading = true<br>            try! await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)<br>            state.isLoading = false<br>            callback()<br>        }<br>    }<br>}</pre><p>As you can see, we have a little bit of extra code, by having to define the statePublisher computed property, which forwards the projected value of our @Published property.</p><p>The view layer would look like this:</p><pre>struct CounterView: View {<br>    let viewModel: any CounterViewModelType<br><br>    @State private var state = CounterState(count: 0, isLoading: false)<br><br>    var body: some SwiftUI.View {<br>        VStack {<br>            Button(&quot;Increment&quot;) { viewModel.increment() }<br>                .disabled(state.isLoading)<br><br>            Text(state.isLoading ? &quot;Loading…&quot; : &quot;\(state.count)&quot;)<br><br>            Button(&quot;Decrement&quot;) { viewModel.decrement() }<br>                .disabled(state.isLoading)<br>        }<br>        .onReceive(viewModel.statePublisher) {<br>            state = $0<br>        }<br>    }<br>}</pre><p>We need two things:</p><ul><li>Declare a new @State variable with the state that will render the view.</li><li>Subscribe to the underlying view model publisher, setting the new state.</li></ul><p>That gets the job done without a lot of boilerplate. But as always, things can be improved.</p><h4>The final solution</h4><p>I always like to think from an API point of view. That is, which is the API I would like to have and work backwards from there. I’d like to:</p><ol><li>Avoid having to subscribe each time to the view model publisher and reset the state.</li><li>I’d like to avoid setting an initial, arbitrary, @State variable on the view. The view model should be in charge of providing the initial state, not the view itself.</li></ol><p>With those two preconditions, our ideal solution would look like this:</p><pre>struct CounterView: View {<br>    let viewModel: any CounterViewModelType<br><br>    var body: some SwiftUI.View {<br>        WithViewModel(viewModel: viewModel) { state in<br>            VStack {<br>                Button(&quot;Increment&quot;) { viewModel.increment() }<br>                    .disabled(state.isLoading)<br><br>                Text(state.isLoading ? &quot;Loading…&quot; : &quot;\(state.count)&quot;)<br><br>                Button(&quot;Decrement&quot;) { viewModel.decrement() }<br>                    .disabled(state.isLoading)<br>            }<br>        }<br>    }<br>}</pre><p>Let’s then create that WithViewModel wrapper.</p><pre>@MainActor<br>protocol ViewModelType&lt;State&gt; {<br>    associatedtype State<br>    <br>    var statePublisher: Published&lt;State&gt;.Publisher { get }<br>}<br><br>@MainActor<br>struct WithViewModel&lt;WrappedView, State&gt;: View where WrappedView: View {<br>    @SwiftUI.State private var state: State?<br>    private let publisher: Published&lt;State&gt;.Publisher<br>    private let wrappedView: (State) -&gt; WrappedView<br><br>    init&lt;ViewModel&gt;(<br>        viewModel: ViewModel,<br>        @ViewBuilder wrappedView: @escaping (State) -&gt; WrappedView<br>    ) where ViewModel: ViewModelType&lt;State&gt; {<br>        self.wrappedView = wrappedView<br>        publisher = viewModel.statePublisher<br>    }<br><br>    var body: some View {<br>        root.onReceive(publisher) {<br>            state = $0<br>        }<br>    }<br><br>    @ViewBuilder<br>    private var root: some View {<br>        if let state {<br>            wrappedView(state)<br>        }<br>    }<br>}</pre><h3>Conclusion</h3><p>Thanks for reaching the end of the article. It was hopefully an interesting exercise to make polymorphic observable objects work flawlessly with SwiftUI.</p><p>I think the final solution gets the job done with a fairly clean API, without fighting the SwiftUI framework too much to make it work.</p><p>But as I previously said, if you are lucky enough to target iOS 17, the new Observation framework solves all your problems. Well, <a href="https://medium.com/the-swift-cooperative/swiftui-observation-framework-state-containers-56133d8a8751">almost</a> all of them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8911a992892b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/swiftui-view-models-a-polymorphic-approach-8911a992892b">SwiftUI View Models: A Polymorphic Approach</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SwiftUI Observation Framework: State Containers]]></title>
            <link>https://medium.com/the-swift-cooperative/swiftui-observation-framework-state-containers-56133d8a8751?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/56133d8a8751</guid>
            <category><![CDATA[development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Fri, 05 Jan 2024 13:08:17 GMT</pubDate>
            <atom:updated>2024-01-06T01:15:05.663Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rpEaq1lowU0ZMgow" /><figcaption>Photo by <a href="https://unsplash.com/@guibolduc?utm_source=medium&amp;utm_medium=referral">Guillaume Bolduc</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>I <a href="https://jobandtalent.engineering/ios-architecture-an-state-container-based-approach-4f1a9b00b82e">first wrote about state containers back in 2017</a>. In its simplest form, a state container is just a wrapper type over some State type.</p><pre>class StateContainer&lt;State&gt;: ObservableObject {<br>    @Published var state: State<br>}</pre><p>Most unidirectional architecture frameworks have a similar base class, so we model all our feature state inside that State type, whether that’s data that should trigger view renders or not.</p><p>And that’s one of the main bottlenecks of some Redux-ish architectures that tend to model all the app state in a single place: view bodies recompute even if the state change was unrelated to that view. There are certainly ways to fix that (like <a href="https://github.com/pointfreeco/swift-composable-architecture/blob/main/Sources/ComposableArchitecture/ViewStore.swift">TCA’s ViewStore</a>), but as always, that comes with complexity and also with a feeling that we are kind of fighting the framework.</p><p>Fortunately, the new Observation framework is here to fix this. Or not… Let’s see.</p><h3>Unnecessary re-rendering</h3><p>Let’s have some foundation in place first via the simplest example that I could come up with. An app that generates a random number and shows it on screen. It also has a “noop” button, which does nothing (it just sets a piece of private state).</p><pre>// CounterView.swift<br>struct CounterView: View {<br>    @StateObject private var viewMoldel = CounterViewModel()<br><br>    var body: some View {<br>        VStack {<br>            Text(&quot;\(viewMoldel.state.count)&quot;)<br>            Button(&quot;Random number&quot;) { viewMoldel.generateRandomNumber() }<br>            Button(&quot;Noop&quot;) { viewMoldel.noop() }<br>        }<br>    }<br>}<br><br>// CounterViewModel.swift<br>struct CounterViewState {<br>    fileprivate(set) var count = 0<br>    fileprivate var privateCount = 0<br>}<br><br>class CounterViewModel: StateContainer&lt;CounterViewState&gt; {<br>    func generateRandomNumber() {<br>        state.count = Int.random(in: 0 ..&lt; 100)<br>    }<br><br>    func noop() {<br>        state.privateCount = Int.random(in: 0 ..&lt; 100)<br>    }<br>}</pre><p>As you can imagine, setting both the count (which is the state read in the view body) or the privateCount (which is not read by the view), will trigger the view body to recompute.</p><p>A possible option to fix this issue is to extend the State type so we have a little more control over when the change should trigger a render pass. Something like this:</p><pre>protocol StateType {<br>    static func shouldViewUpdate(lhs: Self, rhs: Self) -&gt; Bool<br>}<br><br>extension StateType {<br>    static func shouldViewUpdate(lhs: Self, rhs: Self) -&gt; Bool {<br>        true<br>    }<br>}<br><br>class StateContainer&lt;State: StateType&gt;: ObservableObject {<br>    var state: State { // Ideally protected<br>        willSet {<br>            guard State.shouldViewUpdate(lhs: newValue, rhs: state) else {<br>                return<br>            }<br>            objectWillChange.send()<br>        }<br>    }<br>}</pre><p>And now, each State type can tell when a change should trigger the view render:</p><pre>struct CounterViewState: StateType {<br>    fileprivate(set) var count = 0<br>    fileprivate var privateCount = 0<br><br>    static func shouldViewUpdate(lhs: Self, rhs: Self) -&gt; Bool {<br>        lhs.count != rhs.count<br>    }<br>}</pre><p>If you are familiar with React, this is similar to <a href="https://react.dev/reference/react/Component#shouldcomponentupdate">shouldComponentUpdate</a>.</p><p>Of course, this is not ideal and it’s quite error-prone. Having to update shouldViewUpdate whenever we add properties read by the view will likely end up getting out-of-sync. There are other better ways to do this, like having an explicit Rendered type.</p><pre>protocol StateType {<br>    associatedType Rendered: Equatable<br>    var rendered: Rendered { get }<br>}<br><br>extension StateType {<br>    static func shouldViewUpdate(lhs: Self, rhs: Self) -&gt; Bool {<br>        lhs.rendered != rhs.rendered<br>    }<br>}</pre><p>In this case, the view will always read our rendered type and will only render as long as the new rendered is different from the previous one. But as always, this imposes constraints on our StateType, increasing the overall complexity and removing some freedom about how to model our state.</p><p>Fortunately, the new Observation framework is here to help.</p><h3>Enter the @Observable macro</h3><p>iOS 17 changed completely how we observe changes from SwiftUI views.</p><ul><li>SwiftUI is no longer coupled with the Combine framework when it comes to observing external state updates (we don’t need @Published properties any longer).</li><li>Performance is improved as the view will only update as long as the related state changes (the one read inside the view body).</li></ul><p>That premise is really nice. So let’s refactor our previous code and see what happens:</p><pre>@Observable<br>class StateContainer&lt;State&gt; {<br>    var state: State<br><br>    init(initialState: State) {<br>        self.state = initialState<br>    }<br>}<br><br>// Inside our view...<br>@State private var viewMoldel = CounterViewModel()</pre><p>Interestingly, the view body is still recomputed when tapping on the “noop” button 🤔.</p><p>If we think about it, “it makes sense”. We are marking the StateContainer as @Observable, which observes all the underlying properties inside it (in this case, only the state). Being state a value type, changing a piece of private state inside it mutates the whole value type, which will trigger the view body recomputation, regardless of the actual mutation and if that affects the view or not.</p><p>While this “makes sense”, it is also confusing and it can greatly affect performance and unnecessary re-renders when modeling our state using value types.</p><p>The solution would be to mark the underlying State as @Observable. Unfortunately, this cannot be done with value types. <a href="https://forums.swift.org/t/second-review-se-0395-observability/65261/119">At least for now</a>…</p><h3>Leaving value types behind</h3><p>As long as I like to model my state with value types, both the Observable framework and SwiftData (with its @Model macro) are forcing a lot of our types to be reference types, whether we like that or not…</p><p>The solution to our problem is just to change our State type to be a class.</p><pre>class StateContainer&lt;State&gt; { } // This no longer needs to be Observable<br><br>@Observable<br>final class CounterViewState {<br>    fileprivate(set) var count = 0<br>    fileprivate var privateCount = 0<br>}</pre><p>But if we really want to use value types, we have other options…</p><h3>ObservableState</h3><p>The Pointfree guys have come up with a clever way to bypass this limitation for value types, by creating their own macro <a href="https://github.com/pointfreeco/swift-composable-architecture/blob/observation-beta/Sources/ComposableArchitectureMacros/ObservableStateMacro.swift">@ObservableState</a>. Hopefully, that macro will get its own library one day so we can use it without the whole TCA library. The change is just as simple as marking our value type with the @ObservableState macro.</p><pre>@ObservableState<br>struct State {<br>    var count = 0<br>    fileprivate var privateCount = 0<br>}</pre><p>Only you can decide if adding that macro is worth it for the value semantics benefits… 🤷‍♂.</p><h3>Conclusion</h3><p>Fortunately, all this won’t be an issue as long as we don’t use these kinds of generic state containers with that single State type. If we just happen to use a simple view model, in most cases, we can just mark our view model as @Observable and be sure that mutating a private property won’t trigger view re-renders.</p><p>But the truth is that this problem affects a lot of unidirectional libraries that are migrating to the Observable framework. Of course, there’s a simple “solution”, by forcing the State type to be an Observable reference type.</p><pre>protocol StateContainer {<br>    associatedtype State: Observable<br>    var state: State { get }<br>}</pre><p>Depending on the app, that might be a big constraint (State can’t be an enum for instance…) for negligible performance benefits. As always, it depends.</p><p>In any case, hopefully, Swift will support Observable value types in the future, making all this article obsolete.</p><p>Looking forward to that.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56133d8a8751" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/swiftui-observation-framework-state-containers-56133d8a8751">SwiftUI Observation Framework: State Containers</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SwiftUI View Models: Lifecycle Quirks]]></title>
            <link>https://medium.com/the-swift-cooperative/swiftui-view-models-lifecycle-quirks-8dd967e84e31?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/8dd967e84e31</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Tue, 19 Dec 2023 07:30:41 GMT</pubDate>
            <atom:updated>2023-12-20T16:51:11.203Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3FuzsxKAAoXpDEMT5ByKPA@2x.jpeg" /><figcaption>Photo by Nicola Ricca on Unsplash</figcaption></figure><h3>Introduction</h3><p>Coming from UIKit and imperative UI frameworks, SwiftUI is a paradigm shift. It tries to avoid sync issues between our view and our state (which presumably are one of the main sources of bugs in UI development), in a way where we simply tell SwiftUI the immutable description of how we’d like our view to look and the framework handles the rest. Views are usually subscribed to a source of truth so, when it changes, the “pure” function (State) -&gt; View Description re-executes so that SwiftUI interprets that view description into a real view. We could simplify things and say that SwiftUI is all about two functions.</p><ul><li>(State) -&gt; View Description (Us)</li><li>(ViewDescription) -&gt; Real View (SwiftUI)</li></ul><p>This is all really nice. But there’s a subtle detail under all this: <strong>we no longer control the view lifecycle</strong>.</p><p>With UIKit, we did control when our views were created and destroyed. We had <em>the instance</em>, and we could do whatever we wanted with it. Now, the framework is in charge of those instances and real views are just implementation details. Not only that, we don’t control the lifecycle of the view descriptions either. Those <em>view values</em> can be called at any time, multiple times, and it’s also up to the framework to do that. We shouldn’t make any assumptions based on how things work today, because SwiftUI will likely change how our view values are created in the future.</p><p>Fair enough, we don’t control our views… But can we still have control over the underlying state of those views? That is, can we still control when our view models are created and destroyed? It’s still very common to hook onto some lifecycle methods like init and deinit inside our view models to subscribe and unsubscribe to different data sources, implement <a href="https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization">RAII patterns</a> (like Combine’s <a href="https://developer.apple.com/documentation/combine/cancellable">Cancellable</a> protocol, whose subscriptions are automatically canceled when destroyed), etc… Is that still something we can do? Should we instead rely on different lifecycle methods like onAppear and onDisappear? The truth is that, depending on the SwiftUI version, my opinion has changed. Let me rephrase that. Depending on the SwiftUI version, Apple has confused me even more.</p><p>Let’s start from the beginning.</p><h3>It all started with @ObservedObject</h3><p>Let’s have a simple example first. Let’s suppose we have two screens:</p><ul><li>The first one computes a random number every two seconds and uses that number as the initial value to create a second screen when a button is tapped.</li><li>The second one uses that previous initial number and allows the user to increment or decrement that value.</li></ul><pre>struct ContentView: View {<br>    @State private var showingSheet = false<br>    @State private var number = 0<br><br>    var body: some View {<br>        Button(&quot;Show counter with value: \(number)&quot;) {<br>            showingSheet = true<br>        }<br>        .sheet(isPresented: $showingSheet) {<br>            CounterView(viewModel: CounterViewModel(initialValue: number))<br>        }<br>        .task {<br>            while true {<br>                try! await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)<br>                number = Int.random(in: 0..&lt;100)<br>            }<br>        }<br>    }<br>}<br><br>struct CounterView: View {<br>    @ObservedObject private var viewModel: CounterViewModel<br><br>    init(viewModel: CounterViewModel) {<br>        self.viewModel = viewModel<br>    }<br><br>    var body: some View {<br>        VStack {<br>            Button(&quot;Increment&quot;, action: viewModel.increment)<br>            Text(&quot;Number is \(viewModel.count)&quot;)<br>            Button(&quot;Decrement&quot;, action: viewModel.decrement)<br>        }<br>    }<br>}<br><br>class CounterViewModel: ObservableObject {<br>    @Published private(set) var count = 0<br><br>    init(initialValue: Int) {<br>        count = initialValue<br>    }<br><br>    func increment() {<br>        count += 1<br>    }<br><br>    func decrement() {<br>        count -= 1<br>    }<br>}</pre><p>Of course, that code results in the wrong behavior as we can see in the video.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/296/1*pd6ZFn_DZmpI9Av_lnPSRg.gif" /></figure><p>As you might have guessed, the underlying issue is that the CounterView, together with its CounterViewModel, are being recreated…</p><p>The first version of SwiftUI came with a simple way of observing changes to an external piece of state. @ObservedObject was not opinionated about how to handle the underlying lifecycle of the observed object, it was up to us.</p><p>In simple scenarios, we could have a single ObservableObject, inject it as an environment object at the root node of your application, and access it in any children node. It was simple, but depending on the scale and the complexity of the app, it might not be the best solution.</p><p>If SwiftUI doesn’t handle the lifecycle of our view models, we should come up with a way to do so. A common solution would be to delegate that <em>object lifecycle</em> responsibility to some sort of dependency container (a favorite of mine is <a href="https://github.com/hmlongco/Factory">Factory</a>) and leverage scopes to fix our issue.</p><pre>import Factory<br><br>extension Container {<br>    func counterViewModel(initialValue: Int) -&gt; Factory&lt;CounterViewModel&gt; {<br>        self { CounterViewModel(initialValue: initialValue) }.shared<br>    }<br>}<br><br>// In ContentView...<br>CounterView(viewModel: Container.shared.counterViewModel(initialValue: number)())</pre><p>In this case, by using the shared scope, the same view model is reused when the view tries to create a new one. Once the view is dismissed, the underlying view model is destroyed as well, creating a new one the next time we present the screen.</p><p>Even if we have managed to reuse the very same view model, view models are still eager. Even if SwiftUI hasn’t created/shown the real view yet, the fact that the view description has been created forces our view model to be created… (NavigationLink is well known for its eager behavior). This means that patterns like RAII or attaching subscriptions to view model initialization could not be the best idea in SwiftUI, and maybe, only maybe, onAppear and similar methods are a better approach… Well, iOS 14 and @StateObject changed that.</p><h3>@StateObject and iOS 14 to the rescue</h3><p>Fortunately, iOS 14 and @StateObject allowed to <strong>sync the lifecycle of our view models to the lifecycle of our real views</strong>.</p><ul><li>Real views are reusing the correct view model, no matter how many times we create new view descriptions and instantiate new view models.</li><li>View models are created lazily when the real view is created, not when the view description is created.</li><li>View models are destroyed when the real view is destroyed.</li></ul><p>Replacing our previous @ObservedObject with @StateObject is simple.</p><pre>struct CounterView: View {<br>    @StateObject private var viewModel: CounterViewModel<br><br>    init(viewModel: CounterViewModel) {<br>        _viewModel = .init(wrappedValue: viewModel)<br>    }<br>   <br>    …<br>}</pre><p>That change makes the app <em>behave correctly</em>. Even if the CounterView is re-created each time (alongside its view model), the new view model instance will only be used as the <em>initial state</em>, in case SwiftUI decides to remove the counter view completely from the hierarchy and show it again.</p><p>But isn’t it strange that we have to recreate the view model each time? As we previously mentioned, we could have code on init and deinit that could potentially be problematic if we run it multiple times… Yes, it’s generally a good idea to do few things on initializers, but sometimes it’s simply convenient to clean things up when objects are destroyed. The problem in this case is the place where we decided to create the view model. A subtle change fixes the issue.</p><pre>struct CounterView: View {<br>    @StateObject private var viewModel: CounterViewModel<br><br>    init(initialValue: Int) {<br>        _viewModel = .init(wrappedValue: CounterViewModel(initialValue: initialValue))<br>    }<br><br>    …<br>}</pre><p>Instead of injecting the view model into the CounterView, we simply pass the initial value and let the view create the view model. The <em>trick</em> is the wrappedValue parameter, <a href="https://developer.apple.com/documentation/swiftui/stateobject/init(wrappedvalue:)">which is a function, disguised by an </a><a href="https://developer.apple.com/documentation/swiftui/stateobject/init(wrappedvalue:)">@autoclosure.</a></p><pre>init(wrappedValue thunk: @autoclosure @escaping () -&gt; ObjectType)</pre><p>That way, SwiftUI manages to only call that function to create the view model the first time the real view is created, and never the following times where the view description is re-created.</p><p>Having that function also gives us that lazy behavior for view models we mentioned previously. Let’s make a tiny change in our view layer to show the counter view navigated.</p><pre>NavigationLink(&quot;Show counter with value: \(number)&quot;) {<br>    CounterView(initialValue: number)<br>}</pre><p>NavigationLink creates the underlying CounterView eagerly. That means that any view model we need to create CounterView would also be created eagerly. Fortunately, @StateObject works perfectly for this case, creating the view model lazily (via the @autoclosure function we mentioned earlier) at the exact moment when the real view is presented on the screen.</p><p>@StateObject is quite easy to misuse though. Unless we use the correct initializer and have the view model instantiation inside the @autoclosure, we’ll lose all those nice benefits of not creating the view models multiple times and have them created lazily. But if done correctly, it provides a perfect way so views can manage the underlying lifecycle of their view models.</p><p>At this point. I was fully convinced. Relying on initialization and deinitialization for subscriptions and unsubscriptions and implementing the RAII pattern this way was still feasible in SwiftUI. Not only feasible. It was a good idea!</p><p>Well, I’m no longer sure.</p><h3>iOS 17 and the @Observable macro</h3><p>iOS 17 changed everything with the new @Observable macro. It not only decoupled SwiftUI from Combine, but it also made SwiftUI far more efficient about when to update views and when not, depending on the underlying state that is read in the body property.</p><p>Let’s blindly follow <a href="https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro">Apple’s guide</a> to migrate from the old Combine world to the new shiny @Observable world.</p><pre>@Observable class CounterViewModel {<br>    private(set) var count = 0<br>    …<br>}<br><br>struct CounterView: View {<br>    @State private var viewModel: CounterViewModel<br><br>    init(initialValue: Int) {<br>        viewModel = CounterViewModel(initialValue: initialValue)<br>    }<br>    …<br>}</pre><p>Changes are minimal. CounterViewModel no longer needs to be an ObservableObject and can simply adopt the new @Observable macro syntax. Then, we no longer need to use @StateObject and we can use @State, which is freeing to use regardless of the nature of the object type (whether we use values or reference types). @State still makes sure that the proper view model is always used by the view.</p><p>It seems like a naive change. Out of the blue though, we’ve lost two important things we had with @StateObject.</p><ol><li>The view model is created multiple times now, each time the ContentView view is recomputed.</li><li>The view model is no longer created lazily.</li></ol><p>In fact, we’ve lost another thing… The fact that the view model’s deinit is never called. An important thing indeed! At the moment I’m writing this article, with Xcode 15.0, all @State properties are leaked. Fortunately, <a href="https://developer.apple.com/forums/thread/736110">this issue has been fixed in Xcode 15.2 beta</a>.</p><p>As we did with @ObservedObject, let’s finally use the dependency container approach to fix both issues, the fact that we are creating multiple view models, eagerly.</p><p>To avoid creating multiple view models, we’ll have the very same code we previously had, by leveraging the shared scope to reuse the previously in-memory view model. As for the lazy part, we’ll simply have a function as a way to obtain our view model:</p><pre>import Factory<br><br>extension Container {<br>    func counterViewModel(initialValue: Int) -&gt; Factory&lt;CounterViewModel&gt; {<br>        self { CounterViewModel(initialValue: initialValue) }.shared<br>    }<br>}<br><br>struct CounterView: View {<br>    private let viewModel: () -&gt; CounterViewModel<br><br>    init(initialValue: Int) {<br>        viewModel = { Container.shared.counterViewModel(initialValue: initialValue)() }<br>    }<br><br>    …<br>}</pre><h3>Conclusion</h3><p>I guess the conclusion to all this might be that <strong>we shouldn’t rely on controlling the view model’s lifecycle<em>.</em></strong></p><p>While @StateObject seemed like the perfect way to rely back on proper init and deinit behavior (at least the <em>proper</em> way we were used to with UIKit), that behavior has been broken with the new @Observable macro approach. Yes, we can use external dependency containers to fix all these issues, but those aren’t free and can be hard to maintain and error-prone (not to mention that they <em>feel</em> like workarounds to me).</p><p>Relying on the implicit call of init and deinit might be a bad practice we should avoid with SwiftUI. Instead, I think a safer approach is to explicitly rely on lifecycle methods that SwiftUI views give us (whether those are onAppear, onDisappear, or different ones).</p><p>But that’s only my opinion, at least for now. In any case, I’m still confused. Who knows, maybe iOS 18 will change my mind, again.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8dd967e84e31" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-swift-cooperative/swiftui-view-models-lifecycle-quirks-8dd967e84e31">SwiftUI View Models: Lifecycle Quirks</a> was originally published in <a href="https://medium.com/the-swift-cooperative">The Swift Cooperative</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SwiftUI Testing: a Pragmatic Approach]]></title>
            <link>https://medium.com/better-programming/swiftui-testing-a-pragmatic-approach-aeb832107fe7?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/aeb832107fe7</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Thu, 01 Jun 2023 00:49:43 GMT</pubDate>
            <atom:updated>2024-01-19T09:28:15.351Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FqW625Fq04RioJGDAYdYmw@2x.jpeg" /><figcaption>Photo by Joshua Lawrence on Unsplash</figcaption></figure><p>During the last few months, I’ve been reading “<a href="https://www.goodreads.com/en/book/show/48927138">Unit Testing Principles, Practices, and Patterns</a>” by <a href="https://twitter.com/vkhorikov">Vladimir Khorikov</a>. It’s definitely one of the best books I’ve read about testing. One of the things I’ve liked the most is that the author offers a “framework of reference” to analyze how good a test is based on the following traits:</p><ul><li>Protection against regressions.</li><li>Resistance to refactoring.</li><li>Fast feedback.</li><li>Maintainability.</li></ul><p>Those traits are what the author calls “the four pillars of a good unit test”. And the interesting part is that we cannot maximize all of them. Some types of tests provide the best protection against regression and resistance to refactoring but with very slow feedback (like UI testing). At the end of the day, we have to think and decide the best trade-offs for our specific application and use cases.</p><p>I’ve been thinking a lot about this and how to apply that framework to improve my testing, especially applied to the view layer, where things are especially tricky. And more specifically to SwiftUI, where we are still rethinking good practices and patterns to better fit this new exciting platform.</p><p>This article will not tell you “<em>the</em> <em>best way to test SwiftUI code</em>”. Rather, I’ll walk you through a simple example and the different ways we have at our disposal on iOS to test it, its trade-offs, and my subjective thoughts and observations.</p><p>Let’s go!</p><h3>Initial code</h3><p>The example application is simple but complex enough to showcase interesting testing techniques. It loads a bunch of todos from an API, shows them on the screen, and saves them to disk.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/342/1*BapKN1FzStH5zmOrPZBbfw.gif" /></figure><p>The code looks like this.</p><pre>import SwiftUI<br><br>struct TodoListView: View {<br>    @State private var state: ListViewState = .idle<br>    private let databaseManager: DatabaseManager = .shared<br><br>    var body: some View {<br>        Group {<br>            switch state {<br>            case .idle:<br>                Button(&quot;Start&quot;) {<br>                    Task {<br>                        await refreshTodos()<br>                    }<br>                }<br><br>            case .loading:<br>                Text(&quot;Loading…&quot;)<br><br>            case .error:<br>                VStack {<br>                    Text(&quot;Oops&quot;)<br>                    Button(&quot;Try again&quot;) {<br>                        Task {<br>                            await refreshTodos()<br>                        }<br>                    }<br>                }<br><br>            case .loaded(let todos):<br>                VStack {<br>                    List(todos) {<br>                        Text(&quot;\($0.title)&quot;)<br>                    }<br>                }<br>            }<br>        }.onChange(of: state) {<br>            guard case .loaded(let todos) = $0 else {<br>                return<br>            }<br><br>            databaseManager.save(data: todos)<br>        }<br>    }<br><br>    private func refreshTodos() async {<br>        state = .loading<br>        do {<br>            let todos = try await loadTodos().sorted { $0.title &lt; $1.title }<br>            state = .loaded(todos)<br>        } catch {<br>            state = .error<br>        }<br>    }<br><br>    private func loadTodos() async throws -&gt; [Todo] {<br>        let url = URL(string: &quot;https://jsonplaceholder.typicode.com/todos/&quot;)!<br>        let (data, _) = try await URLSession.shared.data(from: url)<br>        let todos = try JSONDecoder().decode([Todo].self, from: data)<br>        return todos<br>    }<br>}<br><br>enum ListViewState: Equatable {<br>    case idle<br>    case loading<br>    case loaded([Todo])<br>    case error<br>}<br><br>struct Todo: Codable, Identifiable, Equatable {<br>    var userId: Int<br>    var id: Int<br>    var title: String<br>    var completed: Bool<br>}<br><br>final class DatabaseManager {<br>    static let shared: DatabaseManager = .init()<br>    private init() {}<br><br>    func save&lt;T&gt;(data: T) where T: Encodable {<br>        // TBD<br>    }<br>}</pre><p>That code is far from ideal, but it gets the job done without overcomplicating things for now.</p><p>We want to test that:</p><ul><li>The initial screen layout is correct.</li><li>The screen shows the correct feedback to the user while the information is being loaded.</li><li>The screen shows feedback to the user in case an error arises.</li><li>The screen shows the sorted list of todos to the user after downloading them from the network.</li><li>The todos are correctly saved to disk.</li></ul><p>We’ll test all those use cases with different tools, starting with the simpler one: using XCUITest.</p><h3>UI testing via XCUITest</h3><p>XCUITest is Apple’s first-party framework for creating UI tests. Using UI tests as our first approach in legacy code bases without tests is usually a smart choice. It doesn’t require any changes in the application, and it provides a safety net that allows further refactoring afterward, something that will be needed to have better unit tests.</p><p>The code looks like this.</p><pre>import SnapshotTesting<br>import XCTest<br><br>final class TodoListViewUITests: XCTestCase {<br>    func testIdle() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br>    }<br><br>    func testLoading() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        // When<br>        app.buttons.element.tap()<br><br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br>    }<br><br>    func testLoaded() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        // When<br>        app.buttons.element.tap()<br><br>        // Then<br>        guard app.collectionViews.element.waitForExistence(timeout: 5) else {<br>            XCTFail()<br>            return<br>        }<br>            <br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br>    }<br>}<br><br>private extension XCUIScreenshot {<br>    // Let&#39;s get rid of both the status bar and home indicator to have deterministic results.<br>    var withoutStatusBarAndHomeIndicator: UIImage {<br>        let statusBarOffset = 40.0<br>        let homeIndicatorOffset = 20.0<br>        let image = image<br>        return .init(<br>            cgImage: image.cgImage!.cropping(<br>                to: .init(<br>                    x: 0,<br>                    y: Int(statusBarOffset * image.scale),<br>                    width: image.cgImage!.width,<br>                    height: image.cgImage!.height - Int((statusBarOffset + homeIndicatorOffset) * image.scale)<br>                )<br>            )!,<br>            scale: image.scale,<br>            orientation: image.imageOrientation<br>        )<br>    }<br>}</pre><p>Some interesting remarks:</p><ul><li>Combining UI tests with screenshot testing is really powerful, as it simplifies quite a lot the view assertion part. Screenshot testing has quite a few downsides that are out of the scope of this article, but they are extremely convenient.</li><li>To make them work deterministically, we must remove both the status bar and the home indicator (yes, the home indicator sometimes changes between test runs ¯\_(ツ)_/¯).</li><li>We cannot test the error case as we don’t have an easy way to control the network.</li><li>Not controlling the network means that tests are slow, relying on timeouts. If we run the tests without having an internet connection, they will fail, etc.</li><li>The loading test is not deterministic. Depending on how fast the network and API response is, the snapshot will be done in the “Loading…” screen or in the loaded screen.</li><li>The loaded test might as well not be deterministic, depending on the network result (loadTodos endpoint). At the moment, it returns the very same data every time, but that might change.</li><li>We cannot test that the data was stored in the database. The UI tests run in a different process than the test runner, so accessing the underlying FileManager‘s data is impossible.</li><li>It’s really hard and cumbersome to prepare tests to go to the specific screen we want to test. The app opens from scratch, and we need to navigate to the <em>screen under test</em> first before performing assertions.</li></ul><p>As you can see, we have many important problems with using XCUITest. It doesn’t seem like the best approach. And, of course, it shouldn’t. UI tests should cover just a small part of critical business paths, as they are the tests that more reliably mimic user interaction.</p><p>But the most important problem is the time it takes to run the test suite, 15 seconds for just four tests. And that’s with a very good internet connection.</p><p>Yes, there are ways to use launchArguments and ProcessInfo to know that we are running UI tests and performing different tasks, like controlling the network and providing a less flaky experience. But that not only couples the main code with testing code, but it also worsens one of the other pillars of good unit tests, maintainability.</p><p>In summary, while UI tests are extremely good at catching regressions and resisting code changes and refactors, the slow feedback and bad maintainability make them only a good choice for a small number of tests, ideally, those covering really important, high-level use cases.</p><h3>UI testing via EarlGrey 2.0</h3><p>In the past, we had <a href="https://github.com/kif-framework/KIF">KIF</a>, a good white-box UI testing alternative to XCUITest. While KIF still works, it has some limitations. The main one is that we cannot interact with alerts and other windows different from the main one. AFAIK this was the main reason why Google abandoned their first version of EarlGrey, which used a similar white-boxed approach to UI testing, in favor of leveraging the existing XCUITest platform, and adding some <a href="https://github.com/google/eDistantObject">“magic”</a> on top. In 2023, I think <a href="https://github.com/google/EarlGrey/tree/earlgrey2">EarlGrey 2.0</a> is the only good alternative to XCUITest worth considering, IMO.</p><p>With EarlGrey 2.0, we can create a specific, properly configured view and set it as the window’s root view controller. To control the network and database, we need to do a small refactor of TodoListView.</p><ul><li>We’ll inject an async function to control the API call.</li><li>We’ll inject an abstraction of the database manager, allowing us to inject the proper mock later.</li></ul><pre>import SwiftUI<br><br>struct TodoListView: View {<br>    @State private var state: ListViewState = .idle<br><br>    private let databaseManager: DatabaseManagerProtocol<br>    private let loadTodos: () async throws -&gt; [Todo]<br><br>    init(<br>        databaseManager: DatabaseManagerProtocol,<br>        loadTodos: @escaping () async throws -&gt; [Todo] = loadTodos<br>    ) {<br>        self.databaseManager = databaseManager<br>        self.loadTodos = loadTodos<br>    }<br><br>    var body: some View { … }<br><br>    private static func loadTodos() async throws -&gt; [Todo] { … }<br>}<br><br>protocol DatabaseManagerProtocol {<br>    func save&lt;T&gt;(data: T) where T: Encodable<br>}<br><br>final class DatabaseManager: DatabaseManagerProtocol {<br>    static let shared: DatabaseManager = .init()<br>    private init() {}<br><br>    func save&lt;T&gt;(data: T) where T: Encodable {<br>        // TBD<br>    }<br>}</pre><p>With that in place, the tests look similar to the previous ones. The given methods will configure the TodoListView with the correct todos and a database spy that we can use to double-check that they have been saved correctly.</p><pre>import XCTest<br>import SnapshotTesting<br><br>final class TodoListViewUITests: XCTestCase {<br>    func testIdle() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br>    }<br><br>    func testLoading() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        let todosSaved = todoListViewHost().givenLoadingTodoListView()<br><br>        // When<br>        app.buttons.element.tap()<br><br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br><br>        XCTAssertFalse(todosSaved())<br>    }<br><br>    func testLoaded() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br><br>        let todosSaved = todoListViewHost().givenLoadedTodoListView()<br><br>        // When<br>        app.buttons.element.tap()<br><br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br><br>        XCTAssertTrue(todosSaved())<br>    }<br><br>    func testError() {<br>        // Given<br>        let app = XCUIApplication()<br>        app.launch()<br>        <br>        let todosSaved = todoListViewHost().givenErrorTodoListView()<br>        <br>        // When<br>        app.buttons.element.tap()<br>        <br>        // Then<br>        assertSnapshot(<br>            matching: app.screenshot().withoutStatusBarAndHomeIndicator,<br>            as: .image<br>        )<br>        <br>        XCTAssertFalse(todosSaved())<br>    }<br>}</pre><p>EarlGrey requires the notion of a “host”, which is a kind of proxy that lets us move between the two worlds: the “test runner process” world and the “app process” world.</p><p>As you can imagine, those two worlds cannot be crossed wildly. There are some limitations. We can only use types that are visible to the Objective-C runtime, as you can see from the @objc keyword in the TodoListViewHost protocol. Also, the different files must belong to very specific targets. The whole configuration is quite cumbersome.</p><p>This article is not intended to be an EarlGrey’s tutorial. There’s extensive documentation and examples on their web page if you are interested.</p><p>These are the two files needed for our example.</p><pre>@objc<br>protocol TodoListViewHost {<br>    func givenLoadedTodoListView() -&gt; () -&gt; Bool<br>    func givenLoadingTodoListView() -&gt; () -&gt; Bool<br>    func givenErrorTodoListView() -&gt; () -&gt; Bool<br>}<br><br>// Hosts cannot be reused across tests. Make sure to create a new one each time.<br>let todoListViewHost: () -&gt; TodoListViewHost = {<br>    unsafeBitCast(<br>        GREYHostApplicationDistantObject.sharedInstance,<br>        to: TodoListViewHost.self<br>    )<br>}</pre><pre>import SwiftUI<br>@testable import Testing<br><br>extension GREYHostApplicationDistantObject: TodoListViewHost {<br>    func givenLoadingTodoListView() -&gt; () -&gt; Bool {<br>        givenTodoListView {<br>            try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br>    }<br><br>    func givenLoadedTodoListView() -&gt; () -&gt; Bool {<br>        givenTodoListView {<br>            // Load unsorted todos so we can verify that they are properly sorted in the view.<br>            Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        }<br>    }<br><br>    func givenErrorTodoListView() -&gt; () -&gt; Bool {<br>        givenTodoListView {<br>            struct SomeError: Error {}<br>            throw SomeError()<br>        }<br>    }<br><br>    private func givenTodoListView(loadTodos: @escaping () async throws -&gt; [Todo]) -&gt; () -&gt; Bool {<br>        let databaseSpy = DatabaseManagerSpy()<br>        let view = TodoListView(databaseManager: databaseSpy, loadTodos: loadTodos)<br>        UIWindow.current.rootViewController = UIHostingController(rootView: view)<br>        return { databaseSpy.called }<br>    }<br>}<br><br>private extension UIWindow {<br>    static var current: UIWindow {<br>        UIApplication.shared.connectedScenes<br>            .compactMap { $0 as? UIWindowScene }<br>            .flatMap(\.windows)<br>            .filter(\.isKeyWindow)<br>            .first!<br>    }<br>}<br><br>private class DatabaseManagerSpy: DatabaseManagerProtocol {<br>    var called: Bool = false<br><br>    func save&lt;T&gt;(data: T) where T: Encodable {<br>        called = true<br>    }<br>}</pre><p>We have improved the initial UI tests quite a lot:</p><ul><li>Tests are now much more deterministic. They never go to the network, and they take less time to execute because of that.</li><li>We can reliably test the loading and loaded cases, as well as the error case.</li><li>We can test that the todos have been properly saved (or not saved) into the database (well, at least we can test that the message is properly sent to the database manager).</li><li>We can set a specific view as the window’s root view without having to navigate to the specific screen as we have to do with normal UI tests.</li></ul><p>But…</p><ul><li>Maintainability is still hard. The “given” is scattered across different files and targets.</li><li>The bridge to cross both processes rely on @objc, which is not very convenient and an important limitation for the types we can use.</li><li>We still have a very slow feedback loop. 12 seconds is still quite a lot of time for just four tests.</li><li>We are coupled now with EarlGrey, a Google’s framework whose future is unknown… That’s something we should take into account. What’s the impact of Google abandoning that framework?</li></ul><p>Let’s keep looking for better alternatives to test our view. Let’s finally move to unit tests.</p><h3>Unit testing the view</h3><p>We can hardly justify those big numbers for our main test suite. One of the most important aspects of tests is that they allow refactoring with confidence, and for that, we need to execute tests a lot while performing changes. UI tests don’t allow that. They are kind of a last-resort safety net to make sure we haven’t broken anything, but we should have better solutions.</p><p>If we want to unit test the view, we need to have a way to perform actions on it. In UIKit, this was as easy as exposing the subviews and exercising those actions via APIs like button.sendActions(for: .touchUpInside)(even if that’s not exactly correct, as subviews should be implementation details of the parent view).</p><p>But SwiftUI is different. We don’t have access to the underlying subviews, as they are implementation details that the framework decides based on a View value.</p><p>Fortunately, there are tools like <a href="https://github.com/nalexn/ViewInspector">ViewInspector</a> that we can use to access view elements and mimic those view interactions.</p><p>A unit test of the view looks like this.</p><pre>func testLoading() throws {<br>    // Given<br>    let databaseSpy = DatabaseManagerSpy()<br>    let sut = TodoListView(databaseManager: databaseSpy) {<br>        try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>        fatalError(&quot;Should never happen&quot;)<br>    }<br><br>    // When<br>    try sut.inspect().find(button: &quot;Start&quot;).tap()<br><br>    // Then<br>    assertSnapshot(matching: sut, as: .wait(for: 0.01, on: .image))<br>    XCTAssertFalse(databaseSpy.called)<br>}</pre><p>While the ViewInspector library correctly retrieves and taps the button via try sut.inspect().find(button: &quot;Start&quot;).tap(), the final snapshot is not the loading view but the idle view. Why?</p><p>If we set some logs in the view body, we can see that the view body is correctly recomputed but always with the idle state… 🤔</p><p>Let’s see what happens if, instead of handling the state via an @State property wrapper, we have a view model via a @StateObject.</p><p>Let’s create the view model first, extracting all the logic from the view.</p><pre>@MainActor<br>final class TodoListViewModel: ObservableObject {<br>    @Published private(set) var state: ListViewState = .idle {<br>        didSet {<br>            guard case .loaded(let todos) = state else {<br>                return<br>            }<br><br>            databaseManager.save(data: todos)<br>        }<br>    }<br><br>    private let databaseManager: DatabaseManagerProtocol<br>    private let loadTodos: () async throws -&gt; [Todo]<br><br>    private var tasks: [Task&lt;Void, Never&gt;] = .init()<br><br>    deinit {<br>        for task in tasks {<br>            task.cancel()<br>        }<br>    }<br><br>    init(<br>        databaseManager: DatabaseManagerProtocol,<br>        loadTodos: @escaping () async throws -&gt; [Todo] = loadTodos<br>    ) {<br>        self.databaseManager = databaseManager<br>        self.loadTodos = loadTodos<br>    }<br><br>    enum Message {<br>        case startButtonTapped<br>        case tryAgainButtonTapped<br>    }<br><br>    func send(_ message: Message) {<br>        switch message {<br>        case .startButtonTapped, .tryAgainButtonTapped:<br>            tasks.append(<br>                Task {<br>                    await refreshTodos()<br>                }<br>            )<br>        }<br>    }<br><br>    private func refreshTodos() async {<br>        state = .loading<br>        do {<br>            let todos = try await loadTodos().sorted { $0.title &lt; $1.title }<br>            state = .loaded(todos)<br>        } catch {<br>            state = .error<br>        }<br>    }<br><br>    static func loadTodos() async throws -&gt; [Todo] {<br>        let url = URL(string: &quot;https://jsonplaceholder.typicode.com/todos/&quot;)!<br>        let (data, _) = try await URLSession.shared.data(from: url)<br>        return try JSONDecoder().decode([Todo].self, from: data)<br>    }<br>}</pre><p>We have extracted all the important logic in the view model, having the view layer dumb, only forwarding events to the view model and rendering itself based on its state.</p><p>The view layer’s API hasn’t changed, though. It’s still created by injecting a database and a way to load todos, keeping the view model private.</p><pre>struct TodoListView: View {<br>    @StateObject private var viewModel: TodoListViewModel<br><br>    init(<br>        databaseManager: DatabaseManagerProtocol,<br>        loadTodos: @escaping () async throws -&gt; [Todo] = TodoListViewModel.loadTodos<br>    ) {<br>        _viewModel = .init(wrappedValue: .init(databaseManager: databaseManager, loadTodos: loadTodos))<br>    }<br><br>    var body: some View {<br>        Group {<br>            switch viewModel.state {<br>            case .idle:<br>                Button(&quot;Start&quot;) {<br>                    viewModel.send(.startButtonTapped)<br>                }<br><br>            case .loading:<br>                Text(&quot;Loading…&quot;)<br><br>            case .error:<br>                VStack {<br>                    Text(&quot;Oops&quot;)<br>                    Button(&quot;Try again&quot;) {<br>                        viewModel.send(.tryAgainButtonTapped)<br>                    }<br>                }<br><br>            case .loaded(let todos):<br>                VStack {<br>                    List(todos) {<br>                        Text(&quot;\($0.title)&quot;)<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>As the view API didn’t change, the test looks the same:</p><pre>func testLoading() throws {<br>    // Given<br>    let databaseSpy = DatabaseManagerSpy()<br>    let sut = TodoListView(databaseManager: databaseSpy) {<br>        try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>        fatalError(&quot;Should never happen&quot;)<br>    }<br><br>    // When<br>    try sut.inspect().find(button: &quot;Start&quot;).tap()<br><br>    // Then<br>    assertSnapshot(matching: sut, as: .wait(for: 0.01, on: .image))<br>    XCTAssertFalse(databaseSpy.called)<br>}</pre><p>As you might expect, the test still fails, but having the view model gives us much more insights than before.</p><p>try sut.inspect().find(button: &quot;Start&quot;).tap() is causing the view model to be initialized three times. Then assertSnapshot causes the final initialization of the view model. In total, while the view is initialized only once, its underlying view model is initialized four times. For some reason, the view model is not correctly retained by the underlying view when running on tests. It’s destroyed and recreated when accessed several times. In fact, we can see the following runtime warnings…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z56f_p84zZoU7DPjXd8l1Q.png" /></figure><p>Let’s now try something different. Let’s inject the view model, so we have a way to retain the view model in the test scope ourselves.</p><pre>struct TodoListView: View {<br>    @StateObject private var viewModel: TodoListViewModel<br><br>    init(viewModel: @escaping @autoclosure () -&gt; TodoListViewModel) {<br>        _viewModel = .init(wrappedValue: viewModel())<br>    }<br><br>    …<br>}</pre><p>Now, the test looks like this.</p><pre>func testLoading() throws {<br>    // Given<br>    let databaseSpy = DatabaseManagerSpy()<br>    let viewModel = TodoListViewModel(databaseManager: databaseSpy) {<br>        try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>        fatalError(&quot;Should never happen&quot;)<br>    }<br>    let sut = TodoListView(viewModel: viewModel)<br><br>    // When<br>    try sut.inspect().find(button: &quot;Start&quot;).tap()<br><br>    // Then<br>    assertSnapshot(matching: sut, as: .wait(for: 0.01, on: .image))<br>    XCTAssertFalse(databaseSpy.called)<br>}</pre><p>We still get the Accessing StateObject’s object without being installed on a View. This will create a new instance each time. error, but the view model is only initialized once, as expected, so everything works correctly. To avoid the aforementioned runtime warning, we can send the messages directly to the view model to “simulate” the interaction with the UI and remove the ViewInspector usage (which kind of feels like a hack to me…).</p><p>Having the view model around in the test can also be handy to assert its state if needed.</p><pre>assertSnapshot(<br>    matching: viewModel.state,<br>    as: .dump<br>)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/550/1*31D8JA723azk3I6KOu2Xcg.png" /></figure><p>The dump strategy can be more fragile than expected sometimes, coupling ourselves with the specific shape of the underlying types we use for our state. Sometimes, it’s a little bit more future-proof to use a different strategy, like the JSON one, when our state conforms to Encodable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/594/1*NJTJAjpgwUJvtIXEPhdC8Q.png" /></figure><p>The final test suite looks like this.</p><pre>@MainActor<br>final class TodoListViewUnitTests: XCTestCase {<br>    func testIdle() {<br>        // Given<br>        let (viewModel, _) = givenTodoListViewModel {<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br>        let sut = TodoListView(viewModel: viewModel)<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>        assertSnapshot(matching: viewModel.state, as: .json)<br>    }<br><br>    func testLoading() async throws {<br>        // Given<br>        let (viewModel, todosSaved) = givenTodoListViewModel {<br>            try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br>        let sut = TodoListView(viewModel: viewModel)<br><br>        // When<br>        viewModel.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>        assertSnapshot(matching: viewModel.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br><br>    func testLoaded() async throws {<br>        // Given<br>        let (viewModel, todosSaved) = givenTodoListViewModel {<br>            // Load unsorted todos so we can verify that they are properly sorted in the view.<br>            Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        }<br>        let sut = TodoListView(viewModel: viewModel)<br><br>        // When<br>        viewModel.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>        assertSnapshot(matching: viewModel.state, as: .json)<br>        XCTAssertTrue(todosSaved())<br>    }<br><br>    func testError() async throws {<br>        // Given<br>        let (viewModel, todosSaved) = givenTodoListViewModel {<br>            struct SomeError: Error {}<br>            throw SomeError()<br>        }<br>        let sut = TodoListView(viewModel: viewModel)<br><br>        // When<br>        viewModel.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>        assertSnapshot(matching: viewModel.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br>}<br><br>private class DatabaseManagerSpy: DatabaseManagerProtocol {<br>    var called: Bool = false<br><br>    func save&lt;T&gt;(data: T) where T: Encodable {<br>        called = true<br>    }<br>}<br><br>@MainActor private func givenTodoListViewModel(loadTodos: @escaping () async throws -&gt; [Todo]) -&gt; (<br>    viewModel: TodoListViewModel,<br>    todosSaved: () -&gt; Bool<br>) {<br>    let databaseSpy = DatabaseManagerSpy()<br>    let viewModel = TodoListViewModel(databaseManager: databaseSpy, loadTodos: loadTodos)<br>    return (viewModel, { databaseSpy.called })<br>}</pre><p>So, we’ve gone from 12 seconds to 0,11 seconds. Still, 110 ms for just four unit tests could be considered a big number, especially when we have a big team with many developers and screens, where those numbers start to add up quickly, ending up with a test suite that needs several minutes to run.</p><p>For simple apps, this approach is a good trade-off. They are “fast enough”, and easy to read and maintain while covering quite a lot of surface area (view + view model) to maximize the protection against regressions and resistance to refactoring traits.</p><h3>Testing the view layout and view model separately</h3><p>Testing the view layout and the view model separately means that we could have our main test suite running just our view models, with our most important business logic running super fast, while the view layout tests, which run slower, could be run in a different target, at a different pace, maybe only by our CI, etc. They wouldn’t be integrated into the development process (in our continuous CMD+U while changing code). In a way, they would be more like “UI tests”. Even if they run much faster than UI tests, they still run slow.</p><p>The view model tests look like this:</p><pre>@MainActor<br>final class TodoListViewModelUnitTests: XCTestCase {<br>    func testIdle() {<br>        // Given<br>        let (sut, _) = givenTodoListViewModel {<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>    }<br><br>    func testLoading() async throws {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br><br>        // When<br>        sut.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br><br>    func testLoaded() async throws {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            // Load unsorted todos so we can verify that they are properly sorted in the view.<br>            Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        }<br><br>        // When<br>        sut.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertTrue(todosSaved())<br>    }<br><br>    func testError() async throws {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            struct SomeError: Error {}<br>            throw SomeError()<br>        }<br><br>        // When<br>        sut.send(.startButtonTapped)<br>        try await Task.sleep(nanoseconds: 1_000_000) // Wait for Tasks...<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br>}</pre><p>Removing the screenshot of the view has decreased the time quite a lot. From 110 ms to just 20 ms.</p><p>These tests are still not ideal, though. We have those Task.sleep due to the underlying asynchronicity of the view model that could eventually lead to flaky tests. We have different ways to avoid that:</p><h4>1. Making the view model’s API async</h4><p>Having the send method asynchronous will lead to changes in the view model’s API, breaking the view. Also, we now have to manage the Task lifecycle inside the view layer if we need to cancel it, for instance.</p><pre>@MainActor<br>final class TodoListViewModel: ObservableObject {<br>    func send(_ message: Message) async {<br>        switch message {<br>        case .startButtonTapped, .tryAgainButtonTapped:<br>            await refreshTodos()<br>        }<br>    } <br>    …<br>}<br><br>struct TodoListView: View {<br>    @StateObject private var viewModel: TodoListViewModel<br><br>    init(viewModel: @escaping @autoclosure () -&gt; TodoListViewModel) {<br>        _viewModel = .init(wrappedValue: viewModel())<br>    }<br><br>    var body: some View {<br>        Group {<br>            switch viewModel.state {<br>            case .idle:<br>                Button(&quot;Start&quot;) {<br>                    Task {<br>                        await viewModel.send(.startButtonTapped)<br>                    }<br>                }<br>     …<br>    }<br>    …<br>}</pre><p>But the tests will be simpler, without waits.</p><pre>func testLoaded() async {<br>    // Given<br>    let (sut, todosSaved) = givenTodoListViewModel {<br>        // Load unsorted todos so we can verify that they are properly sorted in the view.<br>        Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>    }<br><br>    // When<br>    await sut.send(.startButtonTapped)<br><br>    // Then<br>    assertSnapshot(matching: sut.state, as: .json)<br>    XCTAssertTrue(todosSaved())<br>}</pre><p>Unfortunately, not all tests will be as simple as that. The loading test will lead to an infinite wait.</p><pre>func testLoading() async {<br>    // Given<br>    let (sut, todosSaved) = givenTodoListViewModel {<br>        try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>        fatalError(&quot;Should never happen&quot;)<br>    }<br><br>    // When<br>    await viewModel.send(.startButtonTapped) // we wait forever here<br><br>    // Then<br>    assertSnapshot(<br>        matching: sut.state,<br>        as: .json<br>    )<br>    XCTAssertFalse(todosSaved())<br>}</pre><p>Also, I’m not sure it’s the correct API, though. It seems that we are only exposing the send method async because of tests… 🤔.</p><h4>2. Using expectations</h4><p>We can leverage expectations to wait until we have the state we need.</p><pre>@MainActor<br>final class TodoListViewModelTests: XCTestCase {<br>    func testLoading() async {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            try await Task.sleep(nanoseconds: 1_000_000 * NSEC_PER_SEC)<br>            fatalError(&quot;Should never happen&quot;)<br>        }<br><br>        // When<br>        let expectation = expectation(description: &quot;waiting for state&quot;)<br>        var cancellables = Set&lt;AnyCancellable&gt;()<br>        sut.$state.sink { state in<br>            guard case .loading = state else { return }<br>            expectation.fulfill()<br>        }.store(in: &amp;cancellables)<br><br>        sut.send(.startButtonTapped)<br><br>        await fulfillment(of: [expectation], timeout: 5)<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br><br>    func testLoaded() async throws {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            // Load unsorted todos so we can verify that they are properly sorted in the view.<br>            Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        }<br><br>        // When<br>        let expectation = expectation(description: &quot;waiting for state&quot;)<br>        var cancellables = Set&lt;AnyCancellable&gt;()<br>        sut.$state.sink { state in<br>            guard case .loaded = state else { return }<br>            expectation.fulfill()<br>        }.store(in: &amp;cancellables)<br><br>        sut.send(.startButtonTapped)<br><br>        await fulfillment(of: [expectation], timeout: 5)<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertTrue(todosSaved())<br>    }<br><br>    func testError() async throws {<br>        // Given<br>        let (sut, todosSaved) = givenTodoListViewModel {<br>            struct SomeError: Error {}<br>            throw SomeError()<br>        }<br><br>        // When<br>        let expectation = expectation(description: &quot;waiting for state&quot;)<br>        var cancellables = Set&lt;AnyCancellable&gt;()<br>        sut.$state.sink { state in<br>            guard case .error = state else { return }<br>            expectation.fulfill()<br>        }.store(in: &amp;cancellables)<br><br>        sut.send(.startButtonTapped)<br><br>        await fulfillment(of: [expectation], timeout: 5)<br><br>        // Then<br>        assertSnapshot(matching: sut.state, as: .json)<br>        XCTAssertFalse(todosSaved())<br>    }<br>}</pre><h4>3. Separating logic from effects</h4><p><a href="https://medium.com/jobandtalenteng/ios-architecture-separating-logic-from-effects-7629cb763352">I have talked extensively about this approach in the past</a>. We can extract the decision-making from the view model to the state type.</p><pre>enum ListViewState: Equatable, Codable {<br>    case idle<br>    case loading<br>    case loaded([Todo])<br>    case error<br><br>    enum Message {<br>        case input(Input)<br>        case feedback(Feedback)<br><br>        enum Input {<br>            case startButtonTapped<br>            case tryAgainButtonTapped<br>        }<br><br>        enum Feedback {<br>            case didFinishReceivingTodos(Result&lt;[Todo], Error&gt;)<br>        }<br>    }<br><br>    enum Effect: Equatable {<br>        case loadTodos<br>        case saveTodos([Todo])<br>    }<br><br>    mutating func handle(message: Message) -&gt; Effect? {<br>        switch message {<br>        case .input(.startButtonTapped), .input(.tryAgainButtonTapped):<br>            self = .loading<br>            return .loadTodos<br><br>        case .feedback(.didFinishReceivingTodos(.success(let todos))):<br>            self = .loaded(todos.sorted { $0.title &lt; $1.title })<br>            return .saveTodos(todos)<br><br>        case .feedback(.didFinishReceivingTodos(.failure)):<br>            self = .error<br>            return nil<br>        }<br>    }<br>}</pre><p>The view model, which becomes a <a href="https://martinfowler.com/bliki/HumbleObject.html">humble object</a> after extracting all the logic, looks like this now (just forwarding messages to the state type and interpreting effects).</p><pre>@MainActor<br>final class TodoListViewModel: ObservableObject {<br>    @Published private(set) var state: ListViewState = .idle<br><br>    private let databaseManager: DatabaseManagerProtocol<br>    private let loadTodos: () async throws -&gt; [Todo]<br><br>    private var tasks: [Task&lt;Void, Never&gt;] = .init()<br><br>    deinit {<br>        for task in tasks {<br>            task.cancel()<br>        }<br>    }<br><br>    init(<br>        databaseManager: DatabaseManagerProtocol,<br>        loadTodos: @escaping () async throws -&gt; [Todo] = loadTodos<br>    ) {<br>        self.databaseManager = databaseManager<br>        self.loadTodos = loadTodos<br>    }<br><br>    func send(_ message: ListViewState.Message.Input) {<br>        send(.input(message))<br>    }<br><br>    private func send(_ message: ListViewState.Message) {<br>        guard let effect = state.handle(message: message) else { return }<br>        tasks.append(<br>            Task {<br>                await perform(effect: effect)<br>            }<br>        )<br>    }<br><br>    private func perform(effect: ListViewState.Effect) async {<br>        switch effect {<br>        case .loadTodos:<br>            do {<br>                let todos = try await loadTodos()<br>                send(.feedback(.didFinishReceivingTodos(.success(todos))))<br>            } catch {<br>                send(.feedback(.didFinishReceivingTodos(.failure(error))))<br>            }<br><br>        case .saveTodos(let todos):<br>            databaseManager.save(data: todos)<br>        }<br>    }<br><br>    private static func loadTodos() async throws -&gt; [Todo] {<br>        let url = URL(string: &quot;https://jsonplaceholder.typicode.com/todos/&quot;)!<br>        let (data, _) = try await URLSession.shared.data(from: url)<br>        return try JSONDecoder().decode([Todo].self, from: data)<br>    }<br>}</pre><p>The tests are now simpler than ever.</p><pre>@MainActor<br>final class TodoListViewModelUnitTests: XCTestCase {<br>    func testLoading() {<br>        // Given<br>        var sut = ListViewState.idle<br><br>        // When<br>        let effect = sut.handle(message: .input(.startButtonTapped))<br><br>        // Then<br>        assertSnapshot(matching: (sut, effect), as: .dump)<br>    }<br><br>    func testLoaded() {<br>        // Given<br>        var sut = ListViewState.loading<br><br>        // When<br>        let todos: [Todo] = Set(0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        let effect = sut.handle(message: .feedback(.didFinishReceivingTodos(.success(todos))))<br><br>        // Then<br>        let expectedTodos = todos.sorted { $0.title &lt; $1.title }<br>        XCTAssertEqual(effect, .saveTodos(expectedTodos))<br>        XCTAssertEqual(sut, .loaded(expectedTodos))<br>    }<br><br>    func testError() async throws {<br>        // Given<br>        var sut = ListViewState.loading<br><br>        // When<br>        struct SomeError: Error {}<br>        let effect = sut.handle(message: .feedback(.didFinishReceivingTodos(.failure(SomeError()))))<br><br>        // Then<br>        XCTAssertNil(effect)<br>        XCTAssertEqual(sut, .error)<br>    }<br>}</pre><p>As you can see, we can have the normal XCTAssertEqual assertions, or we can assert the tuple (state, effect) snapshot (which can be quite handy when building the final assertion value is cumbersome). Take into account that assertSnapshot is several orders of magnitude slower than using XCTAssertEqual, so use it with caution.</p><p>Also, we have reduced the time to 10 ms. We don’t have waits, everything is deterministic, and we are testing the view logic, which has the highest ROI in unit testing.</p><p>Unit testing the state this way allows us to test all the corner cases very easily. Then, we can have a few “higher level” tests of the view model, with all its dependencies, verifying just some happy cases to double-check that everything works correctly together.</p><p>In his book, Vladimir Khorikov says that the most important parts of our code, the ones with the most domain significance, should have no dependencies. And that’s exactly what we’ve done by extracting the logic to the ListViewState type.</p><h4>A note about refactoring the view model</h4><p>In his book, Vladimir considered <em>resistance to refactoring</em> as the most important trait of a test. Quite a special one as well, as a test either has resistance to refactoring or not. It’s a binary choice. There are no trade-offs here. To do that, we should see our components as black boxes with inputs and outputs and avoid white-box testing and testing interactions as much as possible. Let’s see an example with the view model.</p><ul><li>Input: messages/events sent from the view.</li><li>Output: state (to be rendered by the view) and observable behavior.</li></ul><p>In this case, the observable behavior is the todos being saved to the database (a side effect).</p><p>Let’s imagine we want to refactor the view model by extracting some piece of functionality, the sorting algorithm, for instance, to a standalone component, like a use case. That’s perfectly reasonable. What might not be a good idea is to test that the view model sends certain messages to that use case. The use case is just an implementation detail of how the view model has decided to achieve its business goal. We can decide to split all the responsibilities of the view model into several collaborators, which should be tested separately. But when testing the view model, those collaborators (the stable dependencies) should be considered implementation details. If we decide in the future that the use case should talk to a repository for data access, that shouldn’t break the test because the inputs and outputs of the “view model black box” are the same. We should still assert:</p><ul><li>The final state (via public API).</li><li>The observable behavior: the todos saved in the database or not (via database mock/spy).</li></ul><h4>Testing the view layout</h4><p>After testing the view model, arguably the most important part, we still have to test that the view layer looks correct. We want to test the view layout comfortably without creating the view model and all its dependencies. We have two ways to do that</p><h4>1. Container/presentational views</h4><p>It was Dan Abramov who first popularized this pattern in React back in 2015 with <a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">this article</a>. It’s fairly simple. We should split our “normal SwiftUI views” in two:</p><ul><li><em>Container view</em>: performs the logic and side effects and communicates with the presentation view for display purposes.</li><li><em>Presentational view</em>: it displays data and notifies of view events.</li></ul><p>In our example, it would be as simple as creating an internal Presentation type. The input would be ListViewState, and the output would be the messages sent from the view.</p><pre>struct TodoListView: View {<br>    @StateObject private var viewModel: TodoListViewModel<br><br>    init(viewModel: @escaping @autoclosure () -&gt; TodoListViewModel) {<br>        _viewModel = .init(wrappedValue: viewModel())<br>    }<br><br>    var body: some View {<br>        Presentation(input: viewModel.state, output: viewModel.send)<br>    }<br><br>    struct Presentation: View {<br>        var input: ListViewState<br>        var output: (ListViewState.Message.Input) -&gt; Void<br><br>        var body: some View {<br>            Group {<br>                switch input {<br>                case .idle:<br>                    Button(&quot;Start&quot;) {<br>                        output(.startButtonTapped)<br>                    }<br><br>                case .loading:<br>                    Text(&quot;Loading…&quot;)<br><br>                case .error:<br>                    VStack {<br>                        Text(&quot;Oops&quot;)<br>                        Button(&quot;Try again&quot;) {<br>                            output(.tryAgainButtonTapped)<br>                        }<br>                    }<br><br>                case .loaded(let todos):<br>                    VStack {<br>                        List(todos) {<br>                            Text(&quot;\($0.title)&quot;)<br>                        }<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>Take into account that Presentation should be internal to be accessible from tests but shouldn’t be included as the public API of the container view.</p><p>The tests look like this.</p><pre>final class TodoListViewTests: XCTestCase {<br>    func testIdle() {<br>        // Given<br>        let sut = TodoListView.Presentation(input: .idle) { _ in }<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>    }<br><br>    func testLoading() {<br>        // Given<br>        let sut = TodoListView.Presentation(input: .loading) { _ in }<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>    }<br><br>    func testLoaded() {<br>        // Given<br>        let todos: [Todo] = (0...9).map { .init(userId: $0, id: $0, title: &quot;\($0)&quot;, completed: false) }<br>        let sut = TodoListView.Presentation(input: .loaded(todos)) { _ in }<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>    }<br><br>    func testError() {<br>        // Given<br>        let sut = TodoListView.Presentation(input: .error) { _ in }<br><br>        // Then<br>        assertSnapshot(matching: sut, as: .image)<br>    }<br>}</pre><p>We can even combine the state tests with the view layout tests.</p><pre>func testLoading() {<br>    // Given (a initial state)<br>    var state = ListViewState.idle<br><br>    // When (the user taps the start button)<br>    let effect = state.handle(message: .input(.startButtonTapped))<br><br>    // Then (both state and view layout are in the correct loading state)<br>    assertSnapshot(<br>        matching: (state, effect),<br>        as: .dump<br>    )<br>    assertSnapshot(<br>        matching: TodoListView.Presentation(input: state) { _ in },<br>        as: .image<br>    )<br>}</pre><p>Take into account that having the Presentation type is also very convenient for previews, where we can easily have different previews configured with different states without mocking the view model.</p><h4>2. Frozen view models</h4><p>Because sometimes, we don’t want to create that Presentation type… To do that, we’d need to inject a “dummy” view model that does nothing and can be created with a specific initial state. This is important because the view lifecycle can call view model methods under the hood. We can always “freeze” a view model by creating an abstraction of the view model ad-hoc with a no-op implementation, but it’s much easier and ergonomic (without incurring <a href="https://dhh.dk/2014/test-induced-design-damage.html">test-induced design damage</a>) when using architectures where we fully control the side effects, like the one described <a href="https://medium.com/better-programming/different-flavors-of-unidirectional-architectures-in-swift-781a01380ef6">here</a>.</p><p>Creating a “dummy reducer” that does nothing is as simple as this.</p><pre>extension ViewModel {<br>    static func freeze(with state: State) -&gt; ViewModel&lt;State, Input, Feedback, Output&gt; {<br>        .init(state: state, reducer: DummyReducer())<br>    }<br>}<br><br>private class DummyReducer&lt;State, Input, Feedback, Output&gt;: Reducer where State: Equatable {<br>    func reduce(<br>        message: Message&lt;Input, Feedback&gt;,<br>        into state: inout State<br>    ) -&gt; Effect&lt;Feedback, Output&gt; {<br>        .none<br>    }<br>}</pre><p>And the test looks like this.</p><pre>func testLoading() {<br>    // Given<br>    let sut = TodoListView(viewModel: .freeze(state: .loading))<br><br>    // Then<br>    assertSnapshot(<br>        matching: sut,<br>        as: .image<br>    )<br>}</pre><h4>Conclusion</h4><p>Thanks a lot for reaching the end of the article. It was another long read 😅.</p><p>We’ve seen different ways to test our view layer, both the view logic and also the layout.</p><p>While it’s quite clear that most of our tests should be unit tests and not UI tests, it’s unclear which approach to take.</p><ul><li>Should we test the view and view model together and only verify the view layout?</li><li>Should we test the view model and view layout separately?</li><li>Should we move the view logic outside the view model, to a value layer with no dependencies where it can easily be tested?</li></ul><p>All approaches are perfectly valid, and depending on your project and your needs, you might find one more appropriate than the other.</p><ul><li>Do you have a simple app with just a few screens and developers? The first approach may be the most reasonable.</li><li>Do you have a big app with lots of developers and screens? Consider moving the view layout testing to a different target so the main test suite remains fast.</li><li>Do you have quite complex business logic and requirements with lots of corner cases? Extracting the logic into a value type with no dependencies could simplify testing.</li></ul><p>As always, it depends 😄. As much as we always like to have “our way of doing things”, we should think critically, understand the app domain and requirements, and develop testing solutions that best fit our needs.</p><p>I’m really interested in knowing what you think. Do you have strong opinions about how we should test our view layer? Let me know in the comments.</p><p>Thanks!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aeb832107fe7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/swiftui-testing-a-pragmatic-approach-aeb832107fe7">SwiftUI Testing: a Pragmatic Approach</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Many Flavors of Unidirectional Architectures in Swift]]></title>
            <link>https://medium.com/better-programming/different-flavors-of-unidirectional-architectures-in-swift-781a01380ef6?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/781a01380ef6</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[apple]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Sat, 01 Apr 2023 00:38:25 GMT</pubDate>
            <atom:updated>2023-04-01T13:24:49.944Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>How have SwiftUI and async/await changed the concept of state containers in these last six years?</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9NJP1rVNSeRiZvSr" /><figcaption>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><a href="https://medium.com/jobandtalenteng/ios-architecture-an-state-container-based-approach-4f1a9b00b82e">Back in 2017, I wrote about the concept of “State Containers”</a>. Six years later, I still like to build most of my apps around them, using the very same concept for the two main layers inside my apps:</p><ul><li><strong>The view layer</strong>: a view model is a “view state container” modeling view-specific state and business logic.</li><li><strong>The domain layer</strong>: usually aggregate root models (or data repositories/services), representing the business rules and keeping the integrity and consistency around a specific bounded context (or entity) in the application.</li></ul><p>A lot has changed since 2017, though. In 2019, Apple introduced SwiftUI. Two years later, async/await came along. While we always like to think of good architectures as those not coupled to the specifics of the framework, good architectures are also good citizens of those same frameworks and the general ecosystem. So… <strong>How have SwiftUI and async/await changed the concept of state containers in these last six years?</strong> Let’s see.</p><h3>The Case Against MVVM in SwiftUI</h3><p>But first, let&#39;s talk about some recent conversations in the iOS community regarding MVVM and whether it is a good fit for SwiftUI. <strong>The premise is that “the View is the view model” already, so MVVM is unnecessary.</strong></p><p>I disagree with that premise. While it’s true that SwiftUI already has built-in primitives (in the form of property wrappers) to simplify a lot of the glue code around state observation and re-rendering, <strong>the view is just a declarative description of how the view layer looks like</strong>. A view model is so much more than that. It’s the place where the view-related business logic lives.</p><p>But let’s start with the right question. Why would we want to move code out of the view layer? Without entering in (sometimes subjective) debates about responsibilities and how software should be split, there’s something clear: <strong>unless we move the code out of the view layer, it will be complicated to unit test</strong>. But where should we put that code?</p><p>My answer to this, as most times, is “it depends”. Sometimes, we can have that logic inside a domain state container observed from different views that need the same information. Sometimes, it’s just view-specific business logic. In that case, I like to put that logic inside a view model (a view state container). Other times, it’s a matter of processing different information from different data sources and validating it to produce a view state. In all these scenarios, a view model makes a lot of sense. It makes sense because it bundles essential business logic, which is only important for that view. Moving this view-specific business logic to a domain model will likely make it less cohesive and, eventually, <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">the wrong abstraction</a>.</p><p>Ultimately, <strong>it’s just a matter of moving business logic out of the view in a place where it makes sense, guided by cohesion and coupling principles</strong> (or SOLID principles in general), so it can be easily testable.</p><p>Finally, I wonder why the best iOS course in the world, <a href="https://cs193p.sites.stanford.edu/">Standford’s CS193P</a>, still <a href="https://www.youtube.com/watch?v=--qKOhdgJAs">teaches MVVM as the main presentation pattern for SwiftUI apps</a>.</p><p>Also, Apple seems to feel the same way about not using View as the view model 🤔.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NhdY5C-g91lq4Mx6cj_tyA.png" /></figure><p>Now, get back to state containers.</p><h3>The Shape of a State Container</h3><p>I like to think of state containers (also &quot;Stores&quot; from now on) as just “black boxes” with some inputs and outputs. Specifically, a black box with some observable state that can also emit outputs.</p><pre>@MainActor<br>protocol StoreType&lt;State, Output&gt;: ObservableObject where State: Equatable {<br>    associatedtype State<br>    associatedtype Output<br>    associatedtype StateStream: AsyncSequence where StateStream.Element == State<br>    associatedtype OutputStream: AsyncSequence where OutputStream.Element == Output<br><br>    var stateStream: StateStream { get }<br>    var outputStream: OutputStream { get }<br><br>    var state: State { get set }<br>}</pre><p>Some important decisions in that code are worth mentioning.</p><ul><li>I have decided to use ObservableObject and @MainActor, so we can be sure that it can be used correctly from a SwiftUI view layer, <strong>regardless of the specific type of state: view state or domain state</strong>. We should allow enough flexibility to decide whether a view or domain layer makes sense.</li><li>While Combine’s future is still unclear, I feel it’s safer and more future-proof to provide an API based on AsyncSequence and not couple the store with Combine in this case.</li></ul><p>One interesting nitpick detail about choosing AsyncSequence instead of Combine is that the API is incorrect. Both StateStream and OutputStream should be streams that never fail, but there’s no way to constrain that with the AsyncSequence type. In Combine, and thanks to the recent primary associated types in protocols, it would be as easy and succinct as this:</p><pre>associatedtype StateStream: Publisher&lt;State, Never&gt;<br>associatedtype OutputStream: Publisher&lt;Output, Never&gt;</pre><p>Unlike Combine, AsyncSequence works by using async throwing functions, whose errors are always untyped by design.</p><pre>mutating func next() async throws -&gt; Self.Element?</pre><p>Interestingly, Swift allows conforming to a protocol that declares a throwing function by omitting the throw keyword in the implementation. That means that the equivalent of the Never type in the Combine world would be to use a concrete implementation of AsyncSequence with a non-throwing next function:</p><pre>mutating func next() async -&gt; Self.Element?</pre><p>Fortunately, we will end up using the concrete StateStream and OutputStream types, so all this won’t be a problem.</p><p>Now, it’s time to create the different flavors of that StoreType.</p><h3>Different Flavors of State Size</h3><p>This section brings us to the first two main flavors there are, depending on how we decide to organize our state:</p><ul><li>One single piece of state for the whole application (<em>the Redux way</em>).</li><li>Different distributed pieces of state across the app (<em>the Flux way</em>).</li></ul><p>Each one has its pros and cons. The distributed approach&#39;s main &quot;con&quot; is keeping data consistent across those different state containers, which can be a hard problem to solve. This problem disappears altogether by just having a single state value. That is a huge advantage. But as you might expect, there are also some big cons with the single-state value approach: performance and coupling.</p><h4>The performance problem</h4><p>The performance problem comes in two ways. The mutation can be expensive due to the big state value and the lack of persistent structures in Swift. But, more importantly, <strong>any change of that value will retrigger view re-renders across the app</strong> (or lots of recomputations of functions and Equatable checks). Even if SwiftUI can be smart enough not to render some view nodes, your view bodies will still be called, and you could reach performance bottlenecks. For simple apps, this might not be a problem. I know many apps using TCA, the well-known iOS architecture using the Redux approach, which are working great. But for more complex apps, you might likely run into performance issues, although you can always use a multi-store approach. For more info, take a look at Krzysztof Zabłocki’s blog post <a href="https://www.merowing.info/multi-store-tca/">TCA Performance and Multi-Store</a>.</p><h4>The coupling problem</h4><p>The coupling problem is something that a lot of libraries using the Redux pattern have. You are just coupled to the whole “AppState” value. Some libraries like TCA solve that by adding quite a lot of complexity to the architecture. Propagating state changes and messages from children states to eventually the root state comes with many “TCA DSL” and helpers that you have to learn: like using their prisms library (<a href="https://github.com/pointfreeco/swift-case-paths">CasePaths</a>) to work correctly with enums. Depending on your team and their knowledge about functional programming paradigms, the price to couple your whole app to TCA might be fine or simply too much. I still think TCA might be one of the best state management libraries available for iOS nowadays.</p><p>As for my personal preference,<strong> I prefer to use distributed state containers and bind the state as low as possible in the view hierarchy to avoid many view re-renders and body recomputations</strong>. Also, having the correct bounded contexts and modules that don’t need complex synchronization greatly helps. If you want to read more, <a href="https://dev.to/davidkpiano/redux-is-half-of-a-pattern-1-2-1hd7"><em>Redux is half of a pattern</em></a> is a great article.</p><h3>Different Flavors of Separating Logic and Effects</h3><h4>Mixing everything</h4><p>Let’s start with the simplest flavor, which I already talked about in <a href="https://medium.com/jobandtalenteng/ios-architecture-an-state-container-based-approach-4f1a9b00b82e">this blog post</a>, and the one most people should be used to. Having state and effects mixed together by using a Store base class that we’ll subclass (if needed).</p><pre>@dynamicMemberLookup<br>class Store&lt;State, Output&gt;: StoreType where State: Equatable {<br>    private let stateSubject: CurrentValueSubject&lt;State, Never&gt;<br>    lazy var stateStream = stateSubject.removeDuplicates().values<br><br>    private let outputSubject: PassthroughSubject&lt;Output, Never&gt; = .init()<br>    lazy var outputStream = outputSubject.values<br><br>    init(state: State) {<br>        self.state = state<br>        stateSubject = .init(state)<br>    }<br><br>    @Published<br>    var state: State {<br>        didSet {<br>            if !Thread.isMainThread {<br>                // While `Store` is a main actor, we could have some non-isolated contexts where the state<br>                // is not changed on the main thread. This problem can be minimized by setting &quot;Strict Concurrency Checking&quot;<br>                // to &quot;complete&quot;.<br>                assertionFailure(&quot;Not on main thread&quot;)<br>            }<br><br>            // We have traceability of the state here, being able to log it if needed.<br>            stateSubject.send(state)<br>        }<br>    }<br><br>    func send(output: Output) {<br>        outputSubject.send(output)<br>    }<br><br>    subscript&lt;Value&gt;(dynamicMember keypath: KeyPath&lt;State, Value&gt;) -&gt; Value {<br>        state[keyPath: keypath]<br>    }<br>}</pre><p>We can use it in its simplest form, just wrapping some values that we can change and observe:</p><pre>enum Output { case somethingHappened }<br>let store = Store&lt;Int, Output&gt;(state: 0)<br><br>Task {<br>    for await state in store.stateStream {<br>        print(&quot;New state&quot;, state)<br>    }<br>}<br><br>Task {<br>    for await output in store.outputStream {<br>        print(&quot;New output&quot;, output)<br>    }<br>}<br><br>store.state = 1<br>store.send(output: .somethingHappened)</pre><p>Usually, we’ll subclass that Store type, where we’ll have those state changes and outputs with the appropriate business logic.</p><pre>enum NumbersViewState: Equatable {<br>    case idle<br>    case loading<br>    case loaded([Int])<br>    case error<br>}<br><br>enum NumbersViewOutput {<br>    case numbersDownloaded<br>}<br><br>final class NumbersViewModel: Store&lt;NumbersViewState, NumbersViewOutput&gt; {<br>    init() {<br>        super.init(state: .idle)<br>    }<br><br>    func load() async {<br>        state = .loading<br><br>        do {<br>            let numbers = try await apiClient.numbers()<br>            state = .loaded(numbers)<br><br>            // In this case, the output message is not adding anything meaningful, but it&#39;s just<br>            // an example of some &quot;fire and forget&quot; event that can be useful for things that, unlike state, are not persistent.<br>            send(output: .numbersDownloaded)<br>        } catch {<br>            state = .error<br>        }        <br>    }<br>}</pre><p>Simple enough. We subclass the Store type and provide methods (inputs) to bundle the business logic, execute side effects, change state, and trigger output messages.</p><p>Then, we can very easily subscribe to that NumbersViewModel , both from SwiftUI (by using @StateObject or @ObservedObject), or from UIKit, by listening to StateStream and rendering the state to UIKit views correctly.</p><pre>Task {<br>    for await state in numbersViewModel.stateStream {<br>        switch state {<br>        case .idle, .loading:<br>            loadingView.isHidden = false<br>            numbersView.isHidden = true<br>            errorView.isHidden = true<br><br>        case .loaded(let numbers):<br>            loadingView.isHidden = true<br>            errorView.isHidden = true<br>            numbersView.isHidden = false<br>            numbersView.render(with: numbers)<br><br>        case .error:<br>            loadingView.isHidden = true<br>            numbersView.isHidden = true<br>            errorView.isHidden = false<br>        }<br>    }<br>}</pre><p>I’ve personally had great success with this first kind of flavor. While it lacks some proper encapsulation of the state (as it can be mutated from outside the concrete store subclass due to the lack of protected semantics in Swift), it’s familiar, simple, flexible, gives us accurate traceability of the state changes, and keeps them on the main thread due to @MainActor semantics. I don’t know of any concrete well-known iOS libraries using this pattern, but I know some of them in the Android world. <a href="https://github.com/airbnb/mavericks">Airbnb’s Mavericks</a> could be the most important one. <a href="https://github.com/orbit-mvi/orbit-mvi">Orbit</a> is another nice one.</p><p>But sometimes, having traceability of the state is just not enough. <strong>We want to know why that state changed and have a more constrained way of managing effects</strong>. And for that, we’ll need to complicate things a little bit.</p><h4>Separating logic from effects</h4><p>As I said, this is where things get much more complex (but also fun). I also talked about this flavor in the past <a href="https://medium.com/jobandtalenteng/ios-architecture-separating-logic-from-effects-7629cb763352">here</a>.</p><p>While appealing, the simplicity and flexibility of the previous approach have their drawbacks. <strong>The main one is that the <em>freedom</em> to manage logic, state changes, and effects however we want comes at a price</strong>. In straightforward examples like the previous one, it’s hard to see the advantage of complicating that. But <strong>with more complex business logic, teams, and requirements, having a more constrained and formal approach to dealing with state and effects is extremely useful and tends to scale better in my experience</strong>.</p><p>Let’s have some preconditions first.</p><ul><li>We need a way to establish the relationship between the messages, the state changes, and the effects triggered by them.</li><li>We need to have proper encapsulation of the different message types. Wrong message encapsulation is, unfortunately, very common amongst these types of architectures. It’s not the same a public onAppear message than a private didReceiveData message.</li><li>We need a way to model our effects correctly, leveraging async/await.</li></ul><p>During these last few years, there have been many (too many, I’d say) flavors of these unidirectional architectures, each with its own “opinions” and “trade-offs” in the way they manage state and effects.</p><ul><li>Some of those are “too Combine heavy”, forcing you to couple your app to those Combine combinators.</li><li>Some others bring the concept of “mutation” on top of the concept of “message” (or event/action) so they can differentiate between the actual “state change” and the “intention of change”. The main inspiration is Vue’s architecture, <a href="https://vuex.vuejs.org/">Vuex</a>. <a href="https://github.com/ReactorKit/ReactorKit">ReactorKit</a> is an excellent iOS library using that concept.</li><li>Some other flavors model effects as “feedbacks”: a simple (Publisher&lt;State&gt;) -&gt; Publisher&lt;Event&gt; function. <a href="https://github.com/babylonhealth/ReactiveFeedback">ReactiveFeedback</a> is another great library following this pattern.</li></ul><p>In my experience, those flavors complicate things unnecessarily and make the code harder to understand. Especially the concept of modeling effects as “feedback packages”, as it worsens readability and obfuscates control flow. Let’s imagine that we want to understand what happens in the system whenever an action happens:</p><ul><li>First, we’d go to the function that processes the message and changes the state. That function is usually called a reducer.</li><li>Then, to know what effects are triggered by the previous message, we’d have to check all the different “feedbacks” to see if that new state will trigger any of those.</li></ul><p>That makes the code hard to understand and to build the appropriate mental model needed to change that code. <a href="https://dev.to/feresr/a-case-against-the-mvi-architecture-pattern-1add">Here’s a nice article</a> explaining some of these problems in an MVI architecture.</p><p>The solution? <strong>Instead of “magically running” the effects based on the state change, let the reducer decide the effects to run</strong>.</p><p>That way, we can follow precisely the code execution from the point where a message is sent into the system to the point where all the effects have finished running.</p><p><em>Input message -&gt; State changed -&gt; Effect -&gt; Feedback message…</em></p><h4>The different ways of commanding effects</h4><p>There are different ways that a reducer can decide the effect to run:</p><ul><li>We can wrap the effectful computation inside a data structure with the correct async context. Something like this</li></ul><pre>func handle(event: Event, state: inout State) -&gt; Effect&lt;Event&gt; {<br>    switch event {<br>    case .onAppear:<br>        state = .loading<br>        return .task { send in<br>            let numbers = await apiClient.numbers()<br>            send(.didFinishFetching(numbers))<br>        }<br><br>    case .didFinishFetching(let numbers):<br>        state = .loaded(numbers)<br>        return .none<br>    }<br>}</pre><p>This is how <a href="https://github.com/pointfreeco/swift-composable-architecture">Pointfree’s TCA library</a> works.</p><ul><li>Another option is to return a value representing the effect that will be interpreted later (by an effects handler).</li></ul><pre>func handle(event: Event, state: inout State) -&gt; [Effect] {<br>    switch event {<br>    case .onAppear:<br>        state = .loading<br>        return [.downloadNumbers]<br><br>    case .didFinishFetching(let numbers):<br>        state = .loaded(numbers)<br>        return .none<br>    }<br>}<br><br>class EffectHandler {<br>    func handle(effect: Effect) async {<br>        switch effect {<br>        case .downloadNumbers:<br>            let numbers = await apiClient.numbers()<br>            send(.didFinishFetching(numbers))<br>        }<br>    }<br>}</pre><p>This is how <a href="https://github.com/spotify/Mobius.swift">Spotify’s Mobius library</a> works, which, IIRC, it’s based on <a href="https://gist.github.com/andymatuschak/d5f0a8730ad601bcccae97e8398e25b2">Andy Matuschak&#39;s writing: <em>A composable pattern for pure state machines with effects</em></a>.</p><ul><li>The last option is to return a type conforming to a protocol that bundles the asynchronous work.</li></ul><pre>func handle(event: Event, state: inout State) -&gt; [Effect] {<br>    switch event {<br>    case .onAppear:<br>        state = .loading<br>        return [DownloadNumbers()]<br><br>    case .didFinishFetching(let numbers):<br>        state = .loaded(numbers)<br>        return .none<br>    }<br>}<br><br><br>class DownloadNumbers: Effect {<br>    func run() async {<br>        let numbers = await apiClient.numbers()<br>        send(.didFinishFetching(numbers))<br>    }<br>}</pre><p>This is how <a href="https://square.github.io/workflow/">Square’s Workflow architecture</a> works.</p><p>Each of these flavors has its pros and cons.</p><ul><li>The first approach has fewer “jumps” and it’s very easy to see the result of the effect and go to the feedback message .didFinishFetching. The downside is that, for complex effects, the reducer can end up big and complicated. But we can always extract that code into separate functions as needed.</li><li>The second approach has the advantage of being able to test the reducer very easily. We can exercise the function without needing to create dependencies or mock anything. The reducer becomes the “pure layer”, which is easy to test, while the “effects handler” becomes the “impure layer”, which we may not need to test, as it should be a <a href="https://martinfowler.com/bliki/HumbleObject.html">humble object</a>.</li><li>The third approach might have a more self-contained, cohesive package for a specific effect, bundling the dependencies needed without polluting the reducer with all the different effects dependencies.</li></ul><p>I don’t have strong opinions about the best option here, to be completely honest. The second approach is the one I’ve used the most, and it has worked well for me. But sometimes, those “jumps” between the reducer function and the effect handling could be inconvenient. I recommend putting both in the same file to jump between both parts easily.</p><p>And about testing with the second approach… while we can test the reducer to see the actual effects values returned by a particular message, <strong>that test might not be the best test considering that effects are implementation details </strong>(at least from the store’s public API point of view). The actual state container “observable API” we should assert against is the tuple (state, output). Changing how we handle our effects without modifying our observable API and behavior shouldn’t break our tests.</p><p>For those reasons, I guess the first option might be my preferred option and the one I will implement in the next section.</p><h4>Final implementation</h4><p>Let’s now build the complete implementation of the aforementioned architecture. Feel free to copy everything into a playground and play with the result.</p><p>As I mentioned, having a correct encapsulation of the messages is important and leads to a safer and cleaner API. That’s why we have three types of messages:</p><ul><li><strong>Input</strong>: .onAppear events for view stores or .downloadData commands for domain stores.</li><li><strong>Feedback</strong>: .didDownloadData event. These are private, sent from the effects, and cannot be sent directly from a store instance.</li><li><strong>Output</strong>: Fire and forget events when we need to signal information that should not persist, unlike the state.</li></ul><p>The Effect type has two responsibilities:</p><ul><li>Wrapping the asynchronous work, the effect itself.</li><li>Notifying of any output message.</li></ul><pre>struct Effect&lt;Feedback, Output&gt; {<br>    typealias Operation = (@Sendable @escaping (Feedback) async -&gt; Void) async -&gt; Void<br>    <br>    fileprivate let output: Output?<br>    fileprivate let operation: Operation<br><br>    init(<br>        output: Output?,<br>        operation: @escaping Operation<br>    ) {<br>        self.output = output<br>        self.operation = operation<br>    }<br>}<br><br>extension Effect {<br>    static var none: Self {<br>        return .init(output: nil) { _ in }<br>    }<br><br>    static func output(_ output: Output) -&gt; Self {<br>        return .init(output: output) { _ in }<br>    }<br><br>    static func run(operation: @escaping Operation) -&gt; Self {<br>        self.init(output: nil, operation: operation)<br>    }<br>}</pre><p>The reducer will be the one modifying the state given a specific message and returning the correct Effect type.</p><pre>@MainActor<br>protocol Reducer&lt;State, Input, Feedback, Output&gt;: AnyObject where State: Equatable {<br>    associatedtype State<br>    associatedtype Input<br>    associatedtype Feedback = Never<br>    associatedtype Output = Never<br><br>    func reduce(<br>        message: Message&lt;Input, Feedback&gt;,<br>        into state: inout State<br>    ) -&gt; Effect&lt;Feedback, Output&gt;<br>}</pre><p>With Message being just the sum type of the Input and Feedback messages.</p><pre>enum Message&lt;Input, Feedback&gt;: Sendable where Input: Sendable, Feedback: Sendable {<br>    case input(Input)<br>    case feedback(Feedback)<br>}</pre><p>And finally, the Store.</p><pre>import Combine<br><br>@MainActor<br>@dynamicMemberLookup<br>class Store&lt;State, Input, Feedback, Output&gt;: ObservableObject where State: Equatable, Input: Sendable, Feedback: Sendable {<br>    @Published<br>    private(set) var state: State<br><br>    private let reducer: any Reducer&lt;State, Input, Feedback, Output&gt;<br><br>    private let stateSubject: CurrentValueSubject&lt;State, Never&gt;<br>    lazy var stateStream = stateSubject.removeDuplicates().values<br><br>    private let outputSubject: PassthroughSubject&lt;Output, Never&gt; = .init()<br>    lazy var outputStream = outputSubject.values<br><br>    private var tasks: [Task&lt;Void, Never&gt;] = []<br><br>    deinit {<br>        for task in tasks {<br>            task.cancel()<br>        }<br>    }<br><br>    init(<br>        state: State,<br>        reducer: some Reducer&lt;State, Input, Feedback, Output&gt;<br>    ) {<br>        self.state = state<br>        self.reducer = reducer<br>        stateSubject = .init(state)<br>    }<br><br>    @discardableResult<br>    func send(_ message: Input) -&gt; Task&lt;Void, Never&gt; {<br>        let task = Task { await send(.input(message)) }<br>        tasks.append(task)<br>        return task<br>    }<br><br>    func send(_ message: Input) async {<br>        await send(.input(message))<br>    }<br><br>    private func send(_ message: Message&lt;Input, Feedback&gt;) async {<br>        guard !Task.isCancelled else { return }<br><br>        let effect = reducer.reduce(message: message, into: &amp;state)<br>        stateSubject.send(state)<br><br>        if let output = effect.output {<br>            outputSubject.send(output)<br>        }<br><br>        await effect.operation { [weak self] feedback in<br>            guard !Task.isCancelled else { return }<br><br>            await self?.send(.feedback(feedback))<br>        }<br>    }<br><br>    subscript&lt;Value&gt;(dynamicMember keypath: KeyPath&lt;State, Value&gt;) -&gt; Value {<br>        state[keyPath: keypath]<br>    }<br>}</pre><p>Let’s now build the previous NumbersViewModel example by using the new architecture.</p><pre>import Foundation<br><br>final class APIClient {<br>    func numbers() async throws -&gt; [Int] {<br>        try await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)<br>        return [1, 2, 3]<br>    }<br>}<br><br>final class NumbersViewReducer: Reducer {<br>    enum State: Equatable {<br>        case idle<br>        case loading<br>        case loaded([Int])<br>        case error<br>    }<br><br>    enum Input {<br>        case onAppear<br>    }<br><br>    enum Feedback {<br>        case numbersDownloaded(Result&lt;[Int], Error&gt;)<br>    }<br><br>    enum Output {<br>        case numbersDownloaded<br>    }<br><br>    private let apiClient = APIClient()<br><br>    func reduce(message: Message&lt;Input, Feedback&gt;, into state: inout State) -&gt; Effect&lt;Feedback, Output&gt; {<br>        switch message {<br>        case .input(.onAppear):<br>            state = .loading<br>            return .run { [weak self] send in<br>                guard let self else { return }<br><br>                do {<br>                    let numbers = try await apiClient.numbers()<br>                    await send(.numbersDownloaded(.success(numbers)))<br>                } catch {<br>                    await send(.numbersDownloaded(.failure(error)))<br>                }<br>            }<br><br>        case .feedback(.numbersDownloaded(.success(let values))):<br>            state = .loaded(values)<br>            return .output(.numbersDownloaded)<br><br>        case .feedback(.numbersDownloaded(.failure)):<br>            state = .error<br>            return .none<br>        }<br>    }<br>}<br><br>import SwiftUI<br><br>struct NumbersListView: View {<br>    @StateObject private var viewModel = Store(<br>        state: NumbersViewReducer.State.idle,<br>        reducer: NumbersViewReducer()<br>    )<br><br>    var body: some View {<br>        Group {<br>            switch viewModel.state {<br>            case .idle, .loading:<br>                Text(&quot;…&quot;)<br><br>            case .loaded(let values):<br>                List(values, id: \.self) { value in<br>                    Text(&quot;\(value)&quot;)<br>                }<br><br>            case .error:<br>                Text(&quot;Some error happened&quot;)<br>            }<br>        }.onAppear {<br>            viewModel.send(.onAppear)<br>        }<br>    }<br>}</pre><h3>Conclusion</h3><p>Thanks a lot for reaching the end of the article. It was a long (and hopefully interesting) read.</p><p>During these last years, we’ve had an explosion of state management libraries. We’ve seen how unidirectional architectures have taken over the Mobile space, especially alongside new UI declarative frameworks like SwiftUI (and Jetpack Compose on Android), where they perfectly fit.</p><p>But not only SwiftUI… async/await has also made a big impact on how we develop our apps and handle concurrency and side effects, which has impacted many of the new architectures that have emerged during these last years.</p><p>All the separation of state management and effects has emerged a lot of new functional paradigms within the iOS community, with many people already using TCA as their default architecture for any new iOS project with SwiftUI. This is great news and shows the maturity of the iOS ecosystem at this point.</p><p>While Swift will keep evolving, I don’t particularly foresee any major changes in the coming years that will dramatically change how we develop applications, or at least in a way that can impact state management libraries so drastically, as has happened recently.</p><p>Well… Maybe “Swift Data” to replace Core Data… we’ll see in a few months at <a href="https://developer.apple.com/wwdc23/">WWDC23</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=781a01380ef6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/different-flavors-of-unidirectional-architectures-in-swift-781a01380ef6">The Many Flavors of Unidirectional Architectures in Swift</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Parsing in Swift: a DTO-based Approach]]></title>
            <link>https://medium.com/better-programming/parsing-in-swift-a-dto-based-approach-5edca55eb57a?source=rss-c3e9dcf69985------2</link>
            <guid isPermaLink="false">https://medium.com/p/5edca55eb57a</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Luis Recuenco]]></dc:creator>
            <pubDate>Tue, 31 Jan 2023 14:53:06 GMT</pubDate>
            <atom:updated>2023-02-03T17:13:51.513Z</atom:updated>
            <content:encoded><![CDATA[<h4>Also, a comparison with the Codable approach</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ga-lAgQuMEldWj-m" /><figcaption>Photo by <a href="https://unsplash.com/@thomas_ashlock?utm_source=medium&amp;utm_medium=referral">Thomas Ashlock</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Turning JSON into actual Swift types is something most apps have to deal with eventually. At the beginning of Swift, Apple was not opinionated about how we should approach that task. So people came up with a bunch of <a href="https://github.com/JohnSundell/Unbox">different</a> <a href="https://github.com/tristanhimmelman/ObjectMapper">libraries</a> to do so. Some of them are <a href="https://github.com/thoughtbot/Argo">quite esoteric</a>. Fortunately, Apple came up with its own first-party solution, <a href="https://developer.apple.com/documentation/swift/codable#">Codable</a>.</p><p>Turning JSON into Swift types can be as easy as conforming those types to the Codable protocol. You might not need to do anything else. The compiler will do its magic and synthesize the appropriate parsing code for you. That’s lovely. There’s no better code than the one you don’t have to write. Sometimes, the “parsing code” you have to write is minimal, providing a CodingKeys enum with the appropriate mapping between your type property names and the JSON keys.</p><p>Unfortunately, that’s where the magic ends. The honeymoon with Codable tends to end pretty soon. The more complex, statically safe, and correct our types are, the more impedance we have between the JSON and our types, and the more likely it is that we’ll end up writing quite a lot of complex parsing code inside init(from decoder:) throws.</p><p>I still remember my days writing Objective-C. Swift has definitely made me a better developer. And one of the main reasons is that <a href="https://www.linuxtopia.org/online_books/programming_books/art_of_unix_programming/ch01s06_8.html">I now tend to think much more deeply about my data, trying to encapsulate all the correct semantics and invariants into that domain model. I tend to think about data first</a>. Objective-C lacked a lot in that regard. Luckily, Swift has all the proper primitives to model our domain correctly: optionals, generics, powerful enums, values types…</p><p>While it can be tempting to couple our models to the JSON shape and let Codable do its magic to avoid boilerplate, I do think we should think about the correct data first, and then interpret the JSON correctly via the (JSON) -&gt; Model function. The inherently dynamic nature of JSON leaking into our domain model will likely make it “worse”.</p><h3>The JSON -&gt; Model function</h3><p>I tend to see this function as a kind of anticorruption layer. The sooner we validate the JSON and turn it into the correct types inside our domain, the better. That validation can vary greatly depending on how much control we have over the API.</p><ul><li>On one side of the spectrum, we have a specifically-tuned API via a Backend for Frontend, where the schema contract has been thoroughly thought out and agreed upon amongst Mobile and API developers. Both API and the Mobile app will evolve hand in hand.</li><li>On the other side, we need to communicate with a third-party API we don’t control.</li></ul><p>On either side of the spectrum, the custom parsing/validation code we end up writing is very different. The third-party API will likely require more “validation” to fit our model.</p><p>We have two places to put that parsing code:</p><ol><li>Use Codable and implement init(from decoder:) throws.</li><li>Use some kind of intermediate “data layer” model (let’s call this DTO — <a href="https://en.wikipedia.org/wiki/Data_transfer_object">Data Transfer Object</a>) and have an extra function (DTO) -&gt; Model throws for that transformation.</li></ol><p>I guess the word DTO doesn’t feel as idiomatic in Swift as it might be in other languages like Java or C#. And while I agree that the idiomatic way to approach parsing in Swift is to go the Codable path, it is good sometimes to consider other alternatives and see if they solve the problem better (or worse).</p><p>This article doesn’t try to convince anyone that the DTO approach is a better one (in fact, I don’t think it is for a lot of cases). It only tries to show a “different approach”. A new tool and alternative to that parsing code we are writing inside init(from decoder:) throws. Sometimes, it might simplify things. Some other times, it may not.</p><p>My humble opinion is that writing custom Codable code is not only boilerplate, but also hard. There are quite a few new “idioms” to learn about keyed containers, unkeyed ones, coding keys, etc.</p><p>Any time we have to deal with some “special” case (such as not failing if an element of an array fails to decode, or unknown cases for enums), we end up having to implement init(from decoder:) throws and writing quite a gnarly parsing code.</p><p>While some of that pain can be eased by using <a href="https://github.com/airbnb/ResilientDecoding">property wrappers</a>, the complexity is still there. We only moved it (even coupling ourselves to a third-party library along the way).</p><h3>A Richer Domain</h3><p>Let’s suppose we are trying to transform the following JSON into the correct type.</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;Luis&quot;,<br>    &quot;twitter&quot;: &quot;@luisrecuenco&quot;<br>}<br>&quot;&quot;&quot;</pre><p>Our first approach could be as simple as this:</p><pre>struct Person: Decodable {<br>    var name: String<br>    var twitter: String<br>}</pre><p>We can even have twitter: String? and be a little more flexible if the twitter key is not always present.</p><p>But wouldn’t it be great to have a Twitter type?</p><p>First, to validate the username handler correctly and second, to encapsulate all future business logic related to Twitter (I wrote a lot more about this and refinement types in general in a previous article called <a href="https://jobandtalent.engineering/value-integrity-in-swift-c5bf2b3f8340">Value integrity in Swift</a>). Let’s change the code to do that.</p><pre>struct Person: Decodable {<br>    var name: String<br>    var twitter: Twitter<br>}<br><br>struct Twitter: Decodable {<br>    var handler: String<br>}</pre><p>This doesn’t work, as expected. The Twitter type expects to be decoded by a keyed container with the handler key, but we get a single-value container.</p><p>Fortunately, the solution is as easy as making the Twitter type conform to RawRepresentable. Let’s do that.</p><pre>struct Twitter: RawRepresentable, Decodable {<br>    var rawValue: String<br><br>    init?(rawValue: String) {<br>        self.rawValue = rawValue<br>    }<br>}</pre><p>This is “ok”. We are forced to couple our Twitter type with RawRepresentable, so we are forced to use a specific property name, expose rawValue with the same visibility as the Twitter type, etc.</p><p>Let’s now try to use validation. If we receive a Twitter handler without the @, we fail.</p><pre>struct Person: Decodable {<br>    var name: String<br>    var twitter: Twitter?<br>}<br><br>struct Twitter: RawRepresentable, Decodable {<br>    var rawValue: String<br><br>    init?(rawValue: String) {<br>        guard rawValue.first == &quot;@&quot; else { return nil }<br>        self.rawValue = rawValue<br>    }<br>}</pre><p>This code works as long as:</p><ul><li>We receive a correct Twitter handler with @.</li><li>We don’t receive any Twitter on the JSON.</li></ul><p>But, as soon as we receive an invalid Twitter handler (so the validation fails), the whole decoding process fails as well. This forces us to implement the parsing code manually:</p><pre>init(from decoder: Decoder) throws {<br>    let container = try decoder.container(keyedBy: CodingKeys.self)<br>    name = try container.decode(String.self, forKey: .name)<br>    twitter = try container.decodeIfPresent(String.self, forKey: .twitter).flatMap(Twitter.init)<br>}</pre><p>This kind of process happens a lot with Codable.</p><p>Coupling our domain with Codable damages refactoring. It’s very likely that any refactor in our domain breaks the underlying decoding without the compiler telling anything (having to rely on tests to catch those issues).</p><p>Those new types, as long as they conform to Decodable, will make the compiler synthesize “new decoding magic”, which will likely crash at runtime.</p><p>While implementing init(from decoder:) throws fixes that issue (the compiler will now force all properties to be correctly set), sometimes it’s better to have a proper separation between “the schema types” and the “domain types”.</p><p>Let’s now take a look at another alternative, where Person.DTO is our “schema type”, matching the JSON, whereas Person and Twitter are plain structs.</p><pre>struct Person {<br>    var name: String<br>    var twitter: Twitter?<br>}<br><br>struct Twitter {<br>    var handler: String<br><br>    init?(handler: String) {<br>        guard handler.first == &quot;@&quot; else { return nil }<br>        self.handler = handler<br>    }<br>}<br><br>extension Person {<br>    struct DTO: Decodable {<br>        var name: String<br>        var twitter: String?<br>    }<br><br>    init(from dto: DTO) {<br>        name = dto.name<br>        twitter = dto.twitter.flatMap(Twitter.init)<br>    }<br>}</pre><p>When we change the shape of our domain, the compiler will force us to update the init(from dto:) function, which is exactly what we want. As long as Person.DTO structure doesn’t change (keeping the JSON schema contract), we are good, and the compiler will help with refactoring.</p><p>But using a DTO is not always better. As always, it depends. So let’s analyze both approaches a little bit more thoroughly.</p><h3>Comparing the Codable vs. DTO approaches</h3><p>The other day I was watching <a href="https://www.youtube.com/watch?v=LoRhAEf050E">this talk</a> by <a href="https://mastodon.xyz/@bens">Ben Scheirman</a>. It’s a great talk, you should watch it. In that talk, Ben presents three different use cases, and we’ll use those to see how the DTO approach compares.</p><h4>1. Nested JSON to flatten type</h4><p>The goal is to turn the following JSON payload into the Beer type.</p><pre>{<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery&quot;: {<br>        &quot;id&quot;: &quot;sa001&quot;,<br>        &quot;name&quot;: &quot;Saint arnold&quot;<br>    }<br>}<br><br>struct Beer {<br>    var id: Int<br>    var name: String<br>    var breweryId: String<br>    var breweryName: String<br>}</pre><p>First, let’s have the Codable approach (the one shown in <a href="https://www.youtube.com/watch?v=LoRhAEf050E">the talk</a>):</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery&quot;: {<br>        &quot;id&quot;: &quot;sa001&quot;,<br>        &quot;name&quot;: &quot;Saint arnold&quot;<br>    }<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>struct Beer: Decodable {<br>    var id: Int<br>    var name: String<br>    var breweryId: String<br>    var breweryName: String<br><br>    enum CodingKeys: String, CodingKey {<br>        case id<br>        case name<br>        case brewery<br>    }<br><br>    enum BreweryCodingKeys: String, CodingKey {<br>        case id<br>        case name<br>    }<br><br>    init(from decoder: Decoder) throws {<br>        let container = try decoder.container(keyedBy: CodingKeys.self)<br>        id = try container.decode(Int.self, forKey: .id)<br>        name = try container.decode(String.self, forKey: .name)<br><br>        let breweryContainer = try container.nestedContainer(keyedBy: BreweryCodingKeys.self, forKey: .brewery)<br>        breweryId = try breweryContainer.decode(String.self, forKey: .id)<br>        breweryName = try breweryContainer.decode(String.self, forKey: .name)<br>    }<br>}<br><br>let beer = try! JSONDecoder().decode(Beer.self, from: json)<br>dump(beer)</pre><p>Before digging into the DTO approach, let’s have some minimal infrastructure code to simplify things. We’ll define what an object that can be decoded from a DTO looks like and how to decode it easily.</p><pre>protocol DecodableFromDTO {<br>    associatedtype DTO: Decodable<br>    init(from dto: DTO) throws<br>}<br><br>extension JSONDecoder {<br>    func decode&lt;T&gt;(_ type: T.Type, from data: Data) throws -&gt; T where T : DecodableFromDTO {<br>        try T(from: decode(T.DTO.self, from: data))<br>    }<br>}</pre><p>With that in place, we can have this:</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery&quot;: {<br>        &quot;id&quot;: &quot;sa001&quot;,<br>        &quot;name&quot;: &quot;Saint arnold&quot;<br>    }<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>struct Beer {<br>    var id: Int<br>    var name: String<br>    var breweryId: String<br>    var breweryName: String<br>}<br><br>extension Beer: DecodableFromDTO {<br>    struct DTO: Decodable {<br>        struct Brewery: Decodable {<br>            var id: String<br>            var name: String<br>        }<br>        var id: Int<br>        var name: String<br>        var brewery: Brewery<br>    }<br><br>    init(from dto: DTO) throws {<br>        id = dto.id<br>        name = dto.name<br>        breweryId = dto.brewery.id<br>        breweryName = dto.brewery.name<br>    }<br>}<br><br>let beer = try! JSONDecoder().decode(Beer.self, from: json)<br>dump(beer)</pre><p>I guess both approaches are quite similar. Let’s first analyze the Signal-to-noise ratio, understanding noise as all the parsing lines of code.</p><ul><li>Codable SNR = 41/20 ~ 48% of noise</li><li>DTO SNR = 39/18 ~ 46% of noise</li></ul><p>As we can see, the noise we get is quite similar.</p><p>Another interesting thing to consider is the impact of changes. How many places do we have to update for any change in the JSON/model?</p><ul><li>Codable: update CodingKeys and init(from decoder:) throws.</li><li>DTO: update DTO struct and init(from dto:) throws.</li></ul><p>Again, similar results. We can interpret this in two ways:</p><ul><li>The DTO approach is not worth it, as we are not really getting many advantages from it.</li><li>The DTO approach, which usually is considered as adding more boilerplate, extra DTO type, etc, is not really much worse than the Codable approach.</li></ul><p>Deciding which code is simpler is quite subjective (as many things in programming, unfortunately). Some people might write parsing code inside init(from decoder:) throws very naturally, and Codable containers are second-nature for them.</p><p>Some other people might find the extra DTO + (DTO) -&gt; Model function as a simpler approach. I tend to favor the DTO approach slightly in this case. As I mentioned previously, having the schema types decoupled from my domain types allows me to evolve my domain more safely and rapidly.</p><h4>2. Flat JSON to nested type</h4><p>The goal is to turn the following JSON payload into the Beer and Brewery types:</p><pre>{<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery_id&quot;: &quot;sa001&quot;,<br>    &quot;brewery_name&quot;: &quot;Saint Arnold&quot;<br>}<br><br>struct Beer {<br>    var id: Int<br>    var name: String<br>    var brewery: Brewery<br>}<br><br>struct Brewery {<br>    var id: String<br>    var name: String<br>}</pre><p>As before, let’s go with the Codable approach first:</p><pre>let json = &quot;&quot;&quot;<br> {<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery_id&quot;: &quot;sa001&quot;,<br>    &quot;brewery_name&quot;: &quot;Saint Arnold&quot;<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>struct Beer: Decodable {<br>    var id: Int<br>    var name: String<br>    var brewery: Brewery<br><br>    enum CodingKeys: String, CodingKey {<br>        case id<br>        case name<br>    }<br><br>    init(from decoder: Decoder) throws {<br>        let container = try decoder.container(keyedBy: CodingKeys.self)<br>        id = try container.decode(Int.self, forKey: .id)<br>        name = try container.decode(String.self, forKey: .name)<br>        brewery = try Brewery(from: decoder)<br>    }<br>}<br><br>struct Brewery: Decodable {<br>    var id: String<br>    var name: String<br><br>    enum CodingKeys: String, CodingKey {<br>        case id = &quot;brewery_id&quot;<br>        case name = &quot;brewery_name&quot;<br>    }<br>}<br><br>let beer = try! JSONDecoder().decode(Beer.self, from: json)<br>dump(beer)</pre><p>Now, the DTO-based approach:</p><pre>let json = &quot;&quot;&quot;<br> {<br>    &quot;id&quot;: 123,<br>    &quot;name&quot;: &quot;Endeavor&quot;,<br>    &quot;brewery_id&quot;: &quot;sa001&quot;,<br>    &quot;brewery_name&quot;: &quot;Saint Arnold&quot;<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>struct Beer {<br>    var id: Int<br>    var name: String<br>    var brewery: Brewery<br>}<br><br>struct Brewery {<br>    var id: String<br>    var name: String<br>}<br><br>extension Beer: DecodableFromDTO {<br>    struct DTO: Decodable {<br>        var id: Int<br>        var name: String<br>        var brewery_id: String<br>        var brewery_name: String<br>    }<br><br>    init(from dto: DTO) {<br>        id = dto.id<br>        name = dto.name<br>        brewery = Brewery(id: dto.brewery_id, name: dto.brewery_name)<br>    }<br>}<br><br>let beer = try! JSONDecoder().decode(Beer.self, from: json)<br>dump(beer)</pre><p>SNR:</p><ul><li>Codable SNR = 39/16 ~ 41% of noise</li><li>DTO SNR = 37/14 ~ 37% of noise</li></ul><p>Impact of changes:</p><ul><li>Codable: update Beer‘s CodingKeys and init(from decoder:) throws. Update Brewery‘s CodingKeys and maybe, implement init(from decoder:) throws there.</li><li>DTO: update DTO struct and init(from dto:) throws.</li></ul><p>The DTO approach seems slightly better, especially because the changes we’d have to make in case of JSON/model updates are a bit better scoped and less scattered. But the advantages are minor, really.</p><h4>3. Heterogeneous arrays</h4><p>Let’s now have a much more interesting example. The goal is to turn the following JSON payload into the Feed type.</p><pre>{<br>    &quot;items&quot;: [<br>        {<br>            &quot;type&quot;: &quot;text&quot;,<br>            &quot;id&quot;: 55,<br>            &quot;text&quot;: &quot;This is a text feed item&quot;<br>        },<br>        {<br>            &quot;type&quot;: &quot;image&quot;,<br>            &quot;id&quot;: 56,<br>            &quot;image_url&quot;: &quot;http://placekitten.com/200/300&quot;<br>        }<br>    ]<br>}<br><br>struct Feed {<br>    let items: [FeedItem]<br>}<br><br>class FeedItem {<br>    let id: Int<br>}<br><br>class TextFeedItem: FeedItem {<br>    let text: String<br>}<br><br>class ImageFeedItem: FeedItem {<br>    let imageUrl: URL<br>}</pre><p>Let’s go with the Codable approach first.</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;items&quot;: [<br>        {<br>            &quot;type&quot;: &quot;text&quot;,<br>            &quot;id&quot;: 55,<br>            &quot;text&quot;: &quot;This is a text feed item&quot;<br>        },<br>        {<br>            &quot;type&quot;: &quot;image&quot;,<br>            &quot;id&quot;: 56,<br>            &quot;image_url&quot;: &quot;http://placekitten.com/200/300&quot;<br>        }<br>    ]<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>struct AnyCodingKey: CodingKey {<br>    let stringValue: String<br><br>    init?(stringValue: String) {<br>        self.stringValue = stringValue<br>    }<br><br>    var intValue: Int?<br><br>    init?(intValue: Int) {<br>        fatalError()<br>    }<br>}<br><br>extension AnyCodingKey: ExpressibleByStringLiteral {<br>    init(stringLiteral value: StringLiteralType) {<br>        self.init(stringValue: value)!<br>    }<br>}<br><br>class FeedItem: Decodable {<br>    let id: Int<br><br>    init(id: Int) {<br>        self.id = id<br>    }<br>}<br><br>class TextFeedItem: FeedItem {<br>    let text: String<br><br>    init(text: String, id: Int) {<br>        self.text = text<br>        super.init(id: id)<br>    }<br><br>    enum CodingKeys: String, CodingKey {<br>        case text<br>    }<br><br>    required init(from decoder: Decoder) throws {<br>        let container = try decoder.container(keyedBy: CodingKeys.self)<br>        text = try container.decode(String.self, forKey: .text)<br>        try super.init(from: decoder)<br>    }<br>}<br><br>class ImageFeedItem: FeedItem {<br>    let url: URL<br><br>    init(url: URL, id: Int) {<br>        self.url = url<br>        super.init(id: id)<br>    }<br><br>    enum CodingKeys: String, CodingKey {<br>        case imageUrl = &quot;image_url&quot;<br>    }<br><br>    required init(from decoder: Decoder) throws {<br>        let container = try decoder.container(keyedBy: CodingKeys.self)<br>        url = try container.decode(URL.self, forKey: .imageUrl)<br>        try super.init(from: decoder)<br>    }<br>}<br><br>struct Feed: Decodable {<br>    let items: [FeedItem]<br><br>    enum CodingKeys: String, CodingKey {<br>        case items<br>    }<br><br>    init(from decoder: Decoder) throws {<br>        let container = try decoder.container(keyedBy: CodingKeys.self)<br>        var itemsContainer = try container.nestedUnkeyedContainer(forKey: .items)<br>        var itemsContainerCopy = itemsContainer<br>        var items: [FeedItem] = []<br>        while !itemsContainer.isAtEnd {<br>            let typeContainer = try itemsContainer.nestedContainer(keyedBy: AnyCodingKey.self)<br>            let type = try typeContainer.decode(String.self, forKey: &quot;type&quot;)<br>            let feedItem: FeedItem<br>            switch type {<br>            case &quot;text&quot;:<br>                feedItem = try itemsContainerCopy.decode(TextFeedItem.self)<br>            case &quot;image&quot;:<br>                feedItem = try itemsContainerCopy.decode(ImageFeedItem.self)<br>            default:<br>                fatalError()<br>            }<br>            items.append(feedItem)<br>        }<br>        self.items = items<br>    }<br>}<br><br>let feed = try! JSONDecoder().decode(Feed.self, from: json)<br>dump(feed)</pre><p>In this case, the DTO approach is trickier. In the previous examples, the JSON schema contract was clear, and we could create the correct DTO type that matched that schema.</p><p>But now, the schema depends on the type key. If &quot;type&quot; = &quot;text&quot;, we get a different payload than when &quot;type&quot; = &quot;image&quot;. To fit this dynamic nature into our DTO-based approach, we are going to leverage a JSON type I implemented in a previous article called <a href="https://medium.com/jobandtalenteng/statically-typed-json-payload-in-swift-bd193a9e8cf2">Statically-typed JSON payload in Swift</a>.</p><pre>enum JSON: Codable {<br>    case int(Int)<br>    case string(String)<br>    case bool(Bool)<br>    case double(Double)<br>    case array([JSON])<br>    case dictionary([String: JSON])<br><br>    func encode(to encoder: Encoder) throws {<br>        var container = encoder.singleValueContainer()<br>        switch self {<br>        case .int(let int): try container.encode(int)<br>        case .double(let double): try container.encode(double)<br>        case .string(let string): try container.encode(string)<br>        case .bool(let bool): try container.encode(bool)<br>        case .array(let array): try container.encode(array)<br>        case .dictionary(let dictionary): try container.encode(dictionary)<br>        }<br>    }<br><br>    init(from decoder: Decoder) throws {<br>        var container = try decoder.singleValueContainer()<br>        do { self = .int(try container.decode(Int.self)) } catch {<br>            do { self = .string(try container.decode(String.self)) } catch {<br>                do { self = .bool(try container.decode(Bool.self)) } catch {<br>                    do { self = .double(try container.decode(Double.self)) } catch {<br>                        do { self = .array(try container.decode([JSON].self)) } catch {<br>                            self = .dictionary(try container.decode([String: JSON].self))<br>                        }<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}<br><br>extension JSON {<br>    func decode&lt;T&gt;(_ type: T.Type) throws -&gt; T where T: Decodable {<br>        try JSONDecoder().decode(type, from: JSONEncoder().encode(self))<br>    }<br>}</pre><p>With that JSON type in place, let’s implement our full DTO-based parsing.</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;items&quot;: [<br>        {<br>            &quot;type&quot;: &quot;text&quot;,<br>            &quot;id&quot;: 55,<br>            &quot;text&quot;: &quot;This is a text feed item&quot;<br>        },<br>        {<br>            &quot;type&quot;: &quot;image&quot;,<br>            &quot;id&quot;: 56,<br>            &quot;image_url&quot;: &quot;http://placekitten.com/200/300&quot;<br>        }<br>    ]<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>class FeedItem {<br>    let id: Int<br><br>    init(id: Int) {<br>        self.id = id<br>    }<br>}<br><br>class TextFeedItem: FeedItem {<br>    let text: String<br><br>    init(text: String, id: Int) {<br>        self.text = text<br>        super.init(id: id)<br>    }<br>}<br><br>class ImageFeedItem: FeedItem {<br>    let url: URL<br><br>    init(url: URL, id: Int) {<br>        self.url = url<br>        super.init(id: id)<br>    }<br>}<br><br>struct Feed {<br>    let items: [FeedItem]<br>}<br><br>extension Feed: DecodableFromDTO {<br>    struct DTO: Decodable {<br>        var items: [JSON]<br><br>        struct Text: Decodable {<br>            var id: Int<br>            var text: String<br>        }<br><br>        struct Image: Decodable {<br>            var id: Int<br>            var image_url: URL<br>        }<br><br>        struct DecodingError: Error {}<br>    }<br><br>    init(from dto: DTO) throws {<br>        items = try dto.items.map { json in<br>            guard case .dictionary(let item) = json, case .string(let type) = item[&quot;type&quot;] else { throw DTO.DecodingError() }<br>            switch type {<br>            case &quot;text&quot;:<br>                let textDTO = try json.decode(DTO.Text.self)<br>                return TextFeedItem(text: textDTO.text, id: textDTO.id)<br><br>            case &quot;image&quot;:<br>                let imageDTO = try json.decode(DTO.Image.self)<br>                return ImageFeedItem(url: imageDTO.image_url, id: imageDTO.id)<br><br>            default:<br>                throw DTO.DecodingError()<br>            }<br>        }<br>    }<br>}<br><br>let feed = try! JSONDecoder().decode(Feed.self, from: json)<br>dump(feed)</pre><p>SNR:</p><ul><li>Codable SNR = 95/45 ~ 47% of noise.</li><li>DTO SNR = 85/35 ~ 41% of noise.</li></ul><p>Impact of changes:</p><ul><li>Codable: update TextFeedItem, ImageFeedItem, and Feed’s CodingKeys and init(from decoder:) throws (and maybe FeedItem as well).</li><li>DTO: the JSON type adapts to any kind of JSON shape, so we only have to update Feed.DTO.Text, Feed.DTO.Image and init(from dto:) throws.</li></ul><p>As before, it seems that the impact of changes is better scoped in the DTO version. But in this case, the main advantage to me is that the code inside init(from dto:) throws is much simpler than the code inside init(from decoder:) throws.</p><h3>Mix and match</h3><p>In case we need to combine DecodableFromDTO objects with Decodable objects, we can use the DecodeFromDTO property wrapper for that.</p><pre>let json = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;Luis&quot;,<br>    &quot;beer&quot;: {<br>        &quot;id&quot;: 123,<br>        &quot;name&quot;: &quot;Endeavor&quot;,<br>        &quot;brewery&quot;: {<br>            &quot;id&quot;: &quot;sa001&quot;,<br>            &quot;name&quot;: &quot;Saint arnold&quot;<br>        }<br>    }<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>@propertyWrapper<br>struct DecodeFromDTO&lt;T&gt;: Decodable where T: DecodableFromDTO {<br>    var wrappedValue: T<br><br>    init(from decoder: Decoder) throws {<br>        wrappedValue = try T(from: T.DTO(from: decoder))<br>    }<br>}<br><br>struct Person: Decodable {<br>    var name: String<br>    @DecodeFromDTO var beer: Beer<br>}<br><br>let person = try! JSONDecoder().decode(Person.self, from: json)<br>dump(person)</pre><h3>Conclusion</h3><p>While neither the SNR nor the impact of changes have been objectively better or different in any of the approaches, I still think separating the schema types from the domain types is a good practice that scales better, leads to richer types, and facilitates refactoring.</p><p>Also, the declarative nature of the DTO approach, with its structure matching the JSON schema (which could be automated by tools like <a href="https://quicktype.io">Quicktype</a>), feels much nicer to me than a lot of complex imperative parsing code. But at the end of the day, this is just a tool, and we should use the right tool for the job.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5edca55eb57a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/parsing-in-swift-a-dto-based-approach-5edca55eb57a">Parsing in Swift: a DTO-based Approach</a> was originally published in <a href="https://betterprogramming.pub">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>