<?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"><channel><title><![CDATA[Vitaly Batrakov]]></title><description><![CDATA[Vitaly Batrakov]]></description><link>https://vbat.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 23:28:04 GMT</lastBuildDate><atom:link href="https://vbat.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Rules of Structured Concurrency]]></title><description><![CDATA[You can find video version of this article here.

In the previous article, we covered the key aspects of Swift Concurrency to build a solid mental model of it. In this article, we will discuss structured and unstructured tasks. You’ll see that their ...]]></description><link>https://vbat.dev/rules-of-structured-concurrency</link><guid isPermaLink="true">https://vbat.dev/rules-of-structured-concurrency</guid><category><![CDATA[structured concurrency]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 06 Apr 2025 17:34:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743868544744/22586341-87f0-433d-bcbc-8292bddcaf3e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong><em>You can find video version of this article</em></strong> <a target="_blank" href="https://youtu.be/Ys0vQtXjIOI"><strong><em>here.</em></strong></a></p>
</blockquote>
<p>In the previous <a target="_blank" href="https://vbat.dev/behind-the-scenes-of-async-functions">article</a>, we covered the key aspects of Swift Concurrency to build a solid mental model of it. In this article, we will discuss structured and unstructured tasks. You’ll see that their behavior can be tricky in some scenarios, but we’ll break it down into rules to make it clear.</p>
<blockquote>
<p>⚠️ : I assume you are familiar with the basic concepts of <strong>Structured Concurrency</strong> like <strong>async let</strong> and <strong>task group.</strong> If not, you can start <a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/">here</a>.</p>
</blockquote>
<h1 id="heading-structured-and-unstructured-tasks"><strong>Structured and Unstructured Tasks</strong></h1>
<p>From previous article, we know that to switch gears from sync context to async context we need to use tasks. However, that is not the only use case for tasks. Tasks are, in fact, a much broader concept in Swift Concurrency.</p>
<blockquote>
<p>💡: <a target="_blank" href="https://developer.apple.com/documentation/swift/task">Tasks</a> are <strong>units of asynchronous work</strong> you can run from your code.</p>
<ul>
<li><p><strong>Every async function is executing in a task</strong>. In other words, a <em>task</em> is to <em>asynchronous functions</em>, what a <em>thread</em> is to <em>synchronous functions.</em></p>
</li>
<li><p>A task runs one function at a time; a single task has no concurrency.</p>
</li>
<li><p>Synchronous functions do not necessarily run as part of a task, but they can.</p>
</li>
</ul>
</blockquote>
<p>In terms of Structured Concurrency, there are 2 kinds of tasks:</p>
<ul>
<li><p><strong>Structured Concurrency Tasks</strong> (we will call them simply Structured Tasks)</p>
<ul>
<li>Represented by <strong>async let</strong> and <strong>task group</strong></li>
</ul>
</li>
<li><p><strong>Unstructured Concurrency Tasks</strong> (we will call them simply Unstructured Tasks)</p>
<ul>
<li>Represented by <strong>regular unstructured task</strong> (<strong>Task { … })</strong> and <strong>detached task</strong> (<strong>Task.detached { … })</strong></li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>⁉️ : What is the difference between Structured and Unstructured tasks?</strong><br />Why they are actually called “structured” and “unstructured”? What does it mean? To understand the difference we need to understand the parent and child relation for tasks.</p>
</blockquote>
<h1 id="heading-parent-and-child-tasks"><strong>Parent and Child Tasks</strong></h1>
<ul>
<li><p><strong>Parent Task is</strong> a task that spawns one or more subtasks (child tasks). Parent task is responsible for managing its child tasks.</p>
</li>
<li><p><strong>Child Task</strong> is a subtask created within the context of a parent task. Child tasks return their results (if any) to the parent task (if explicitly awaited).</p>
</li>
</ul>
<p>Parent and child tasks together form a <strong>task tree structure</strong>, that simplifies control over related tasks completion, cancellation and resource management.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743868803871/d21551a2-5365-4f27-a7de-a7f0ceac769c.png" alt class="image--center mx-auto" /></p>
<p><strong>Root Task</strong> is an <strong>initial</strong> task in task tree (root node), which can be <strong>parent</strong> if it spawns child tasks.</p>
<ul>
<li><p><strong>Structured task</strong> can be a <strong>child or parent task (or both). But it can’t be root node in the task tree.</strong></p>
</li>
<li><p><strong>Unstructured task</strong> <strong>can’t be a child task</strong>. But it <strong>can be a parent task</strong> if you create new child <strong>structured</strong> tasks from them. In other words, <strong>unstructured task can only be a root node in task tree structure.</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743868828960/51807485-7f39-4545-b62d-df2984df28d0.png" alt class="image--center mx-auto" /></p>
<p>That's why they are called "structured" and "unstructured" tasks:</p>
<ul>
<li><p><strong>Structured tasks</strong> always join the current task tree structure as child tasks.</p>
</li>
<li><p><strong>Unstructured tasks</strong> never join the current task tree. They start a new, independent task tree as a root node with no parent-child relation to the original task, with child tasks created later if needed.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743868858942/b74687fb-e270-41b0-b8be-92bcafea5308.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>⚠️ : Although we can nest one unstructured task within another, don’t be misled by that - it doesn’t create a parent-child relationship between them, regardless of whether the tasks are regular or detached. Later, we will demonstrate through examples that the behavior defined for a task tree hierarchy is fundamentally different for nested unstructured tasks.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743868885849/c3bcccd7-3a8c-4048-a709-f4d030b1b749.png" alt class="image--center mx-auto" /></p>
</blockquote>
<h2 id="heading-structured-tasks"><strong>Structured tasks</strong></h2>
<p>Structured tasks in Swift are represented by <strong>async let</strong> and <strong>task group.</strong></p>
<p>Although it may not be obvious with <strong>async let</strong>, both of them create child tasks within the parent task where they are used. The child tasks created by <strong>async let</strong> and <strong>task group</strong> are designed to be executed concurrently.</p>
<h3 id="heading-async-let"><strong>async let</strong></h3>
<p>With <code>async let</code>, you can start multiple tasks that <strong>run concurrently.</strong></p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchData</span><span class="hljs-params">()</span></span> async {
    async <span class="hljs-keyword">let</span> first = fetchPart1()
    async <span class="hljs-keyword">let</span> second = fetchPart2()
    async <span class="hljs-keyword">let</span> third = fetchPart3()

    <span class="hljs-keyword">let</span> result = await (first, second, third)

    <span class="hljs-built_in">print</span>(result)
}
</code></pre>
<p>Although it is not explicitly stated in code, three new child tasks are created here, one for each <strong>async let.</strong></p>
<p>The <strong>lifecycle</strong> of <code>async let</code> is bound to the <strong>local scope</strong> where it is created, such as a function, closure, or <code>do/catch</code> block.</p>
<h3 id="heading-taskgroup">TaskGroup</h3>
<p>If you need to create a <strong>dynamic</strong> number of concurrent tasks, a <strong>task group</strong> is the better choice:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchData</span><span class="hljs-params">(<span class="hljs-built_in">count</span>: Int)</span></span> async {
    <span class="hljs-keyword">var</span> results = [<span class="hljs-type">String</span>]()

    await withTaskGroup(of: <span class="hljs-type">String</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">for</span> index <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..&lt;<span class="hljs-built_in">count</span> {
            group.addTask {
                await fetchPart(index)
            }
        }
        <span class="hljs-keyword">for</span> await result <span class="hljs-keyword">in</span> group {
            results.append(result)
        }
    }

    <span class="hljs-built_in">print</span>(results)
}
</code></pre>
<p>Important difference with <code>async let</code> is the order of awaiting results. Unlike <code>async let</code>, where the order is code-driven, task group follows a "<strong>first finished → first handled</strong>" approach, this is how an <code>AsyncSequence</code> works.</p>
<p>Lifecycle of <strong>task group</strong> is bound to the closure inside <code>withTaskGroup</code> function.</p>
<blockquote>
<p>💡: You can find more insights about async lets and task groups <a target="_blank" href="https://vbat.dev/async-let-vs-task-group">here</a>.</p>
</blockquote>
<h2 id="heading-unstructured-tasks"><strong>Unstructured tasks</strong></h2>
<p>Unstructured tasks are usually used as a bridge between sync and async contexts.</p>
<p>If we create some structured child tasks inside of unstructured task, unstructured task will be a root task (node) in the task tree, and parent task for structured tasks.</p>
<p>Unstructured tasks are represented by <strong>regular</strong> (it is not a common term, we will call it regular just to give it a distinctive name) and <strong>detached</strong> tasks.</p>
<pre><code class="lang-swift"><span class="hljs-type">Task</span> { <span class="hljs-comment">// regular task</span>
    <span class="hljs-keyword">let</span> result = await fetchData()
    <span class="hljs-built_in">print</span>(result)
}
<span class="hljs-type">Task</span>.detached { <span class="hljs-comment">// detached task</span>
    <span class="hljs-keyword">let</span> result = await fetchData()
    <span class="hljs-built_in">print</span>(result)
}
</code></pre>
<blockquote>
<p>💡: Lifecycle of unstructured tasks does not bound to the local scope or a single closure like async let or task group. When execution leaves local scope unstructured task will simply continue it’s execution.</p>
<p>💡: Unstructured tasks can be nested, but nested task doesn’t have parent/child relation with outer unstructured task. You can think about unstructured task like about initiating new task tree root. It could have no child tasks at all but it is independent from parent/child relation with context where it is created.</p>
<p><strong>⁉️ : What is the difference between regular and detached tasks? When to use each of them?</strong></p>
<p>We’ll discuss this in more detail later, but for now, we can say that a <strong>regular task inherits the execution context</strong>, while a detached task does not. It's quite hard to think of a specific scenario where a detached task would be useful, so in most cases, using a regular task is the better choice.</p>
</blockquote>
<h1 id="heading-rules-of-structured-concurrency">Rules of Structured Concurrency</h1>
<p>The task tree structure enables cooperative operations, offering features such as <strong>priority escalation</strong>, which means when setting a higher priority on a child task, the parent task’s priority is automatically escalated.</p>
<p>We can consider some of those features as some kind of <strong>structured concurrency rules</strong>:</p>
<ul>
<li><p><strong>Error Propagation Rule</strong></p>
</li>
<li><p><strong>Group Cancellation Rule</strong></p>
</li>
<li><p><strong>Group Completion Rule</strong></p>
</li>
</ul>
<blockquote>
<p>💡 : If you like acronyms for better memorization, you can use <strong>EGG</strong> 🥚.</p>
</blockquote>
<p>We will explore how these rules work within structured concurrency tasks and compare them with the behavior of nested unstructured tasks. You will see that these rules apply to tasks that are part of a single task tree structure, but they do not work for nested unstructured tasks.</p>
<h1 id="heading-group-completion-rule"><strong>Group Completion Rule</strong></h1>
<blockquote>
<p>👉 <strong>Group Completion Rule:</strong> In a parent task, you can’t forget to wait for its child tasks to complete. Parent task can’t complete until child tasks are completed.</p>
</blockquote>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> parentTask = <span class="hljs-type">Task</span> {
    async <span class="hljs-keyword">let</span> first = fetchPart1()  <span class="hljs-comment">// returns 1</span>
    async <span class="hljs-keyword">let</span> second = fetchPart2() <span class="hljs-comment">// returns 2</span>
    async <span class="hljs-keyword">let</span> third = fetchPart3()  <span class="hljs-comment">// returns 3</span>

    <span class="hljs-keyword">let</span> result = await (first, second, third)
    <span class="hljs-built_in">print</span>(result)
}

<span class="hljs-keyword">let</span> result = await parentTask.value
<span class="hljs-built_in">print</span>(<span class="hljs-string">"parentTask completes"</span>)

<span class="hljs-comment">// Prints:</span>
<span class="hljs-comment">// (1, 2, 3)</span>
<span class="hljs-comment">// parentTask completes</span>
</code></pre>
<p>Awaiting <code>parentTask</code>’s value means waiting until it completes. <code>"parentTask completes"</code> will not be printed until all child tasks created by <code>async let</code>s are completed first. For task group inside of unstructured parent task, behavior will be the same.</p>
<p>But it doesn’t work the same way for nested unstructured tasks once there are no parent/child relation there:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> rootTask = <span class="hljs-type">Task</span> {
    <span class="hljs-keyword">let</span> nestedTask = <span class="hljs-type">Task</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"nestedTask started"</span>)
        <span class="hljs-keyword">try</span>? await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>) <span class="hljs-comment">// 10 secs</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"nestedTask ended"</span>)
    }
}
<span class="hljs-keyword">let</span> result = await rootTask.value
<span class="hljs-built_in">print</span>(<span class="hljs-string">"rootTask completes"</span>)

<span class="hljs-comment">// Prints:</span>
<span class="hljs-comment">// rootTask completes</span>
<span class="hljs-comment">// nestedTask started</span>
<span class="hljs-comment">// ...after 10 seconds</span>
<span class="hljs-comment">// nestedTask ended</span>
</code></pre>
<p>As you can see in this example, <code>rootTask</code> completes even before <code>nestedTask</code> is started.</p>
<p>But we can achieve the desired behavior if we explicitly await <code>nestedTask</code> inside root task:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> rootTask = <span class="hljs-type">Task</span> {
    <span class="hljs-keyword">let</span> nestedTask = <span class="hljs-type">Task</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"nestedTask started"</span>)
        <span class="hljs-keyword">try</span>? await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>) <span class="hljs-comment">// 10 secs</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"nestedTask ended"</span>)
    }
    <span class="hljs-keyword">let</span> result = await nestedTask.value
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"nestedTask completes"</span>)
}
<span class="hljs-keyword">let</span> result = await rootTask.value
<span class="hljs-built_in">print</span>(<span class="hljs-string">"rootTask completes"</span>)

<span class="hljs-comment">// Prints:</span>
<span class="hljs-comment">// nestedTask started</span>
<span class="hljs-comment">// ...after 10 seconds</span>
<span class="hljs-comment">// nestedTask ended</span>
<span class="hljs-comment">// nestedTask completes</span>
<span class="hljs-comment">// rootTask completes</span>
</code></pre>
<p>We just need to await both nested and root unstructured tasks.</p>
<p>To sum up:</p>
<ul>
<li><p>async lets will be <strong>implicitly awaited</strong> when execution leaves the local scope, which allows parent task to complete after that as well, so group completion works as expected. ✅</p>
</li>
<li><p>Task group will <strong>implicitly await</strong> the completion of its child tasks when execution exits the task group closure, so parent task could complete after that as well, ensuring that group completion rule works as expected. ✅</p>
</li>
<li><p>Nested unstructured task, if not awaited, will not cause the external task (<code>rootTask</code>) to wait for its completion. It behaves like a "spawn and forget" mechanism, which means it doesn’t follow the group completion rule. ❌ (which is expected, because it is unstructured and has no parent).</p>
</li>
</ul>
<h1 id="heading-group-cancellation-rule"><strong>Group Cancellation Rule</strong></h1>
<blockquote>
<p>👉 <strong>Group Cancellation Rule:</strong> If parent task is canceled, each of its child tasks is also automatically canceled.</p>
</blockquote>
<p>Structured Concurrency promotes us to think about operation cancellation. But we need to understand precisely how it works.</p>
<blockquote>
<p>⚠️ : Keep in mind that <strong>cancelling the task does not stop the task</strong>, it only marks that results are not gonna be needed. Swift concurrency uses a cooperative cancellation model. Each task checks (with <strong>Task.isCancelled</strong> or <strong>Task.checkCancellation()</strong>) whether it has been canceled at the appropriate points in its execution, and responds to cancellation appropriately. Depending on what work the task is doing, responding to cancellation usually means one of the following:</p>
<ul>
<li><p>Throwing an error like <code>CancellationError</code></p>
</li>
<li><p>Returning <code>nil</code> or an empty collection</p>
</li>
<li><p>Returning the partially completed work</p>
</li>
</ul>
</blockquote>
<p>Let’s consider an example where we have child structured task and parent unstructured task:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">asyncWork</span><span class="hljs-params">()</span></span> async {
    <span class="hljs-keyword">do</span> {
        <span class="hljs-comment">/// If current task is canceled before the time ends,</span>
        <span class="hljs-comment">/// sleep function throws `CancellationError`.</span>
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>) <span class="hljs-comment">// 10 secs</span>
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Cancelled as a child task with \\(error)"</span>)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Not cancelled -&gt; not child task!"</span>)
}

<span class="hljs-keyword">let</span> parentTask = <span class="hljs-type">Task</span> {
    async <span class="hljs-keyword">let</span> result = asyncWork()
    await result
}
parentTask.cancel()

<span class="hljs-comment">// Prints:</span>
<span class="hljs-comment">// "Cancelled as a child task with CancellationError()" </span>
<span class="hljs-comment">// right after parentTask cancellation</span>
</code></pre>
<p>Once <strong>unstructured task</strong> can only be a root task in a task tree, it can’t be cancelled automatically (has no parent). It can be cancelled only explicitly with <code>cancel()</code> method. Doing that will cancel all child structured tasks in the task tree. That means 👉 <strong>Group Cancellation Rule works as expected</strong> ✅ for structured tasks.</p>
<blockquote>
<p><strong>⁉️ : Can we cancel single structured child task without cancelling it’s parent task?</strong> To cancel single async lets child task, we have to cancel parent task, which will cancel all child tasks together. For task group we also have an option without parent task cancellation - call group’s <code>cancelAll()</code> method.</p>
</blockquote>
<p>But 👉 <strong>Group Cancellation Rule</strong> doesn’t work ❌ for nested unstructured tasks.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> rootTask = <span class="hljs-type">Task</span> {
    <span class="hljs-type">Task</span> {
        await asyncWork() 
        <span class="hljs-comment">// prints "Not cancelled -&gt; not child task!" after 10 seconds</span>
    }
    <span class="hljs-type">Task</span>.detached {
        await asyncWork() 
        <span class="hljs-comment">// prints "Not cancelled -&gt; not child task!" after 10 seconds</span>
    }
}
rootTask.cancel()
</code></pre>
<p>Nested unstructured tasks aren’t cancelled when rootTask is cancelled.</p>
<p>We have only explicit cancellation option with <code>cancel</code> method called when rootTask is cancelled:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> rootTask = <span class="hljs-type">Task</span> {
    <span class="hljs-keyword">let</span> nestedTask = <span class="hljs-type">Task</span> {
        await asyncWork() 
        <span class="hljs-comment">// prints "Cancelled as a child task with CancellationError()" </span>
        <span class="hljs-comment">// right after rootTask cancellation</span>
    }
    <span class="hljs-keyword">if</span> <span class="hljs-type">Task</span>.isCancelled {
        nestedTask.cancel()
    }
}
rootTask.cancel()
</code></pre>
<h1 id="heading-error-propagation-rule"><strong>Error propagation Rule</strong></h1>
<blockquote>
<p>👉 <strong>Error Propagation Rule:</strong> If error is propagated outside of local scope, all child tasks are implicitly cancelled and implicitly awaited.</p>
</blockquote>
<p>An <code>async</code> function can throw errors by being marked with both the <code>async</code> and <code>throws</code>. <strong>Error propagation</strong> in Swift refers to the process of passing errors up the call stack, allowing higher-level code to handle or respond to issues that occur during execution.</p>
<p>Let’ check this rule with examples.</p>
<p>When we leave the local scope due to error, async lets and task group child tasks will be <strong>implicitly cancelled</strong> and <strong>implicitly awaited</strong> ✅ <strong>:</strong></p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fast</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">5_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast ended"</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError1</span>() <span class="hljs-comment">// &lt;- ERROR IS THROWN HERE</span>
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">slow</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow ended"</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError2</span>() <span class="hljs-comment">// &lt;- ERROR IS THROWN HERE</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">testErrorPropagation</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    async <span class="hljs-keyword">let</span> f = fast()
    async <span class="hljs-keyword">let</span> s = slow()
    <span class="hljs-keyword">try</span> await (f, s)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving local scope"</span>) <span class="hljs-comment">// &lt;- will not be printed</span>
}

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    fast ended // after 5 secs</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
<span class="hljs-comment">//    external catch TestError1()</span>
</code></pre>
<p>After <code>fast</code> throws <code>TestError1</code>, execution leaves the local scope because the error is propagated outside of it. As a result, <code>slow</code> is implicitly cancelled and implicitly awaited.<br /><code>TestError1</code> is propagated and caught outside of local scope. <code>TestError2</code> was not propagated because <code>TestError1</code> was handled first.</p>
<p><strong>What about error propagation in unstructured tasks?</strong></p>
<p>Throwing an error in unstructured nested task doesn’t cancel other unstructured nested tasks:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> externalTask = <span class="hljs-type">Task</span> {
    <span class="hljs-keyword">let</span> nestedTask = <span class="hljs-type">Task</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 1 started"</span>)
        <span class="hljs-keyword">do</span> {
            <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">5_000_000_000</span>)
        } <span class="hljs-keyword">catch</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 1 cancelled"</span>, error)
        }
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 1 ended"</span>)
        <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError1</span>()
    }
    <span class="hljs-keyword">let</span> nestedTask2 = <span class="hljs-type">Task</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 2 started"</span>)
        <span class="hljs-keyword">do</span> {
            <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
        } <span class="hljs-keyword">catch</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 2 cancelled"</span>, error)
        }
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nested Task 2 ended"</span>)
        <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError2</span>()
    }

    <span class="hljs-keyword">try</span> await nestedTask.value
    <span class="hljs-keyword">try</span> await nestedTask2.value
}

<span class="hljs-keyword">do</span> {
    <span class="hljs-keyword">try</span> await externalTask.value
} <span class="hljs-keyword">catch</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Caught error \\(error)"</span>)
}

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    Nested Task 1 started</span>
<span class="hljs-comment">//    Nested Task 2 started</span>
<span class="hljs-comment">//    Nested Task 1 ended // after 5 secs</span>
<span class="hljs-comment">//    Caught error TestError1()</span>
<span class="hljs-comment">//    Nested Task 2 ended // after 10 secs</span>
</code></pre>
<p>As you can see, when we throw <code>TestError1</code> from <code>nestedTask</code>, error is propagated and get caught outside. <code>nestedTask2</code> was not cancelled and ended later after that. That means:</p>
<blockquote>
<p>👉 <strong>Error Propagation Rule</strong> doesn’t work ❌ for nested unstructured tasks.</p>
<p>💡: There is another rule that works for <strong>both structured and unstructured tasks (including nested)</strong> related to error propagation: 👉 <strong>Error is propagated from task only if that task is awaited explicitly. No await → no propagation.</strong></p>
<p>In this example, even though error is thrown we will leave the task group closure without error propagated because child tasks are not awaited:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">try</span> await withThrowingTaskGroup(of: <span class="hljs-type">Void</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
    group.addTask {
        <span class="hljs-keyword">try</span> await fast() <span class="hljs-comment">// throws error</span>
    }
    group.addTask {
        <span class="hljs-keyword">try</span> await slow() <span class="hljs-comment">// throws error</span>
    }

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving task group closure without error propagated”)
}</span>
</code></pre>
</blockquote>
<h1 id="heading-context-inheritance"><strong>Context Inheritance</strong></h1>
<p>Even though it’s not the part of defined structured concurrency rules, let’s also discuss how structured and unstructured tasks inherit properties from the context they are created from.</p>
<p><strong>Regular unstructured task</strong> inherits:</p>
<ul>
<li><p><strong>Task Priority</strong></p>
<ul>
<li><p>In an async context, when you create an unstructured task without specifying a priority, it inherits the priority from the current task.</p>
</li>
<li><p>In sync context it will use the <strong>priority of the thread or queue</strong> from which it was called.</p>
<pre><code class="lang-swift">  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
      <span class="hljs-keyword">super</span>.viewDidLoad()

      <span class="hljs-type">Task</span> {
          <span class="hljs-built_in">print</span>(<span class="hljs-type">Task</span>.currentPriority) <span class="hljs-comment">// Prints `.high`</span>
      }
      <span class="hljs-type">Task</span> {
          <span class="hljs-type">DispatchQueue</span>.global(priority: .low).async {
              <span class="hljs-built_in">print</span>(<span class="hljs-type">Task</span>.currentPriority) <span class="hljs-comment">// Prints `.low`</span>
          }
      }
      <span class="hljs-type">Task</span>(priority: .background) {
          <span class="hljs-type">Task</span> {
              <span class="hljs-built_in">print</span>(<span class="hljs-type">Task</span>.currentPriority) <span class="hljs-comment">// Prints `.background`</span>
          }
      }
      <span class="hljs-type">Task</span>(priority: .background) {
          <span class="hljs-type">DispatchQueue</span>.main.async {
              <span class="hljs-type">Task</span> {
                  <span class="hljs-built_in">print</span>(<span class="hljs-type">Task</span>.currentPriority) <span class="hljs-comment">// Prints `.high`</span>
              }
          }
      }
  }
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Task local values</strong></p>
<ul>
<li><p>Swift lets us attach metadata to a task using <em>task-local values</em>, which are small pieces of information that any code inside a task can read. A task-local value is bound and read in the context of a task. It is implicitly carried with the task, and is accessible by any child tasks it creates. If we create unstructured task, is also inherit it from the task it was created from.</p>
<pre><code class="lang-swift">  <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">TaskLocalStorage</span> </span>{
      @<span class="hljs-type">TaskLocal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> requestID: <span class="hljs-type">String?</span>
  }

  <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkTaskLocalValue</span><span class="hljs-params">()</span></span> async {
      await <span class="hljs-type">TaskLocalStorage</span>.$requestID.withValue(<span class="hljs-string">"12345"</span>) {
          <span class="hljs-built_in">print</span>(<span class="hljs-string">"RequestID: \\(TaskLocalStorage.requestID ?? "</span><span class="hljs-type">No</span> <span class="hljs-type">Request</span> <span class="hljs-type">ID</span><span class="hljs-string">")"</span>) 
          <span class="hljs-comment">// Prints: Request ID: 12345</span>

          <span class="hljs-type">Task</span> {
              <span class="hljs-comment">// Regular unstructured task inherits task-local values.</span>
              <span class="hljs-built_in">print</span>(<span class="hljs-string">"Regular unstructured RequestID: \\(TaskLocalStorage.requestID ?? "</span><span class="hljs-type">No</span> <span class="hljs-type">Request</span> <span class="hljs-type">ID</span><span class="hljs-string">")"</span>) 
              <span class="hljs-comment">// Prints: Regular unstructured RequestID: 12345</span>
          }

          <span class="hljs-comment">// async let and task group will inherit task local values as well.</span>
          <span class="hljs-comment">// Only detached task is "detached" from TaskLocalStorage.</span>

          <span class="hljs-type">Task</span>.detached {
              <span class="hljs-comment">// Detached task does not inherit task-local values.</span>
              <span class="hljs-built_in">print</span>(<span class="hljs-string">"Detached Task RequestID: \\(TaskLocalStorage.requestID ?? "</span><span class="hljs-type">No</span> <span class="hljs-type">Request</span> <span class="hljs-type">ID</span><span class="hljs-string">")"</span>) 
              <span class="hljs-comment">// Prints: Detached Task RequestID: No Request ID</span>
          }
      }
  }
</code></pre>
</li>
</ul>
</li>
<li><p>The <strong>actor</strong> we’re currently running on (if any). Basically it means that we inherit the context’s executor (aka <strong>execution context</strong>).</p>
</li>
</ul>
<p><strong>Structured task</strong> inherits <strong>the same as regular task except actor isolation:</strong></p>
<blockquote>
<p>⚠️ : Some articles claim that child structured tasks created with <code>async let</code> inherit actor isolation from the context in which they are created. This can be misleading. <strong>Structured tasks do not inherit actor isolation from their surrounding context</strong>. It wouldn’t make sense because structured tasks are designed to parallelize the work, and inheriting actor isolation would force them to run everything on the same actor (if present), sequentially.</p>
<p>By default, child structured tasks run on the global concurrent executor. However, keep in mind that any async functions you call within these tasks can have their own actor isolation. For <code>async let</code>, once it is assigned with an async function call, it may look like as though it inherits the actor isolation of that function. But again, <strong>structured tasks never inherit actor isolation from the context where they are created</strong>.</p>
<pre><code class="lang-swift">nonisolated <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchFirst</span><span class="hljs-params">()</span></span> async -&gt; <span class="hljs-type">Int</span> { <span class="hljs-comment">//... }</span>
@<span class="hljs-type">MainActor</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchSecond</span><span class="hljs-params">()</span></span> async -&gt; <span class="hljs-type">Int</span> async { <span class="hljs-comment">//... }</span>

async <span class="hljs-keyword">let</span> first = fetchFirst()
async <span class="hljs-keyword">let</span> second = fetchSecond()

<span class="hljs-comment">// is converted under the hood into something like this:</span>

<span class="hljs-keyword">let</span> first = <span class="hljs-type">ChildTask</span> { <span class="hljs-comment">// global concurrent executor</span>
    await fetchFirst() <span class="hljs-comment">// global concurrent executor</span>
}
<span class="hljs-keyword">let</span> second = <span class="hljs-type">ChildTask</span> { <span class="hljs-comment">// global concurrent executor</span>
    await fetchSecond() <span class="hljs-comment">// main executor</span>
}
</code></pre>
</blockquote>
<p><strong>Detached task</strong> inherits <strong>nothing</strong>. On practice it means that it always runs on the global concurrent executor, so it will run on any thread from cooperative thread pool. But not main thread for sure.</p>
<h1 id="heading-when-to-use-structured-and-unstructured-concurrency">When to use structured and unstructured concurrency?</h1>
<p><strong>When to use structured concurrency?</strong></p>
<p>When you need to run multiple concurrent operations at once. But keep in mind it’s “implicit awaiting” behavior: when execution exits this scope — either normally or due to an error — all child tasks will be <strong>implicitly</strong> <strong>awaited</strong>.</p>
<p><strong>When to use async let vs task group?</strong></p>
<p>Several factors to consider:</p>
<ul>
<li><p>Amount of tasks: static count for async let, dynamic count for task groups.</p>
</li>
<li><p>If you have error propagation logic, a task group may be a better choice than <code>async let</code>. The task group’s "first thrown, first caught" logic is more predictable and won’t be affected by the order of awaiting, unlike <code>async let</code>, where error propagation can be influenced by the awaiting order. Task groups are more suitable for cases where you want to "fail fast" compared to <code>async let</code>.</p>
</li>
<li><p>async let can be easier to use with <em>heterogeneous results</em> and step-by-step initialization patterns.</p>
</li>
</ul>
<p><strong>When to use unstructured concurrency?</strong></p>
<ul>
<li><p>Obviously, use it when you need to switch gears from sync to async context.</p>
</li>
<li><p>Regular unstructured task might also be used if you want to perform a piece of work independently of the function you’re in, without awaiting. Some “fire-and-forget” style operation that will allow the function to not wait for this task completion.</p>
<pre><code class="lang-swift">  <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">someWorkThatDontWantToWait</span><span class="hljs-params">()</span></span> async {
      <span class="hljs-type">Task</span> {
          await work()
      }
  }
</code></pre>
</li>
<li><p><strong>Detached Tasks</strong> use case is kind of tricky, because hard to say exactly when you need it:</p>
<ul>
<li><p>That’s why it’s often referenced as a <strong>last resort</strong>, when nothing else would suit.</p>
</li>
<li><p>Anyway, make sure you really understand why usually it is not needed.</p>
<blockquote>
<p>💡: Usually regular task is enough. Even though it inherits actor isolation, even if you want to avoid it and run some function on global concurrent executor, remember that this function also has it’s own actor isolation context which will define where it will be executed, not the task from where we call it. For example:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewController</span>: <span class="hljs-title">UIViewController</span> </span>{ <span class="hljs-comment">// Main actor isolated</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">someWorkOnMainThread</span><span class="hljs-params">()</span></span> async {
        <span class="hljs-type">Task</span>.detached {
             await <span class="hljs-keyword">self</span>.asyncMethod()
             await <span class="hljs-keyword">self</span>.syncMethod()
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">asyncMethod</span><span class="hljs-params">()</span></span> async {
        <span class="hljs-comment">// called on the main thread</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">syncMethod</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// called on the main thread</span>
    }
}
</code></pre>
<p>Although we called methods from detached task, it didn’t make them to be called on global concurrent executor. Both method are main actor isolated as methods of ViewController which is main actor isolated.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<p>Finally, for quick reference, keep this the helper table from <a target="_blank" href="https://developer.apple.com/videos/play/wwdc2021/10134/">Apple WWDC session</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743869458360/d6800796-d6eb-4355-8f0a-3ae3d3128c1f.png" alt /></p>
<p>For structured tasks lifecycle when local scope is left, this scheme should be useful as well (you can find more info on that matter <a target="_blank" href="https://vbat.dev/async-let-vs-task-group">here</a>):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743869464696/ed4d6da3-4474-46a7-aa88-76e8280d8bf0.png" alt /></p>
<p>If you spot any inaccuracies or misleading points in this post, please share them in the comments - your feedback helps everyone!</p>
<p>Good luck on your Swift Concurrency journey! See you in the next article!</p>
]]></content:encoded></item><item><title><![CDATA[Behind the scenes of async functions]]></title><description><![CDATA[You can find video version of this article here.

Hey iOS Folks!
Swift Concurrency (SC) is becoming an integral part of every iOS app project, making a strong understanding of it essential. While WWDC sessions offer valuable insights, they often leav...]]></description><link>https://vbat.dev/behind-the-scenes-of-async-functions</link><guid isPermaLink="true">https://vbat.dev/behind-the-scenes-of-async-functions</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 09 Mar 2025 15:15:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741527103056/6068f5b3-7c4c-45a7-afa4-381c548631e5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>You can find video version of this article <a target="_blank" href="https://youtu.be/TWhffbbMPDk">here</a>.</p>
</blockquote>
<p>Hey iOS Folks!</p>
<p><strong>Swift Concurrency (SC)</strong> is becoming an integral part of every iOS app project, making a strong understanding of it essential. While WWDC sessions offer valuable insights, they often leave some questions unanswered. If your mental model of SC still feels incomplete, I hope this article helps.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741483444445/27da316a-2f27-4af5-8794-0c66ac2fddec.jpeg" alt class="image--center mx-auto" /></p>
<p>In short, I want you to feel like Buzz Lightyear when working with SC — not like Woody, lost in confusion. We'll take a step-by-step approach to see how each SC concept connects, helping you build a solid understanding of how it all works together.</p>
<blockquote>
<p>⚠️ : I assume you are familiar with the basic concepts of <strong>Swift Concurrency.</strong> If not, you can start <a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/">here</a>. I will not explain every Swift Concurrency concept in detail. My goal is to show how everything works together and try to provide a full picture.</p>
</blockquote>
<p>Let’s start with async functions, as they are the part of Swift Concurrency we most often work with directly.</p>
<h3 id="heading-async-functions"><strong>Async functions</strong></h3>
<p>What is an <strong>async function</strong>? Well, it’s a function with <code>async</code> keyword, obviously. But what does that actually mean? The simplest way to understand an async function is to think of it as a special kind of function that <strong>can be suspended</strong> during it’s execution (specifics of when and how this suspension occurs aren't as important right now as the fact that it <strong><em>can</em> be suspended)</strong>.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">asyncWork</span><span class="hljs-params">()</span></span> async {
    <span class="hljs-comment">//...</span>
}
</code></pre>
<p>It may seem counterintuitive, but ordinary <strong>synchronous functions can work as asynchronous functions, but not the other way around</strong>. Asynchronous functions <strong>can be</strong> <strong>suspended</strong>, but they might not always be. Therefore, the compiler can treat a sync function as an async function that simply never suspends.</p>
<p>The reverse, however, is not allowed. Sync functions are expected to <strong>never be suspended</strong>, so an async function does not meet these requirements.</p>
<p>For example:</p>
<ul>
<li><p><code>async</code> method protocol requirement <strong>can be implemented</strong> ✅ with a <code>sync</code> method.</p>
<pre><code class="lang-swift">  <span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">SomeProtocol</span> </span>{
      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">work</span><span class="hljs-params">()</span></span> async
  }

  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SomeStruct</span>: <span class="hljs-title">SomeProtocol</span> </span>{
      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">work</span><span class="hljs-params">()</span></span> {
          <span class="hljs-comment">//...</span>
      }
  }
</code></pre>
</li>
<li><p><code>sync</code> method <strong>protocol requirement can’t be implemented ❌ with an</strong> <code>async</code> <strong>method</strong>.</p>
<pre><code class="lang-swift">  <span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">SomeProtocol</span> </span>{
      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">work</span><span class="hljs-params">()</span></span>
  }

  <span class="hljs-comment">// Compiler Error: Type 'SomeStruct' does not conform to protocol 'SomeProtocol'</span>
  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SomeStruct</span>: <span class="hljs-title">SomeProtocol</span> </span>{ 
      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">work</span><span class="hljs-params">()</span></span> async {
          <span class="hljs-comment">//...</span>
      }
  }
</code></pre>
</li>
</ul>
<p>That gives us the idea that conforming to a "sync function" is a "stronger" (stricter) requirement than conforming to an “async function”. Sync function can work as async function, but not the other way around.</p>
<p>Async functions <strong>can be called</strong> <strong>only from</strong> <strong>async context</strong>.</p>
<blockquote>
<p>💡: Basically, being in <strong>async context</strong> means that we are inside async function (or closure).</p>
</blockquote>
<p>When the app starts running, it is in a <strong>synchronous(sync) context</strong> by default. Most of the code related to the app or UI lifecycle is also executed in a sync context.</p>
<blockquote>
<p>⁉️: <strong>How we can switch from sync context to async context?</strong> Indeed, if <strong>we can call async function only from async function or closure,</strong> how we can call async function or closure in the first time? We can do it using unstructured concurrency tasks (<strong>Task {}</strong>), but we will discuss that a bit later.</p>
</blockquote>
<p>To call an asynchronous function, we always need to <strong>await</strong> (asynchronously wait) it. That fact gives us another example of how sync functions are “stronger” than async functions. You can’t call async function without await. But you can call sync function with await (with warning from compiler, of course, once nothing async is happening there) or without await:</p>
<pre><code class="lang-swift">await syncFunc() <span class="hljs-comment">// Warning: No 'async' operations occur within 'await'</span>
asyncFunc()  <span class="hljs-comment">// Error: Expression is 'async' but is not marked with 'await'</span>
</code></pre>
<h3 id="heading-async-await"><strong>Async → await</strong></h3>
<p>Async/await feature allows to write asynchronous functions and call them asynchronously by awaiting them. In result, we can write asynchronous code in a more sequential and readable manner, like we did with sync functions.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">asyncWork</span><span class="hljs-params">()</span></span> async -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-comment">//...</span>
}

<span class="hljs-keyword">let</span> result = await asyncWork() <span class="hljs-comment">// doesn't block the thread execution</span>
</code></pre>
<p>But what does it mean to “asynchronously wait” the function call and how this waiting doesn’t block the thread execution?</p>
<p>Do you remember that an <code>async</code> function can be suspended? Well, this happens only when you use the <code>await</code> keyword to await for another async function. When a function encounters <code>await</code>, its execution can be paused until the awaited function call is complete. In other words, <code>await</code> creates a <strong>potential suspension point</strong> 😴 in your code.</p>
<blockquote>
<p>💡: <code>await</code> is only a <strong>potential</strong> suspension point. It only will be an <strong>actual suspension point</strong> if <strong>execution contexts</strong> of caller and callee functions are different. We will come back to it to discuss how it works more deeply a bit later.</p>
</blockquote>
<p>Ok, but how it doesn’t block the thread then?</p>
<blockquote>
<p>💡: Simply put, a suspended async function gives up its thread to perform other work while it waits for the awaited function to complete.</p>
</blockquote>
<p>When an <code>async</code> function reaches an <code>await</code> point that causes suspension, a continuation is created - a snapshot of the current execution state that can resumed later. This frees up the thread for other tasks. Instead of blocking threads, continuations allow execution to switch between tasks efficiently, incurring only the cost of a function call rather than a full thread context switch.</p>
<p><strong>What is continuation?</strong></p>
<p>The continuation is a <strong>snapshot of async function execution state</strong> (return address, params, local vars, etc.). In other words, continuation remembers the <strong>point in the caller function</strong> where execution should resume after the awaited operation is completed. When a suspended function is ready to resume, the runtime can pick up the continuation and continue execution, <strong>potentially on a different thread</strong>. At runtime, continuation is represented by the list of <strong>async frames</strong> stored on the <strong>heap.</strong></p>
<blockquote>
<p>💡: We're not diving too deep here, but I recommend to watch <a target="_blank" href="https://developer.apple.com/videos/play/wwdc2021/10254/">WWDC2021 - Swift concurrency: Behind the scenes</a> (since 14:26) session to understand how the async frames work.</p>
<p>⁉️: <strong>Why the heap is needed?</strong> Because continuation can potentially be picked up by a thread different from the original thread where the async function was awaited, there is a necessity for the continuation state to be shared across different threads. Therefore, it cannot be bound to the call stack of a single thread.</p>
</blockquote>
<h3 id="heading-tasks"><strong>Tasks</strong></h3>
<p>We already mentioned that to switch from sync to async context, we need to create an unstructured concurrency <strong>Task:</strong></p>
<pre><code class="lang-swift"><span class="hljs-type">Task</span> {
    await callAsyncFunc()
}
</code></pre>
<p>However, that is not the only use case for tasks. Tasks are, in fact, a much broader concept in Swift Concurrency.</p>
<blockquote>
<p>💡: <a target="_blank" href="https://developer.apple.com/documentation/swift/task">Tasks</a> are <strong>units of asynchronous work</strong> you can run from your code.</p>
</blockquote>
<ul>
<li><p><strong>Every async function is executing in a task</strong>. In other words, a <em>task</em> is to <em>asynchronous functions</em>, what a <em>thread</em> is to <em>synchronous functions.</em></p>
</li>
<li><p>A task runs one function at a time; a single task has no concurrency.</p>
</li>
<li><p>Synchronous functions do not necessarily run as part of a task, but they can.</p>
</li>
</ul>
<blockquote>
<p>💡: <a target="_blank" href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0304-structured-concurrency.md#tasks">Here</a> you can find more insights.</p>
<p>⁉️: <strong>How tasks can be created?</strong></p>
<ul>
<li><p><strong>Explicitly</strong> with <strong>Task {}</strong> and <strong>Task.detached {}</strong>.</p>
</li>
<li><p><strong>Implicitly</strong> by <strong>async let</strong> and <strong>task group</strong> (they are gonna be child tasks)<strong>.</strong></p>
</li>
</ul>
<p>⁉️: <strong>Is the task created when an</strong> <code>await</code> with suspension point 😴 <strong>occurs?</strong> No, even if it is an actual suspension point.</p>
<ul>
<li><p>When a function makes an async call, the called function is still running as part of the same task.</p>
</li>
<li><p>Similarly, when a function returns from an async call, the caller resumes running on the same task.</p>
</li>
</ul>
</blockquote>
<p>A task can be in one of three states:</p>
<ul>
<li><p>🔴 A <strong>suspended</strong> task has more work to do but is not currently running (initial state).</p>
<ul>
<li><p>It may be <strong>schedulable</strong>, meaning that it’s ready to run and is just waiting for the system to instruct a thread to begin executing it.</p>
</li>
<li><p>or it may be <strong>waiting</strong> on some external event before it can become schedulable.</p>
</li>
</ul>
</li>
<li><p>🟡 A <strong>running</strong> task is currently running on a thread.</p>
<ul>
<li>It will run until it either completes or reaches an actual <strong>suspension point</strong> 😴 (and becomes suspended again). A task's state can change between running and suspended multiple times during its lifecycle.</li>
</ul>
</li>
<li><p>🟢 A <strong>completed</strong> task has no more work to do and will never enter any other state.</p>
<ul>
<li>Code can wait for a task to become completed by <code>await</code>ing on it.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡: Do you remember that async functions can be suspended at potential suspension points with <code>await</code>? Well, what actually gets suspended is the task in which the async function is executing. Of course, the async function itself is also suspended since it's part of the task, but it's important to highlight that the task is what's truly being “paused”.</p>
<p>⁉️ <strong>: Are we already inside some task when app starts? No</strong>, you can check it from any <code>viewDidLoad</code> or <code>AppDelegate</code> :</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkIfInsideTask</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Bool</span> {
    withUnsafeCurrentTask { $<span class="hljs-number">0</span> != <span class="hljs-literal">nil</span> }
}
</code></pre>
<p>Again, by default, app runs in synchronous world, so you need to create a task to switch the gears from sync to async.</p>
</blockquote>
<h3 id="heading-jobs"><strong>Jobs</strong></h3>
<p>Task are units of async work but not the most basic ones. Tasks consists of the <strong>jobs</strong>, even more smaller units of asynchronous work. <strong>Jobs</strong> are the <strong>basic</strong> units of <strong>schedulable</strong> <strong>synchronous</strong> work that occurs between suspension points within a Task. When a Task suspends (for instance, while waiting for an async function to complete), the next job will be scheduled for later execution.</p>
<p>Let’s consider an example:</p>
<pre><code class="lang-swift"><span class="hljs-type">Task</span> {
    beforeWork()
    await asyncWork()
    afterWork()
}
</code></pre>
<p>Every async call with <code>await</code> can be thought of as splitting the code into three different parts:</p>
<ul>
<li><p>job before the suspension point;</p>
</li>
<li><p>job that resumes after the suspension point;</p>
</li>
<li><p>job (or jobs) that will be created based on <code>asyncWork</code> structure (<code>asyncWork</code> can have more awaits inside of it).</p>
</li>
</ul>
<blockquote>
<p>⚠️ : This isn't documented, so I could be wrong in some details, but this is how I understood it from this Swift forum <a target="_blank" href="https://forums.swift.org/t/jobs-and-tasks/53957/1">thread</a>. Let me know in the comments if you have more details.</p>
<p>💡: Simply put, you can think of it as breaking up an <code>async</code> function into synchronous chunks, each of which is a <strong>job</strong>.</p>
</blockquote>
<p>Developers usually don’t work with the jobs directly. Jobs are created by runtime. But their structure is defined at compiling the awaits structure. The compiler generates code for async functions, including how they suspend, resume, and manage continuations. This precompiled structure tells the runtime how to create jobs when needed, but actual job instances are created at runtime when suspension occur.</p>
<blockquote>
<p>💡: Single task has no concurrency, meaning the jobs within a single task also have no concurrency with each other. They wait for the previous job to complete before starting. Jobs are designed to be executed <strong>sequentially</strong>, one after another, with the possibility of the task being suspended if the next job needs to be executed on a thread that is currently busy.</p>
<p>💡: Jobs defined as <strong>synchronous</strong> which means there are no <strong>await</strong>’s inside of a job.</p>
</blockquote>
<h3 id="heading-actors"><strong>Actors</strong></h3>
<p>Tasks and jobs can perform work concurrently, which is great. However, when multiple tasks need to access or modify the same shared data, there’s a risk of <strong>data races</strong>, which can lead to unpredictable behavior. This is where <strong>actors</strong> come into play. <a target="_blank" href="https://developer.apple.com/documentation/swift/actor">Actors</a> allow you to <strong>isolate async function calls</strong> to safely share their (actor’s) state with other concurrent tasks or threads. This ensures that the <strong>mutable state</strong> within the actor is accessed in a thread-safe manner, preventing race conditions and data corruption.</p>
<blockquote>
<p>💡: Metaphorically, if a task were a car, an actor would be a garage — only one car can park in the garage at a time. A car can park in a garage, but car doesn’t own it.</p>
</blockquote>
<p>You probably already familiar with actors from other articles, but we will consider one simple example to refresh it quickly:</p>
<pre><code class="lang-swift">actor <span class="hljs-type">SomeActor</span> {
    <span class="hljs-keyword">let</span> immutableState = <span class="hljs-number">1</span>

    <span class="hljs-keyword">var</span> mutableState = <span class="hljs-number">2</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateState</span><span class="hljs-params">(<span class="hljs-number">_</span> newValue: Int)</span></span> {
        mutableState = newValue
    }
}

<span class="hljs-keyword">let</span> actor = <span class="hljs-type">SomeActor</span>()

<span class="hljs-built_in">print</span>(actor.immutableState) <span class="hljs-comment">// accessed without await in sync context</span>

<span class="hljs-type">Task</span>.detached {
    await <span class="hljs-built_in">print</span>(actor.mutableState) <span class="hljs-comment">// accessed mutable state from async context</span>

    actor.mutableState = <span class="hljs-number">3</span> <span class="hljs-comment">// Compilation Error: Actor-isolated property </span>
    <span class="hljs-comment">// 'mutableState' can not be mutated from a nonisolated context</span>

    await actor.updateState(<span class="hljs-number">3</span>) <span class="hljs-comment">// can be mutated with actor method</span>
}
</code></pre>
<p>Main takeaways from example:</p>
<ul>
<li><p>Actors allow only one async func (~task) to access their mutable state at a time (it will happen asynchronously), which makes it safe for code in multiple tasks to interact with.</p>
</li>
<li><p>Immutable state can be accessed synchronously. Yes, you can I access actor’s immutable state from sync context without using <code>await</code>.</p>
</li>
<li><p>Mutable state can be accessed only from async context with await.</p>
</li>
<li><p>You can change the mutable state only with actor own methods.</p>
</li>
</ul>
<h3 id="heading-executors"><strong>Executors</strong></h3>
<p><a target="_blank" href="https://developer.apple.com/documentation/swift/executor"><strong>Executor</strong></a> is a special service which accepts the enqueued jobs and arranges some thread to run them. Once job is enqueued to some executor, it will be fully completed there. Executors manage job priorities, ensuring that high-priority tasks are executed before lower-priority ones. For the most part, ios engineers will not have to work directly with executors or jobs unless they are implementing a custom executor, which is probably not very common.</p>
<p>Jobs were defined as <strong>schedulable</strong> but what does it actually mean?</p>
<p>Swift doesn’t expose an explicit "scheduler" object, meaning the runtime implicitly acts as one. Scheduling involves the runtime enqueueing jobs to the appropriate executor based on the "context" — essentially, the code itself. From the code, we can determine which executor should handle a job. The code also defines the job structure, so each job has a designated target executor. This will become clearer when we discuss different types of executors later.</p>
<blockquote>
<p>💡: Later, when we use <strong>execution context</strong>, we will mean <strong>the executor</strong> and vice versa.</p>
</blockquote>
<p>Executor is also the key concept to understand when <strong>potential</strong> <strong>suspension point</strong> becomes <strong>actual suspension point</strong> 😴.</p>
<p>When async function calls another async function with the same target executor, it continues running without suspension, there is no <strong>actual suspension point</strong>. It becomes an actual suspension point if the caller and callee should run on different executors, and the runtime <strong>switches the execution contexts (hops to another executor)</strong> to execute the async function.</p>
<p>When awaited async function call completes, caller function always continues it’s execution on the same executor as it was before the async call:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">exampleFunc</span><span class="hljs-params">()</span></span> async {
  <span class="hljs-comment">// hop to executor of firstFunc</span>
  await firstFunc()
  <span class="hljs-comment">// hop to executor of exampleFunc</span>

  syncWork()

  <span class="hljs-comment">// hop to executor of secondFunc</span>
  await secondFunc()
  <span class="hljs-comment">// hop to executor of exampleFunc</span>

  syncWork2()
}
</code></pre>
<p>Executors run jobs using threads, but they don’t create or own threads. Instead, they borrow threads from the <strong>Cooperative Thread Pool (CTP)</strong>.</p>
<h3 id="heading-cooperative-thread-pool"><strong>Cooperative Thread Pool</strong></h3>
<p>Before Swift Concurrency, we mostly built the concurrency in our apps with <strong>Dispatch</strong> <strong>queues</strong> (<strong>Grand Central Dispatch</strong> aka <strong>GCD</strong> aka <strong>libdispatch</strong>) and <strong>OperationQueue</strong> (which is basically object oriented wrapper around GCD).</p>
<p>Dispatch queues can be either <strong>serial</strong> or <strong>concurrent</strong>. Like Executors in Swift Concurrency, they execute submitted work on a system-managed thread pool.</p>
<p>We also had a special queue - <strong>Main Queue</strong> - that executes code only on the <strong>Main Thread</strong>.</p>
<p><strong>GCD</strong> with queues is a powerful approach, but it can cause issues at scale. GCD schedules tasks onto its threads using a <strong>preemptive model</strong>. (Keep in mind that a GCD task differs from a Swift Concurrency <code>Task</code>. A GCD task is essentially a closure — a block of code that can be queued and executed later). If too many tasks are queued concurrently, GCD may create new threads as needed. This can overload CPU resources because GCD doesn’t strictly limit the number of active threads to the available CPU cores. This situation, known as a <strong>thread explosion</strong>, occurs when an app spawns too many threads, often leading to performance degradation, resource exhaustion, or even crashes.</p>
<p>Swift Concurrency introduced the <strong>Cooperative Thread Pool (CTP)</strong> - a shared pool of threads. While thread pools are not new to GCD, <strong>CTP</strong> was a new feature added specifically for Swift Concurrency (so SC relies on GCD under the hood). Unlike previous dynamic thread pool, <strong>CTP</strong> uses a <strong>fixed number of threads</strong>, limited to the number of CPU cores (e.g., six threads for a six-core iPhone 16 Pro).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741484368491/a90c5cdc-a158-475a-b1e2-0a525be6e1c0.png" alt class="image--center mx-auto" /></p>
<p>You can recognize CTP threads by “cooperative (concurrent)" label there. As you can see, there are also other threads outside CTP doing something there at the same time.</p>
<blockquote>
<p>💡: Limiting CTP thread count to CPU cores count allows to avoid <strong>thread explosion</strong> and <strong>scheduling threads overhead</strong> of context switching (switching CPU core between threads is expensive operation).</p>
<p>⁉️: <strong>Why does it match the thread count to the CPU core count? Isn’t the main thread always there? Why not make it N-1 once the main thread is handling UI updates?</strong> The main thread doesn’t necessarily always have work to do. As I mentioned in my article on <a target="_blank" href="https://vbat.dev/behind-the-scenes-of-ui-part-1-uikit#heading-runloop">UI under the hood</a>, the run loop on the main thread puts it into sleep after each iteration if there’s no work to do. When there’s no work for the main thread, it’s better to use the CPU core it was using. That why in SC, I guess, it matches the thread count to the core count - for better performance. It doesn't completely disable thread context switching, but aims to minimize it and keep performance optimal.</p>
</blockquote>
<p>In Swift Concurrency, we also continue to use <strong>Main thread</strong> (and main queue) which is <strong>not</strong> the part of <strong>CTP.</strong></p>
<blockquote>
<p>💡: All tasks managed by SC are executed either on the <strong>CTP</strong> or <strong>Main Thread</strong>.</p>
<p>💡: SC removes our ability to create or manage threads directly and encourages us to focus on executors and actor isolation instead. When running code with SC, we no longer think about which thread it should run on. Instead, we focus on how it should be executed in terms of actor isolation, which basically means - which type of executor will handle the task.</p>
</blockquote>
<h3 id="heading-types-of-executors">Types of executors</h3>
<p>Before combining all the concepts we've covered so far, we need to understand the different types of executors that exist. Once we already know the roles of actors, the Cooperative Thread Pool (CTP), and the main thread in Swift Concurrency, we can explain how they all connect to executors.</p>
<p>There are 3 types of executors that we need to know about:</p>
<ul>
<li><p>Default concurrent executor</p>
</li>
<li><p>Serial executor</p>
</li>
<li><p>Main executor</p>
</li>
</ul>
<p><strong>Default concurrent executor</strong></p>
<p>It’s a <strong>default</strong> executor and it’s <strong>concurrent</strong>, obviously.</p>
<p>“Default” means that all <strong>non-actor-isolated</strong> jobs are executed on this executor by default. Jobs executions happens using threads from Cooperative Thread Pool.</p>
<p>“Concurrent” means that it can execute jobs from different tasks concurrently, using multiple threads from CTP (if needed and if they are available).</p>
<p><strong>Serial executor</strong></p>
<p>Special executor type for <strong>actor isolated</strong> jobs. Every actor you write in your code in Swift Concurrency has its own serial executor. Serial executor runs jobs on the Cooperative Thread Pool too, but it reserves a single thread from the pool to do it. (This thread can vary at different moments for the same actor, but during any particular job, it remains the same). "Serial" means executing jobs one after another, sequentially, one at a time.</p>
<blockquote>
<p>⚠️ : But don’t be misled by the “serial” part of the name of serial executor. Actors are not like serial queues because of <a target="_blank" href="https://www.donnywals.com/actor-reentrancy-in-swift-explained/"><strong>reentrancy</strong></a>.</p>
<p>💡: When we say <strong>actor context</strong> (aka <strong>actor executor</strong> or <strong>actor isolation</strong>) it basically means that <strong>execution context</strong> aka <strong>executor</strong> of that actor.</p>
</blockquote>
<p><strong>Main executor</strong></p>
<p>Special executor that handles <strong>main actor isolated</strong> jobs and <em>represents the main thread.</em></p>
<p>Executes jobs on Main thread (technically they simply go to Main Dispatch Queue).</p>
<p>We can visualize how a job is mapped to its executor:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741484619535/4722086d-b606-408d-83e9-ddc5b72c82e2.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-executors-and-threads">Executors and Threads</h3>
<p>Worth to mention that even though execution context after await always remains same as before await, it doesn’t mean that thread will remain the same in this case. At least in most cases.</p>
<ul>
<li><p>For tasks that are not actor-isolated, the continuation will run on the global concurrent executor (i.e., the cooperative thread pool). However, the thread may differ before and after the continuation, although it will certainly not be the main thread.</p>
<pre><code class="lang-swift">  <span class="hljs-type">Task</span>.detached {
      <span class="hljs-comment">// global concurrent executor, some thread from CTP</span>
      someSyncFunction()

      await someAsyncFunction() <span class="hljs-comment">// executor of someAsyncFunction</span>

      <span class="hljs-comment">// still global concurrent executor, maybe different thread from CTP</span>
      someSyncFunction()
  }
</code></pre>
</li>
<li><p>For some actor (not main one) - continuation will run on the same serial actor executor but thread can be different from original one that was executing the task before suspension point.</p>
<pre><code class="lang-swift">  actor <span class="hljs-type">SomeActor</span> {
      @<span class="hljs-type">MainActor</span>
      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">someAsyncFunction</span><span class="hljs-params">()</span></span> async {}

      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">someSyncFunction</span><span class="hljs-params">()</span></span> {}

      <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">someMethod</span><span class="hljs-params">()</span></span> async {
          <span class="hljs-comment">// same actor executor, some thread from CTP</span>
          someSyncFunction()

          await someAsyncFunction() <span class="hljs-comment">// executor of someAsyncFunction (main executor)</span>

          <span class="hljs-comment">// same actor executor, maybe different thread from CTP</span>
          someSyncFunction()
      }
  }
</code></pre>
</li>
<li><p>But main actor is special - continuation will run on the main actor and main thread as well for both before and after suspension point.</p>
<pre><code class="lang-swift">  <span class="hljs-type">Task</span> { @<span class="hljs-type">MainActor</span> <span class="hljs-keyword">in</span>
      <span class="hljs-comment">// main actor executor, main thread</span>
      someSyncFunction()

      await someAsyncFunction() <span class="hljs-comment">// executor of someAsyncFunction</span>

      <span class="hljs-comment">// main actor executor, main thread</span>
      someSyncFunction()
  }
</code></pre>
</li>
</ul>
<h3 id="heading-recap-for-full-picture">Recap for Full Picture</h3>
<p>Let’s go bottom-up and summarise everything once again.</p>
<p><strong>Cores and Threads</strong></p>
<p>In Swift Concurrency, to avoid thread explosion and for optimal parallel performance, threads live in <strong>Cooperative Thread Pool</strong> and threads count is limited to CPU cores number.</p>
<p><strong>Main thread</strong> is not the part of <strong>Cooperative Thread Pool.</strong></p>
<p><strong>Executors</strong></p>
<p>Executor is a service that executes <strong>jobs</strong>. All executors except main executor borrow threads from CTP.</p>
<p><strong>Main executor</strong> uses main queue for main actor isolated jobs/tasks.</p>
<p><strong>Serial executor</strong> is a special executor type for actors to do actor isolated jobs/tasks (except main actor of course).</p>
<p>All other jobs goes to <strong>default concurrent executor</strong>.</p>
<p><strong>Tasks</strong></p>
<p>Tasks are <strong>units of asynchronous work</strong> you can run from your code. Tasks serve as a bridge between sync and async contexts. Tasks consist of the jobs.</p>
<p><strong>Jobs</strong></p>
<p>Jobs are the <strong>basic</strong> units of <strong>schedulable</strong> <strong>synchronous</strong> work that occurs between <strong>suspension points</strong> within a Task. Every job has it’s target executor based on it’s actor isolation. Actors allow us to <strong>isolate jobs</strong> in the tasks to safely share their state with other tasks. Code inside task is separated into jobs based on the structure of awaits for async function inside of it (which are potential suspension points). Jobs are created by runtime. But their structure is defined at compiling the awaits structure of async functions calls.</p>
<p><strong>Async/await</strong></p>
<p>Async function is special function that can be suspended while awaiting another async function to complete. Suspension point is the point in an <strong>async function</strong> where execution can be paused while another async function call is <strong>awaited</strong> and resumed later with <strong>continuation</strong>. The continuation is a <strong>snapshot of async function execution state</strong> that can be resumed later.</p>
<p><strong>Full scheme:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741484791561/8746cefd-f348-400c-8c81-9a3d5a34ae31.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-final-thoughts"><strong>Final thoughts</strong></h1>
<p>Swift concurrency is a powerful tool that eliminates callback hell, offers a cleaner syntax, and reduces concerns like forgotten completion handlers. Its focus on readability, error handling and data-race safety makes it a significant step forward. However, it comes with a steep learning curve. Compared to GCD, which was simpler to grasp initially but exposed its limitations as complexity grew, Swift concurrency demands more effort upfront.</p>
<p>If you interested to know more about Swift Concurrency internals, I recommend <a target="_blank" href="https://swiftrocks.com/how-async-await-works-internally-in-swift">this article</a> for reading as well.</p>
<p>Swift concurrency requires time and practice to fully understand. Be patient and prepared, as reading articles alone probably won’t be enough - you’ll need hands-on experience, trial and error, and, yes, a few “grabbles” along the way.</p>
<p>If you spot any inaccuracies or misleading points in this article, please share them in the comments - your feedback helps everyone!</p>
<p>See you in the next article!</p>
]]></content:encoded></item><item><title><![CDATA[async let vs Task group]]></title><description><![CDATA[Hey iOS folks!
In Swift, Structured Concurrency tasks are represented by async let and task groups. While both serve the same purpose, their lifecycles work slightly differently. Today, we'll explore these differences with examples.

⚠️ : I assume yo...]]></description><link>https://vbat.dev/async-let-vs-task-group</link><guid isPermaLink="true">https://vbat.dev/async-let-vs-task-group</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[Swift Concurrency]]></category><category><![CDATA[async let]]></category><category><![CDATA[Task Group]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Mon, 17 Feb 2025 18:42:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739890032924/e0521dfd-5b5d-4801-ac98-3283f7a731ec.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey iOS folks!</p>
<p>In Swift, Structured Concurrency tasks are represented by <code>async let</code> and <strong>task groups</strong>. While both serve the same purpose, their lifecycles work slightly differently. Today, we'll explore these differences with examples.</p>
<blockquote>
<p>⚠️ : I assume you are familiar with the basic concepts of <strong>Swift Concurrency</strong>. If not, you can start <a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/">here</a>.</p>
</blockquote>
<h3 id="heading-async-let"><strong>async let</strong></h3>
<p>With <code>async let</code>, you can start multiple tasks that <strong>run concurrently</strong>.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchData</span><span class="hljs-params">()</span></span> async {
    async <span class="hljs-keyword">let</span> first = fetchPart1()
    async <span class="hljs-keyword">let</span> second = fetchPart2()
    async <span class="hljs-keyword">let</span> third = fetchPart3()

    <span class="hljs-keyword">let</span> result = await (first, second, third)

    <span class="hljs-built_in">print</span>(result)
}
</code></pre>
<p>Although it is not explicitly stated in code, three new child tasks were created here, one for each <strong>async let.</strong></p>
<p><code>async let</code> can work with both sync and async functions:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchPart1</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Int</span> { ... }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchPart2</span><span class="hljs-params">()</span></span> async -&gt; <span class="hljs-type">Int</span> { ... }
</code></pre>
<p>Moreover, it actually works with any expression:</p>
<pre><code class="lang-swift">async <span class="hljs-keyword">let</span> number = <span class="hljs-number">123</span>
async <span class="hljs-keyword">let</span> str = <span class="hljs-string">"Hello World!"</span>
</code></pre>
<blockquote>
<p><strong>⁉️ : How it works?</strong> Under the hood, it wraps the initializer expression in a <strong>separate, concurrently executing child task</strong>. The child task starts running as soon as the <code>async let</code> is encountered, using the default concurrent executor.</p>
<pre><code class="lang-swift">async <span class="hljs-keyword">let</span> first = fetchPart1()
async <span class="hljs-keyword">let</span> second = fetchPart2()
async <span class="hljs-keyword">let</span> number = <span class="hljs-number">123</span>
async <span class="hljs-keyword">let</span> str = <span class="hljs-string">"Hello World!"</span>

<span class="hljs-comment">// is converted into something like this:</span>

<span class="hljs-keyword">let</span> first = <span class="hljs-type">ChildTask</span> {
    fetchPart1()
}
<span class="hljs-keyword">let</span> second = <span class="hljs-type">ChildTask</span> {
    await fetchPart2()
}
<span class="hljs-keyword">let</span> number = <span class="hljs-type">ChildTask</span> {
    <span class="hljs-number">123</span>
}
<span class="hljs-keyword">let</span> str = <span class="hljs-type">ChildTask</span> {
    <span class="hljs-string">"Hello World!"</span>
}
</code></pre>
</blockquote>
<p>The <strong>lifecycle</strong> of <code>async let</code> is bound to the <strong>local scope</strong> where it is created, such as a function, closure, or <code>do/catch</code> block. When execution exits this scope — either normally or due to an error — all tasks created with <code>async let</code> will be <strong>implicitly cancelled</strong> <strong>and</strong> <strong>awaited</strong>.</p>
<blockquote>
<p>⚠️ <strong>:</strong> Even if you don’t explicitly await an <code>async let</code>, it will be awaited at the end of the local scope. This means an <code>async let</code> group always runs as long as its longest-running child task.</p>
<p>⚠️ : Keep in mind that <strong>cancelling the task does not stop the task</strong>, it only marks that results are not gonna be needed. Swift concurrency uses a cooperative cancellation model. Each task checks whether it has been canceled at the appropriate points in its execution (with <strong>Task.isCancelled</strong> or <strong>Task.checkCancellation()</strong>), and responds to cancellation appropriately. Depending on what work the task is doing, responding to cancellation usually means one of the following:</p>
<ul>
<li><p>Throwing an error like <code>CancellationError</code></p>
</li>
<li><p>Returning <code>nil</code> or an empty collection</p>
</li>
<li><p>Returning the partially completed work</p>
</li>
</ul>
</blockquote>
<p>Another important point to highlight can be observed here:</p>
<pre><code class="lang-swift">async <span class="hljs-keyword">let</span> result1 = task1()
async <span class="hljs-keyword">let</span> result2 = task2()

<span class="hljs-keyword">let</span> results = await (result1, result2)
</code></pre>
<p>Pay close attention - it may not be immediately obvious, but while child tasks created with <code>async let</code> run concurrently, awaiting them in a tuple is <strong>sequential</strong>. Swift evaluates tuple elements from left to right, following its expression evaluation rules.</p>
<p>To clarify, all the expressions below are equivalent in terms of the order of sequential <code>await</code>ing each result:</p>
<pre><code class="lang-swift">await (result1, result2)
<span class="hljs-comment">// or</span>
(await result1, await result2)
<span class="hljs-comment">// or</span>
await result1
await result2
<span class="hljs-comment">// awaiting order will be the same</span>
</code></pre>
<p>This can lead to a tricky behavior when child tasks throw errors, but we will discuss that later in the error propagation edge cases section.</p>
<blockquote>
<p>💡: I would like to encourage you to read <a target="_blank" href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0317-async-let.md"><strong>async let</strong></a> proposal for more insights, such as why <code>async var</code> is not allowed or why passing an <code>async let</code> to an escaping closure is prohibited.</p>
</blockquote>
<h3 id="heading-taskgroup">TaskGroup</h3>
<p>If you need to create a <strong>dynamic</strong> number of concurrent tasks, a <strong>task group</strong> is the better choice:</p>
<pre><code class="lang-swift">
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchData</span><span class="hljs-params">(<span class="hljs-built_in">count</span>: Int)</span></span> async {
    <span class="hljs-keyword">var</span> results = [<span class="hljs-type">String</span>]()

    await withTaskGroup(of: <span class="hljs-type">String</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">for</span> index <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..&lt;<span class="hljs-built_in">count</span> {
            group.addTask {
                await <span class="hljs-keyword">self</span>.fetchPart(index)
            }
        }
        <span class="hljs-keyword">for</span> await result <span class="hljs-keyword">in</span> group {
            results.append(result)
        }
    }

    <span class="hljs-built_in">print</span>(results)
}
</code></pre>
<p>Keep in mind that <code>group: TaskGroup</code> param inside task group closure is actually <a target="_blank" href="https://developer.apple.com/documentation/swift/asyncsequence"><code>AsyncSequence</code></a>. So instead of <code>for await</code> we can use another iterator approach with <code>.next()</code> method that produces the same result:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> result = await group.next() {
    results.append(result)
}
</code></pre>
<p>Lifecycle of <strong>task group</strong> is bound to the closure inside <code>withTaskGroup</code> function. But logic is a bit more complex than with async let:</p>
<ul>
<li><p>If execution leaves the closure normally without awaiting child tasks, they will be implicitly awaited (but not cancelled).</p>
</li>
<li><p>If execution leaves the closure due to an error, child tasks will be both implicitly cancelled and awaited.</p>
</li>
</ul>
<p>For example, if we remove the <code>for await</code> part from the task group code above, it won’t just go to printing the results (which would be empty, obviously). Instead, it will await all child tasks first until the each of them is fully completed.</p>
<blockquote>
<p>💡 : An important difference from <code>async let</code> is the order in which results are awaited. Unlike <code>async let</code>, where the order is code-driven, a task group follows a <strong>"first finished → first handled"</strong> approach, based on how an <code>AsyncSequence</code> works.</p>
</blockquote>
<h2 id="heading-lifecycle-edge-cases">Lifecycle edge cases</h2>
<p>We briefly covered the lifecycle logic for implicit cancellation and awaiting of structured tasks.</p>
<blockquote>
<p>The <strong>lifecycle</strong> of <code>async let</code> is bound to the <strong>local scope</strong> where it is created, such as a function, closure, or <code>do/catch</code> block. When execution exits this scope — either normally or due to an error — all tasks created with <code>async let</code> will be <strong>implicitly cancelled</strong> and <strong>awaited</strong>.</p>
<p>Lifecycle of <strong>task group</strong> is bound to the closure inside <code>withTaskGroup</code> function. But logic is a bit more complex than with async let:</p>
<ul>
<li><p>If execution leaves the closure normally without awaiting child tasks, they will be implicitly awaited (but not cancelled).</p>
</li>
<li><p>If execution leaves the closure due to an error, child tasks will be both implicitly cancelled and awaited.</p>
</li>
</ul>
</blockquote>
<p>It may seem a bit complicated already, so let’s try to clarify that with examples.</p>
<blockquote>
<p>⚠️ <strong>Disclaimer</strong>: Not awaiting structured tasks may not be a good idea in some cases. Even if you want to create parallel "fire and forget" operations without caring about the results, structured tasks might not work as you expected. Remember that both <code>async let</code> and task groups have implicit awaiting, meaning you'll always wait for the longest child task to complete before proceeding. This makes true "fire and forget" behavior impossible unless you wrap the tasks in an unstructured root task or simply use an unstructured tasks instead.</p>
</blockquote>
<h3 id="heading-what-if-we-dont-await-child-tasks-and-leave-the-local-scope-normally"><strong>What if we don’t await child tasks and leave the local scope normally?</strong></h3>
<p><code>async let</code></p>
<p>When we we <strong>leave</strong> <strong>local scope normally without awaiting child tasks</strong>, async let’s child tasks will be <strong>implicitly cancelled</strong> and <strong>implicitly awaited:</strong></p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fast</span><span class="hljs-params">()</span></span> async {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-comment">/// If current task is canceled before the time ends,</span>
        <span class="hljs-comment">/// Task.sleep function throws `CancellationError`.</span>
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">5_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast ended"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">slow</span><span class="hljs-params">()</span></span> async {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow ended"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">go</span><span class="hljs-params">()</span></span> async {
    async <span class="hljs-keyword">let</span> f = fast()
    async <span class="hljs-keyword">let</span> s = slow()

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving local scope"</span>)
}

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    leaving local scope</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
<span class="hljs-comment">//    fast cancelled CancellationError()</span>
<span class="hljs-comment">//    fast ended</span>
</code></pre>
<p>Keep in mind that it will implicitly cancel and await tasks in reverse order, starting from the last defined <code>async let</code> child task. In this example, it implicitly cancels and awaits the <code>slow</code> task first, and then the <code>fast</code> task will also be implicitly cancelled and awaited.</p>
<p>If your last defined async let task will take long time to finish and will not handle cancellation properly, previous ones will not be cancelled until first one is completed, which will affect total group completion time. For example, if we make slow one “more slower” and ignore cancellation for it:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">slow</span><span class="hljs-params">()</span></span> async {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow cancelled"</span>, error)
    }
    sleep(<span class="hljs-number">10</span>) <span class="hljs-comment">// this sleep will ignore cancellation</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow ended"</span>)
}

<span class="hljs-comment">// fast and go funcs are the same</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    leaving local scope</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    fast ended // after 5 secs</span>
<span class="hljs-comment">//    slow ended // after 10 sec</span>
</code></pre>
<p>As you can see, this time <code>fast</code> wasn’t even cancelled this time, because it managed to complete before <code>slow</code> is completed (<code>slow</code> was implicitly cancelled and implicitly awaited).</p>
<p><code>Task group</code></p>
<p>When we leave <code>Task group</code> closure <strong>normally without awaiting child tasks</strong>, task group’s child tasks will be implicitly awaited but <strong>not cancelled</strong>:</p>
<pre><code class="lang-swift">await withTaskGroup(of: <span class="hljs-type">Void</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
    group.addTask {
        await fast()
    }
    group.addTask {
        await slow()
    }

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving task group closure"</span>)
}

<span class="hljs-comment">// fast and slow funcs are the same</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    leaving task group closure</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast ended // after 5 sec</span>
<span class="hljs-comment">//    slow ended // after 20 sec</span>
</code></pre>
<h3 id="heading-what-if-we-do-not-await-and-leave-the-local-scope-due-to-error"><strong>What if we do not await and leave the local scope due to error?</strong></h3>
<blockquote>
<p>💡: Remember, that if child task is not awaited explicitly, throwing an error inside child task will not be propagated and caught outside (Rule: not awaited explicitly → not propagated). Task group will only implicitly await it’s child tasks in this case (no implicit cancellation) as it did in previous case without error.</p>
</blockquote>
<p>If we don’t await child tasks but error is somehow thrown inside <code>async let</code>’s local scope or Task group’s closure and it leaves the scope/closure uncaught, child tasks will be <strong>implicitly cancelled and implicitly awaited,</strong> and after that error will be propagated outside:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">try</span> await withThrowingTaskGroup(of: <span class="hljs-type">Void</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
    group.addTask {
        <span class="hljs-keyword">try</span> await fast()
    }
    group.addTask {
        <span class="hljs-keyword">try</span> await slow()
    }

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving task group closure"</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError</span>()
}

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    leaving task group closure</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    fast cancelled CancellationError()</span>
<span class="hljs-comment">//    fast ended</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
<span class="hljs-comment">//    external catch TestError() // error caught outside task group closure</span>
</code></pre>
<p>Opposite to async let where the implicit cancellation + awaiting happens in order opposite to <code>async let</code>’s declaration, for task group:</p>
<ul>
<li><p>there is not defined order for cancellation and awaiting (random order)</p>
</li>
<li><p>it implicitly cancels all child tasks first, then implicitly awaits all of them.</p>
</li>
</ul>
<p>We can check that in this example:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fast</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">5_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast cancelled"</span>, error)
    }
    sleep(<span class="hljs-number">5</span>) <span class="hljs-comment">// this sleep will ignore cancellation</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast ended"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">slow</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow cancelled"</span>, error)
    }
    sleep(<span class="hljs-number">10</span>) <span class="hljs-comment">// this sleep will ignore cancellation</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow ended"</span>)
}

<span class="hljs-comment">// code of withThrowingTaskGroup is the same as previous</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    leaving task group closure</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    fast cancelled CancellationError()</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    fast ended // after 5 seconds</span>
<span class="hljs-comment">//    slow ended // after 10 seconds</span>
<span class="hljs-comment">//    external catch TestError() // error caught outside task group closure</span>
</code></pre>
<p>As you can see, it implicitly cancels both <code>fast</code> and <code>slow</code> first, then awaits <code>fast</code> and <code>slow</code> after that.</p>
<h3 id="heading-what-if-we-await-child-tasks-and-error-is-handled-locally"><strong>What if we await child tasks and error is handled locally?</strong></h3>
<p><code>async let</code></p>
<p>async let’s child tasks will be implicitly cancelled and implicitly awaited.</p>
<p>Let’s say we throw an error in <code>fast</code> and <code>slow</code> now:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fast</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">5_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"fast ended"</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError1</span>() <span class="hljs-comment">// &lt;- HERE</span>
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">slow</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow started"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">try</span> await <span class="hljs-type">Task</span>.sleep(nanoseconds: <span class="hljs-number">10_000_000_000</span>)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow cancelled"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"slow ended"</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-type">TestError2</span>() <span class="hljs-comment">// &lt;- HERE</span>
}

async <span class="hljs-keyword">let</span> f = fast()
async <span class="hljs-keyword">let</span> s = slow()
<span class="hljs-keyword">do</span> {
    <span class="hljs-keyword">try</span> await (f, s)
} <span class="hljs-keyword">catch</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"caught error locally"</span>, error)
}
<span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving local scope"</span>)

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast ended // after 5 seconds</span>
<span class="hljs-comment">//    caught error locally TestError1()</span>
<span class="hljs-comment">//    leaving local scope</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
</code></pre>
<p>See how <code>TestError2</code> from <code>slow</code> wasn’t even caught because of await order for async let’s (we await <code>fast</code> first and <code>slow</code> second). We caught <code>TestError1</code> and left <code>do</code> block, leaving <code>slow</code> unawaited, so it was implicitly cancelled and awaited after we left the local scope.</p>
<p>Order of implicit cancellation + awaiting is the same as before for async let - reversed to async let declaration order.</p>
<p>Keep in mind that the order of awaiting can directly affect the order of error propagation. If we await the <code>slow</code> task first, <code>TestError2</code> will be propagated, even though the <code>fast</code> task ended first and threw an error first — it was never caught because fast was never awaited on practice:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">try</span> await (s, f) <span class="hljs-comment">// changed the order of awaits</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast ended // after 5 seconds</span>
<span class="hljs-comment">//    slow ended // after 10 seconds</span>
<span class="hljs-comment">//    caught error locally TestError2()</span>
<span class="hljs-comment">//    leaving local scope</span>
</code></pre>
<p><code>Task group</code></p>
<p>Task group’s child tasks will be implicitly awaited but not cancelled.</p>
<p>Let’s say again that we throw an error in <code>fast</code> and <code>slow</code> :</p>
<pre><code class="lang-swift"><span class="hljs-keyword">try</span> await withThrowingTaskGroup(of: <span class="hljs-type">Void</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
    group.addTask {
        <span class="hljs-keyword">try</span> await fast()
    }
    group.addTask {
        <span class="hljs-keyword">try</span> await slow()
    }
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">for</span> <span class="hljs-keyword">try</span> await result <span class="hljs-keyword">in</span> group {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received: \(result)"</span>)
        }
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"caught error locally"</span>, error)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving task group closure"</span>)
}

<span class="hljs-comment">// fast and slow are the same as in previous example</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast ended // after 5 secs</span>
<span class="hljs-comment">//    caught error locally TestError1()</span>
<span class="hljs-comment">//    leaving task group closure</span>
<span class="hljs-comment">//    slow ended // after 10 secs</span>
</code></pre>
<p>When <code>fast</code> throws <code>TestError1</code>, we catch it locally and after we leave the task group closure, <code>slow</code> is implicitly awaited. Even though <code>slow</code> throws <code>TestError2</code> error eventually, we didn’t catch it (because it is not awaited anymore → not propagated).</p>
<h3 id="heading-what-if-we-await-child-tasks-and-error-is-propagated-outside-local-scope"><strong>What if we await child tasks and error is propagated outside local scope?</strong></h3>
<p><code>async let</code></p>
<p>async let’s child tasks are implicitly cancelled and implicitly awaited.</p>
<p>Let’s remove local do/catch block to test that case:</p>
<pre><code class="lang-swift">async <span class="hljs-keyword">let</span> f = fast()
async <span class="hljs-keyword">let</span> s = slow()
<span class="hljs-keyword">try</span> await (f, s)
<span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving local scope"</span>)

<span class="hljs-comment">// fast and slow are the same as in previous example</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    fast ended // after 5 secs</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
<span class="hljs-comment">//    extenral catch TestError1()</span>
</code></pre>
<p>After <code>fast</code> throws <code>TestError1</code> , execution jumps to external catch block and leaves <code>slow</code> unawaited. <code>slow</code> is implicitly cancelled and implicitly awaited. <code>TestError1</code> is propagated and caught outside of the local scope.</p>
<p><code>Task group</code></p>
<p>task group’s child tasks are implicitly cancelled and implicitly awaited.</p>
<p>Let’s remove local do/catch block to test that case as well:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">try</span> await withThrowingTaskGroup(of: <span class="hljs-type">Void</span>.<span class="hljs-keyword">self</span>) { group <span class="hljs-keyword">in</span>
    group.addTask {
        <span class="hljs-keyword">try</span> await fast()
    }
    group.addTask {
        <span class="hljs-keyword">try</span> await slow()
    }
    <span class="hljs-keyword">for</span> <span class="hljs-keyword">try</span> await result <span class="hljs-keyword">in</span> group { <span class="hljs-comment">// Removed do/catch here</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received: \\(result)"</span>)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"leaving task group closure"</span>)
}

<span class="hljs-comment">// fast and slow are the same as in previous example</span>

<span class="hljs-comment">//    Prints:</span>
<span class="hljs-comment">//    slow started</span>
<span class="hljs-comment">//    fast started</span>
<span class="hljs-comment">//    fast ended // after 5 secs</span>
<span class="hljs-comment">//    slow cancelled CancellationError()</span>
<span class="hljs-comment">//    slow ended</span>
<span class="hljs-comment">//    external catch TestError1()</span>
</code></pre>
<p>After <code>fast</code> throws <code>TestError1</code> , execution jumps to external catch block and leaves <code>slow</code> unawaited. <code>slow</code> is implicitly cancelled and awaited. <code>TestError1</code> is propagated and caught outside of task group closure.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<blockquote>
<p>⚠️ : If you have error propagation logic, a task group may be a better choice than <code>async let</code>. The task group’s "first thrown, first caught" logic is more predictable and won’t be affected by the order of awaiting, unlike <code>async let</code>, where error propagation can be influenced by the awaiting order. Task groups are more suitable for cases where you want to "fail fast" compared to <code>async let</code>.</p>
</blockquote>
<p>As you could notice, there were quite a few cases to consider. Hopefully, your brain isn’t boiling just yet. Luckily, the logic behind these cases is simpler than it may seem. To make it more visual and easier to understand, we can build a diagram:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739816327497/d0dcbbfd-6956-4d9a-a87d-bb6d087e5400.png" alt class="image--center mx-auto" /></p>
<p>If you spot any inaccuracies or misleading points in this post, please share them in the comments - your feedback helps everyone!</p>
<p>Good luck on your Swift Concurrency journey! See you in the next articles!</p>
]]></content:encoded></item><item><title><![CDATA[Coordinators & SwiftUI]]></title><description><![CDATA[If you prefer to watch video you can find it here.
Hey iOS folks,
With the release of iOS 18, many teams are likely moving towards adopting iOS 16 as the new minimum, bringing newer SwiftUI features, such as NavigationStack, into focus. Opinions on S...]]></description><link>https://vbat.dev/coordinators-swiftui</link><guid isPermaLink="true">https://vbat.dev/coordinators-swiftui</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[UIkit]]></category><category><![CDATA[coordinator]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 27 Oct 2024 19:10:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730050423533/2cd11e48-93da-40fc-b8df-ceed9e8dab8f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>If you prefer to watch video you can find it</strong> <a target="_blank" href="https://youtu.be/SaKWfvGi5rA"><strong>here</strong></a><strong>.</strong></p>
<p>Hey iOS folks,</p>
<p>With the release of iOS 18, many teams are likely moving towards adopting iOS 16 as the new minimum, bringing newer SwiftUI features, such as <strong>NavigationStack</strong>, into focus. Opinions on SwiftUI navigation in general are quite mixed. Some developers have embraced it and advocate for it, while others argue that even with NavigationStack, SwiftUI’s navigation system isn't fully ready for large-scale apps.</p>
<p>Many teams, including mine, use SwiftUI for building UI but still rely on UIKit’s navigation by wrapping SwiftUI views into <strong>UIHostingController</strong> .</p>
<p>In this article, I aim to explore whether SwiftUI, particularly NavigationStack, can be used with the Coordinator approach, how it can be adapted to SwiftUI, and how to build a SwiftUI-based Coordinator with minimal code - without relying on complex third-party libraries.</p>
<p>We will revisit what the Coordinator pattern is, explore how it can be implemented with both UIKit and SwiftUI, and compare the two implementations while discussing their limitations.</p>
<h3 id="heading-what-is-coordinator">What is coordinator?</h3>
<p>Like many great concepts in software design, the <strong>Coordinator</strong> pattern is quite straightforward.</p>
<p>Simply put, a Coordinator is an object that <strong>manages the navigation flow</strong>.</p>
<p><strong>What is the navigation flow?</strong></p>
<p>In simple terms, it's the sequence of screens in an app, connected by transitions.</p>
<p>These transitions can vary - it can be pushing, presenting, or even custom transitions, depending on your needs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730050680672/9a090eb4-db7d-430b-8cb7-6b13eb7d0b8d.png" alt class="image--center mx-auto" /></p>
<p><strong>What does it mean to manage the navigation flow?</strong></p>
<p>It means having full control over navigation flow through the app. Not only can we push new screens when buttons are tapped by user, but we can also change the flow state programmatically based on events happening outside the current screen.</p>
<p><strong>What are the benefits of using a Coordinator?</strong></p>
<p>Instead of letting views (and view controllers) handle navigation directly, the Coordinator takes over.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730050724161/de14fac3-5886-489b-83d4-6875693000da.png" alt class="image--center mx-auto" /></p>
<p>By decoupling navigation logic from views, we reduce dependencies between views, making it easier to maintain, control, test and reuse. Since we control the state of the navigation flow, handling complex scenarios - even programmatically - becomes straightforward.</p>
<p>Coordinators <strong>scale</strong> exceptionally well. You can <strong>nest</strong> them, allowing sub-coordinators to create hierarchies within the navigation flow, organizing it into a tree structure that simplifies scaling. This approach fits neatly into modular design. If you're following a modular architecture with feature modules, you can easily integrate a feature-specific Coordinator from the composition root.</p>
<p><strong>Router vs. Coordinator</strong></p>
<p>You might be wondering: "We already use the <strong>Router</strong> pattern in our project - so what's the difference?"</p>
<p>Both the <strong>Router</strong> and <strong>Coordinator</strong> handle navigation, obviously.</p>
<p>A <strong>Router’s</strong> scope can vary depending on how a team defines it. In my experience, a Router typically manages the navigation routes for a single screen, as seen in patterns like <strong>MVVM-R</strong> (<strong>Model-View-ViewModel-Router</strong>) or <strong>VIPER</strong>. In some cases, a Router might handle the flow of multiple screens or even manage the entire app’s navigation - though this is more common in smaller projects and not a widely adopted approach.</p>
<p>A <strong>Coordinator</strong>, on the other hand, typically controls the flow of the screens, not just individual screen routes. While it’s possible for a Coordinator to manage just one screen in certain cases, its main strength lies in controlling the navigation for a set of related screens like an some feature flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730050664301/74fe7b8d-dc80-4517-8ee3-05b2dd32ef36.png" alt class="image--center mx-auto" /></p>
<p>Another difference is that <strong>coordinators usually have a hierarchical structure</strong>: Coordinators can be nested, with parent Coordinators managing child Coordinators, allowing for complex tree-like flows across the app.</p>
<p>Hopefully, this gives you a clearer understanding of Coordinators. Next, we can explore examples of how to implement them in UIKit and SwiftUI.</p>
<h1 id="heading-plain-old-uikit-coordinator">Plain Old UIKit Coordinator</h1>
<p>First, let’s define the coordinator type:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Coordinator</span>: <span class="hljs-title">CoordinatorFinishDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> <span class="hljs-comment">// 1</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">finish</span><span class="hljs-params">()</span></span> <span class="hljs-comment">// 2</span>
    <span class="hljs-keyword">var</span> finishDelegate: <span class="hljs-type">CoordinatorFinishDelegate?</span> { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> } <span class="hljs-comment">// 3</span>
}
</code></pre>
<ol>
<li><p><code>start()</code> : This method starts the coordinator flow. It provides the flexibility to create a coordinator and activate it only when we’re ready if we need so.</p>
</li>
<li><p><code>finish()</code> : This method is called to end the flow, such as dismissing the screen flow.</p>
</li>
<li><p>When a coordinator finishes, there needs to be a mechanism to notify the parent coordinator so that it can remove the child coordinator from its list or perform additional tasks. Typically, the <code>finishDelegate</code> is the parent coordinator. To follow the <a target="_blank" href="https://en.wikipedia.org/wiki/Principle_of_least_privilege">principle of least privilege</a>, we avoid giving the child direct access to control the parent. Instead, we hide the parent behind a <code>CoordinatorFinishDelegate</code> protocol.</p>
</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">CoordinatorFinishDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didFinish</span><span class="hljs-params">(childCoordinator: Coordinator)</span></span>
}
</code></pre>
<blockquote>
<p><strong>NOTE</strong>: By using a <code>start()</code> method, we separate coordinator <strong>initialization</strong> from <strong>activation</strong>. There are several reasons for this:</p>
<ol>
<li><p><strong>Separation of Concerns</strong>: Instantiation and execution are conceptually different -initialization creates the object, while <code>start()</code> triggers the flow. This separation makes the code easier to understand and maintain, as the responsibilities of creation and execution are clearly decoupled.</p>
</li>
<li><p><strong>Deferred Execution</strong>: This approach allows for deferred execution of the navigation flow, which can be useful in certain scenarios where immediate navigation isn't desired.</p>
</li>
<li><p><strong>Dependency Injection</strong>: Coordinators often need dependencies like services, data, or other coordinator before starting their flow. An explicit <code>start()</code> method provides the flexibility to inject these dependencies before navigation begins.</p>
</li>
<li><p><strong>Multiple Entry Points</strong>: In some cases, a coordinator might have multiple entry points depending on the context. Instead of complicating the initialization logic, having different <code>start()</code> methods allows for more straightforward handling of different use cases.</p>
</li>
</ol>
</blockquote>
<p>Next we need to define how coordinators will work with child coordinators:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">FlowCoordinator</span>: <span class="hljs-title">Coordinator</span> </span>{
    <span class="hljs-keyword">var</span> childCoordinator: <span class="hljs-type">Coordinator?</span> { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }
}

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">CompositionCoordinator</span>: <span class="hljs-title">Coordinator</span> </span>{
    <span class="hljs-keyword">var</span> childCoordinators: [<span class="hljs-type">Coordinator</span>] { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }
}
</code></pre>
<p><code>FlowCoordinator</code> is designed for typical navigation processes involving push and present actions. It follows a linear flow and therefore has only one <code>childCoordinator</code>.</p>
<p><code>CompositionCoordinator</code>, on the other hand, manages multiple child coordinators. An example would be a <code>TabBarCoordinator</code>, which acts as a <code>CompositionCoordinator</code> for each tab's coordinator.</p>
<p>But why don’t we include a <code>childCoordinators</code> array in the base coordinator protocol? It's a reasonable question. While we <em>could</em> add a <code>childCoordinators</code> array to the base <code>Coordinator</code> protocol - something many implementations do - it's better to adhere to the <strong>YAGNI</strong> (You Aren't Gonna Need It) and <strong>Interface Segregation</strong> principles here. As you'll see shortly, this approach simplifies the implementation.</p>
<blockquote>
<p><strong>NOTE</strong>: In some Coordinator implementations, you may find a <code>UINavigationController</code> defined in the coordinator protocol. Adding it allows you to create default methods in a protocol extension for common navigation tasks, such as <code>push</code>, <code>present</code>, <code>popToRoot</code>, or <code>dismissToRoot</code>, which can then be reused throughout your project. While this approach adds some complexity, it also makes the solution more ready for universal flows. However, including <code>UINavigationController</code> in the protocol is optional, and there are several reasons why avoiding it can be beneficial:</p>
<ol>
<li><p><strong>Framework Independence</strong>: The Coordinator concept is independent of UIKit, so keeping the coordinator protocol free from framework-specific details helps maintain a cleaner architecture.</p>
</li>
<li><p><strong>Flexibility</strong>: Without restricting the coordinator to <code>UINavigationController</code>, you have the freedom to use other containers for views, such as <code>UIViewController</code>, <code>UITabBarController</code>, or even <code>UIWindow</code>, depending on the specific requirements of the flow.</p>
</li>
</ol>
<p><strong>NOTE</strong>: We can also add default implementations to protocol extensions for finish, child coordinators manipulation and etc.</p>
<pre><code class="lang-swift">
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Coordinator</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">finish</span><span class="hljs-params">()</span></span> {
        finishDelegate?.didFinish(childCoordinator: <span class="hljs-keyword">self</span>)
    }
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">FlowCoordinator</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addChild</span><span class="hljs-params">(<span class="hljs-number">_</span> coordinator: Coordinator)</span></span> {
        childCoordinator = coordinator
        coordinator.finishDelegate = <span class="hljs-keyword">self</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeChild</span><span class="hljs-params">()</span></span> {
        childCoordinator = <span class="hljs-literal">nil</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didFinish</span><span class="hljs-params">(childCoordinator: Coordinator)</span></span> {
        removeChild()
    }
}
</code></pre>
</blockquote>
<p>For specific screen flow like a <strong>feature flow</strong>, coordinator will look like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FeatureCoordinator</span>: <span class="hljs-title">FlowCoordinator</span> </span>{

    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> finishDelegate: (any <span class="hljs-type">CoordinatorFinishDelegate</span>)?

    <span class="hljs-keyword">var</span> childCoordinator: <span class="hljs-type">Coordinator?</span>

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> navigationController: <span class="hljs-type">UINavigationController</span>

    <span class="hljs-keyword">init</span>(navigationController: <span class="hljs-type">UINavigationController</span>) {
        navigationController = navigationController
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> rootScreen = <span class="hljs-type">FirstTabScreen</span>(coordinator: <span class="hljs-keyword">self</span>)
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">UIHostingController</span>(rootView: rootScreen)
        navigationController.setViewControllers([vc], animated: <span class="hljs-literal">false</span>)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pushNextScreen</span><span class="hljs-params">(animated: Bool = <span class="hljs-literal">true</span>)</span></span> {
        <span class="hljs-keyword">let</span> nextScreen = <span class="hljs-type">NextScreen</span>(coordinator: <span class="hljs-keyword">self</span>)
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">UIHostingController</span>(rootView: nextScreen)
        navigationController.pushViewController(vc, animated: animated)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentAnotherScreen</span><span class="hljs-params">(animated: Bool = <span class="hljs-literal">true</span>)</span></span> {
        <span class="hljs-keyword">let</span> anotherScreen = <span class="hljs-type">AnotherScreen</span>(coordinator: <span class="hljs-keyword">self</span>)
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">UIHostingController</span>(rootView: anotherScreen)
        navigationController.present(vc, animated: animated)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">startChildFeature</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> child = <span class="hljs-type">ChildFeatureCoordinator</span>(
            navigationController: navigationController
        )
        addChild(child)
        child.start()
    }
}
</code></pre>
<p>We have a lot of freedom with this approach:</p>
<ol>
<li><p>We can decide and customize how the coordinator starts itself using the injected navigation controller.</p>
</li>
<li><p>We can use both UIKit and SwiftUI views (wrapped with UIHostingController) if needed.</p>
<pre><code class="lang-swift"> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SomeFeatureScreen</span>: <span class="hljs-title">View</span> </span>{

     <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> coordinator: <span class="hljs-type">FeatureCoordinator?</span>

     <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
         <span class="hljs-comment">// some body that calls 'pushNext' and 'presentAnother'</span>
     }

     <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pushNext</span><span class="hljs-params">()</span></span> {
         coordinator?.pushNextScreen()
     }

     <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentAnother</span><span class="hljs-params">()</span></span> {
         coordinator?.presentAnotherScreen()
     }
 }
</code></pre>
<blockquote>
<p><strong>NOTE</strong>: In this example, each view is tied to a specific coordinator type. While this is a limitation, it's generally not a significant issue. If needed, you can use closures and wrapper views to handle multiple screens with different coordinators.</p>
</blockquote>
</li>
</ol>
<ol start="3">
<li><p>We have complete control over the navigation flow, allowing us to modify it programmatically - for example, when handling a deep link.</p>
<pre><code class="lang-swift"> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FeatureDeeplinkHandler</span>: <span class="hljs-title">DeeplinkHandlerProtocol</span> </span>{

     <span class="hljs-comment">// ...</span>

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">canOpenURL</span><span class="hljs-params">(<span class="hljs-number">_</span> url: URL)</span></span> -&gt; <span class="hljs-type">Bool</span> {
         url.absoluteString.hasPrefix(<span class="hljs-string">"deeplink://feature"</span>)
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">openURL</span><span class="hljs-params">(<span class="hljs-number">_</span> url: URL)</span></span> {
         <span class="hljs-keyword">guard</span> canOpenURL(url) <span class="hljs-keyword">else</span> {
             <span class="hljs-keyword">return</span>
         }
         featureCoordinator?.dismissAll { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-keyword">in</span>
             <span class="hljs-keyword">self</span>?.featureCoordinator?.startChildFeature()
         }
     }
 }
</code></pre>
</li>
</ol>
<p>This approach is simple, scalable, and powerful - proven effective in large, mature modular apps. That's great, but what does SwiftUI offer for navigation?</p>
<h1 id="heading-what-does-swiftui-offer">What does SwiftUI offer?</h1>
<p><strong>NavigationStack vs. NavigationView</strong></p>
<p>Before the introduction of <code>NavigationStack</code>, SwiftUI's <code>NavigationView</code> had many limitations. Many common navigation tasks were either difficult or impossible to achieve, such as pushing multiple screens or popping back to the root through several views or recreating navigation flows to open a specific screen sequence.</p>
<p><code>NavigationView</code> struggled with managing state and handling programmatic navigation. Developers often resorted to workarounds or even reinvent the navigation stack, which was far from ideal.</p>
<blockquote>
<p><strong>NOTE</strong>: Since iOS 18, NavigationView is officially deprecated due to these limitations. Apple has made it clear that NavigationStack is the path forward for managing navigation in SwiftUI.</p>
</blockquote>
<p>With iOS 16, Apple addressed these issues by introducing state-based navigation via <strong>NavigationStack</strong>, allowing developers to control navigation state programmatically in a convenient way. This resolved many challenges related to managing navigation state.</p>
<p>Let’s consider a simple example of NavigationStack:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">State</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> path = <span class="hljs-type">NavigationPath</span>()

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">NavigationStack</span>(path: $path) {
            <span class="hljs-type">RootView</span>()
                .navigationDestination(<span class="hljs-keyword">for</span>: <span class="hljs-type">HashableRoute</span>.<span class="hljs-keyword">self</span>) { route <span class="hljs-keyword">in</span>
                    <span class="hljs-keyword">switch</span> route {
                    <span class="hljs-keyword">case</span> .firstScreen:
                        <span class="hljs-type">FirstScreen</span>()
                    <span class="hljs-keyword">case</span> .secondScreen:
                        <span class="hljs-type">SecondScreen</span>()
                    }
                }
        }
    }
}

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">HashableRoute</span>: <span class="hljs-title">Hashable</span> </span>{
    <span class="hljs-keyword">case</span> firstScreen
    <span class="hljs-keyword">case</span> secondScreen
}
</code></pre>
<p>In essence, we can now control the navigation stack by modifying the navigation path, which can contain any <code>Hashable</code> elements. All it takes is wrapping the view in a <code>NavigationStack</code> and providing a path along with all the navigation destinations.</p>
<p>Now let’s explore how we can implement the Coordinator pattern in SwiftUI using <code>NavigationStack</code>. We'll aim to keep the implementation simple, but this will introduce some limitations as a result.</p>
<h1 id="heading-swiftui-coordinator">SwiftUI Coordinator</h1>
<p>Let’s start by defining the coordinator protocols again:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Coordinator</span>: <span class="hljs-title">ObservableObject</span>, <span class="hljs-title">CoordinatorFinishDelegate</span> </span>{

    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Content</span>: <span class="hljs-type">View</span>

    @<span class="hljs-type">MainActor</span> @<span class="hljs-type">ViewBuilder</span> <span class="hljs-keyword">var</span> rootView: <span class="hljs-type">Content</span> { <span class="hljs-keyword">get</span> }

    <span class="hljs-keyword">var</span> finishDelegate: <span class="hljs-type">CoordinatorFinishDelegate?</span> { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">finish</span><span class="hljs-params">()</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">CompositionCoordinator</span>: <span class="hljs-title">Coordinator</span> </span>{
    <span class="hljs-keyword">var</span> childCoordinators: [any <span class="hljs-type">Coordinator</span>] { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }
}

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">FlowCoordinator</span>: <span class="hljs-title">Coordinator</span> </span>{

    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Route</span>: <span class="hljs-type">Routable</span>
    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Destination</span>: <span class="hljs-type">View</span>

    @<span class="hljs-type">MainActor</span> @<span class="hljs-type">ViewBuilder</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">destination</span><span class="hljs-params">(<span class="hljs-keyword">for</span> route: Route)</span></span> -&gt; <span class="hljs-type">Destination</span>

    <span class="hljs-keyword">var</span> childCoordinator: (any <span class="hljs-type">Coordinator</span>)? { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }

    <span class="hljs-keyword">var</span> navigationControllers: [<span class="hljs-type">NavigationController</span>&lt;<span class="hljs-type">Route</span>&gt;] { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }
}
</code></pre>
<p>It looks quite similar to the UIKit version, but there are a few new elements:</p>
<ul>
<li><p>The Coordinator is now an <code>ObservableObject</code>, allowing us to observe navigation state changes.</p>
</li>
<li><p>We have a <code>rootView</code> now. But why? In the UIKit example, we wanted to decouple navigation logic from views, avoiding UI framework details in the coordinator protocol. This remains a good goal, but in SwiftUI, the <code>rootView</code> is necessary for the setup to work. We’ll revisit this point later when discussing limitations.</p>
</li>
<li><p>The <code>start</code> method is gone. As you’ll see, it’s no longer needed due to limitations in presenting child coordinators.</p>
</li>
</ul>
<p>We also have some changes in <code>FlowCoordinator</code>:</p>
<ul>
<li><p><code>Route</code> and <code>Destination</code> are new types defining the routes and screens handled by the coordinator. These will be used to provide destination views for the <code>NavigationStack</code>.</p>
</li>
<li><p><code>NavigationController</code> is our custom analog of <code>UINavigationController</code>, storing the <code>navigationPath</code> and <code>presentedRoute</code> state. We need it to build the state of our coordinator navigation flow.</p>
<pre><code class="lang-swift">  <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NavigationController</span>&lt;<span class="hljs-title">Route</span>: <span class="hljs-title">Routable</span>&gt;: <span class="hljs-title">ObservableObject</span> </span>{

      @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> navigationPath = <span class="hljs-type">NavigationPath</span>()

      @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> presentedRoute: <span class="hljs-type">Route?</span>

      <span class="hljs-comment">//...</span>
  }
</code></pre>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730051306030/e92ff08c-6c4b-4b0a-b9d6-5b300a9138e2.png" alt class="image--center mx-auto" /></p>
<p>  We can think of our coordinator navigation flow as a chain of navigation controllers, each managing its navigation path and presenting the next one in the chain.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730051422400/1ce97c13-3b91-4732-8c6a-9f3ae6c5ee10.png" alt class="image--center mx-auto" /></p>
<p>  They are all combined together and stored in the <code>navigationControllers</code> array, which we can see in the <code>FlowCoordinator</code> protocol.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730051439226/6bc0935a-628c-459c-9855-53fbe65d526e.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<p>For specific screen flow like a <strong>feature flow</strong>, SwiftUI coordinator will look like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FeatureCoordinator</span>: <span class="hljs-title">FlowCoordinator</span> </span>{

    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> finishDelegate: <span class="hljs-type">CoordinatorFinishDelegate?</span>

    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> childCoordinator: (any <span class="hljs-type">Coordinator</span>)?

    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> navigationControllers = [<span class="hljs-type">NavigationController</span>&lt;<span class="hljs-type">FeatureRoute</span>&gt;]()

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">destination</span><span class="hljs-params">(<span class="hljs-keyword">for</span> route: FeatureRoute)</span></span> -&gt; some <span class="hljs-type">View</span> {
        <span class="hljs-keyword">switch</span> route {
            <span class="hljs-keyword">case</span> .next:
                <span class="hljs-type">NextScreen</span>()
            <span class="hljs-keyword">case</span> .another:
                <span class="hljs-type">AnotherScreen</span>()
        }
    }

    <span class="hljs-keyword">var</span> rootView: some <span class="hljs-type">View</span> {
        <span class="hljs-type">NavigatingView</span>(
            nc: <span class="hljs-keyword">self</span>.rootNavigationController,
            coordinator: <span class="hljs-keyword">self</span>
        ) {
            <span class="hljs-type">FeatureScreen</span>()
        }
        .environmentObject(<span class="hljs-keyword">self</span>)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pushNextScreen</span><span class="hljs-params">()</span></span> {
        push(route: .next)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentAnotherScreen</span><span class="hljs-params">()</span></span> {
        present(route: .another)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentChild</span><span class="hljs-params">()</span></span> {
        present(child: <span class="hljs-type">ChildCoordinator</span>())
    }
}

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">FeatureRoute</span>: <span class="hljs-title">Routable</span> </span>{
    <span class="hljs-keyword">case</span> next
    <span class="hljs-keyword">case</span> another

    <span class="hljs-keyword">var</span> navigationType: <span class="hljs-type">NavigationType</span> {
        <span class="hljs-keyword">switch</span> <span class="hljs-keyword">self</span> {
            <span class="hljs-keyword">case</span> .next:
                <span class="hljs-keyword">return</span> .push
            <span class="hljs-keyword">case</span> .another:
                <span class="hljs-keyword">return</span> .present(.sheet([.medium, .large]))
        }
    }
}
</code></pre>
<p>As shown, <code>FeatureRoute</code> is an enum that contains all routes managed by <code>FeatureCoordinator</code>.</p>
<p>You probably noticed that <code>rootView</code> is a <code>NavigatingView</code>. <code>NavigatingView</code> is central to our SwiftUI navigation. In SwiftUI, everything is a <code>View</code>, and this wrapper view serves as the link between the Coordinator and SwiftUI views.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">NavigatingView</span>&lt;<span class="hljs-title">SomeCoordinator</span>: <span class="hljs-title">FlowCoordinator</span>&gt;: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">StateObject</span> <span class="hljs-keyword">var</span> nc: <span class="hljs-type">NavigationController</span>&lt;<span class="hljs-type">SomeCoordinator</span>.<span class="hljs-type">Route</span>&gt;
    @<span class="hljs-type">StateObject</span> <span class="hljs-keyword">var</span> coordinator: <span class="hljs-type">SomeCoordinator</span>

    <span class="hljs-keyword">var</span> content: () -&gt; any <span class="hljs-type">View</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">NavigationStack</span>(path: $nc.navigationPath) {
            <span class="hljs-type">AnyView</span>(content())
                .navigationDestination(<span class="hljs-keyword">for</span>: <span class="hljs-type">SomeCoordinator</span>.<span class="hljs-type">Route</span>.<span class="hljs-keyword">self</span>) {
                    coordinator.destination(<span class="hljs-keyword">for</span>: $<span class="hljs-number">0</span>)
                }
        }
        .sheet(isPresented: nc.isPresenting(with: .sheet())) {
            <span class="hljs-keyword">let</span> detents = nc.presentedRoute?.navigationType.presentationType?.detents
            viewToPresent
                .presentationDetents(detents ?? .<span class="hljs-keyword">init</span>())
        }
        .fullScreenCover(isPresented: nc.isPresenting(with: .fullScreenCover)) {
            viewToPresent
        }
        .sheet(isPresented: coordinator.shouldPresentChild(from: nc)) {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> childCoordinator = coordinator.childCoordinator {
                <span class="hljs-type">AnyView</span>(childCoordinator.rootView)
            }
        }
    }

    @<span class="hljs-type">ViewBuilder</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> viewToPresent: some <span class="hljs-type">View</span> {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> route = nc.presentedRoute {
            <span class="hljs-type">NavigatingView</span>(
                nc: coordinator.topNavigationController,
                coordinator: coordinator
            ) {
                coordinator.destination(<span class="hljs-keyword">for</span>: route)
            }
        }
    }
}
</code></pre>
<p>As you can see, our <code>NavigationStack</code> is located inside <code>NavigatingView</code>. We also have our routes presentation implementation here with <code>.sheet</code> and <code>.fullScreenCover</code> modifiers.</p>
<p>If we need a new navigation type, we should add it to <code>NavigationType</code> - even custom navigation types if we want. Then it should be implemented in <code>NavigatingView</code> .</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">NavigationType</span> </span>{
    <span class="hljs-comment">/// A push transition style, commonly used in navigation controllers.</span>
    <span class="hljs-keyword">case</span> push
    <span class="hljs-comment">/// A presentation style, often used for modal or overlay views.</span>
    <span class="hljs-keyword">case</span> present(<span class="hljs-type">PresentationType</span>)
}

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">PresentationType</span> </span>{
    <span class="hljs-comment">/// A sheet presentation style</span>
    <span class="hljs-keyword">case</span> sheet(<span class="hljs-type">Set</span>&lt;<span class="hljs-type">PresentationDetent</span>&gt;? = <span class="hljs-literal">nil</span>)
    <span class="hljs-comment">/// A full-screen cover presentation style.</span>
    <span class="hljs-keyword">case</span> fullScreenCover
}
</code></pre>
<p>The last thing to highlight in <code>NavigatingView</code> is how we present the child coordinator. To simplify the implementation, we intentionally limited the presentation options to the <code>.sheet</code> modifier. This is why the <code>start</code> method doesn’t add much value in this case - there’s no customization available for the child coordinator to initiate itself.</p>
<p>Finally, we can take a look on <code>FeatureScreen</code>:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FeatureScreen</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">EnvironmentObject</span> <span class="hljs-keyword">var</span> coordinator: <span class="hljs-type">FeatureCoordinator</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-comment">// some body that calls 'pushNext' and 'presentAnother'</span>
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pushNext</span><span class="hljs-params">()</span></span> {
        coordinator.pushNextScreen()
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentAnother</span><span class="hljs-params">()</span></span> {
        coordinator.presentAnotherScreen()
    }

    <span class="hljs-comment">//...</span>
}
</code></pre>
<p>The <code>FeatureScreen</code> is quite similar to the one we used in the UIKit navigation example, with the main difference being the use of the <code>EnvironmentObject</code> property wrapper here. <code>EnvironmentObject</code> is used to demonstrate the options available, but it could also be an <code>ObservedObject</code> if you prefer using plain old constructor dependency injection.</p>
<p>For deeplink handling, we can use the same approach as we did with UIKit with <code>FeatureDeeplinkHandler</code>.</p>
<p>In the end, we had to write more code in comparison to UIKit, but now we can apply the Coordinator pattern using SwiftUI navigation. However, is this solution truly smooth and effective, or are there still limitations?</p>
<h1 id="heading-limitations">Limitations</h1>
<p>Let's explore the limitations of our SwiftUI Coordinator implementation, especially when compared to UIKit approach. Many of them are simply differences between SwiftUI and UIKit navigation systems, but some comes from the attempt to not overcomplicate the SwiftUI solution.</p>
<p><strong>View-Bound Nature</strong></p>
<p>In SwiftUI approach, you might have noticed we needed to add more view-related properties to our Coordinator protocols, such as <code>rootView</code> and <code>destination</code>. This makes SwiftUI's <strong>NavigationStack</strong> based approach more tightly coupled to the views, which contrasts with UIKit’s approach where coordinator protocol was abstracted away from the views. This "view-bound" characteristic comes from SwiftUI's view-centric nature, where everything is a wrapper around a view. This makes it difficult to keep Coordinator protocol agnostic to the framework details. But this is not a significant limitation unless you intend to reuse your Coordinator solution across different platforms or frameworks.</p>
<p><strong>Complexity</strong></p>
<p>Navigating in SwiftUI tends to feel more complex when it goes a bit from simple examples. You might have noticed the increased amount of code required for the SwiftUI coordinator compared to UIKit one. In UIKit, injecting a <code>UINavigationController</code> into specific coordinator implementation was usually enough for most navigation tasks, since navigation controller also acts as a <code>UIViewController</code> that can present other view controllers.</p>
<p>In contrast, <code>NavigationStack</code> in SwiftUI is limited. It only handles pushing and popping views. Presentation is out of it’s control. For more advanced scenarios, you need to create custom solutions. We had to create a custom <code>NavigationController</code> and wrap views within <code>NavigatingView</code> to achieve similar capabilities, adding to the overall complexity.</p>
<p><strong>Starting Child Coordinators</strong></p>
<p>UIKit Coordinator approach offers a great deal of freedom in how you navigate to child coordinators - be it pushing, presenting, or using custom transitions. It also allows child coordinators to handle their own starting process through a <code>start</code> method.</p>
<p>In our SwiftUI implementation, starting child coordinators is restricted to using the <code>sheet</code> modifier. While other modifiers like <code>fullScreenCover</code> could theoretically be supported, this would further complicate the solution. This limitation is the reason why the <code>start</code> method doesn’t make sense in SwiftUI approach - it makes sense only when a child can determine and initiate its own presentation, which isn’t possible under this approach where presentation is fully controlled by parent.</p>
<p>Another limitation is that attempting to push a new coordinator onto an existing NavigationStack is particularly challenging there because each <code>FlowCoordinator</code> is bound to a specific <code>Route</code> type and making two coordinators to share the same <code>Route</code> type doesn’t make much sense.</p>
<p><strong>Changing the Navigation Path</strong></p>
<p>SwiftUI's <code>NavigationStack</code> also has limitations regarding modifying navigation path. The <code>NavigationPath</code> can only pop elements from the end of the stack with <code>removeLast(_ k: Int = 1)</code> method. Unlike UIKit, it can't remove elements from the middle of the stack, which limits its flexibility.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730052000012/00ce7eb0-e3bd-4087-bc57-1ad46de0f74c.gif" alt class="image--center mx-auto" /></p>
<p>Even if you use an array of routes instead of <code>NavigationPath</code>, removing elements from the middle causes strange behavior with unexpected pop animations. You can see it on example above.</p>
<p>Moreover, the initial root view in <code>NavigationStack</code> isn’t included in the <code>navigationPath</code> array, making any modification of the root view impossible.</p>
<p><strong>Custom Navigation Transitions</strong></p>
<p>SwiftUI lacks support for custom navigation transitions when using <code>NavigationStack</code>. A third-party package called <a target="_blank" href="https://github.com/davdroman/swiftui-navigation-transitions">NavigationTransitions</a> supports custom transitions for iOS 16, but depending on external SDKs has its own downsides. iOS 18 introduces a new API, <a target="_blank" href="https://developer.apple.com/documentation/swiftui/navigationtransition">NavigationTransition</a>, that allows custom transitions like <code>zoom</code>. However, this isn’t helpful if you need to support iOS 16 or earlier versions.</p>
<p>UIKit remains more robust and reliable for custom navigation transitions. If you’re okay with using only standard transitions, SwiftUI will suffice, but for more sophisticated effects, UIKit still stands out.</p>
<p><strong>Navigation Bar Customization</strong></p>
<p>SwiftUI allows navigation bar customization with modifiers like <code>navigationTitle</code>, and <code>toolbar</code>. However, deeper customizations - like adjusting the height, adding a background image, or modifying the back button - are more limited compared to UIKit. You can access global <code>UINavigationBarAppearance</code> to achieve some customizations in SwiftUI, but UIKit remains far superior in this regard.</p>
<p><strong>Working with Environment</strong></p>
<p>Finally, an advantage for SwiftUI approach. If you rely on the <code>@EnvironmentObject</code> property wrapper, SwiftUI offers direct integration, which doesn't work properly in UIKit's hosting controller approach. While <code>@Environment</code> still works in UIHostingController approach, <code>@EnvironmentObject</code> does not.</p>
<p>Personally, I am not a big fan of environment objects. I prefer constructor injection, much like how I prefer using <strong>Factory</strong> over <strong>Swinject</strong>, or why I avoid force-unwrapped optionals. I respect <a target="_blank" href="https://en.wikipedia.org/wiki/Murphy%27s_law"><strong>Murphy's law</strong></a> and believe that <strong>if it can crash, it will crash</strong>. (Depending on environment objects can lead to unpredictable crashes—check out my <a target="_blank" href="https://vbat.dev/di-in-swiftui">DI in SwiftUI article</a> for more details.)</p>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>Given these limitations, I find myself leaning towards continuing using UIKit Coordinator approach for navigation, at least for now.</p>
<p>UIKit's navigation model is powerful, predictable, and well-proven. When needed, I can combine UIKit with SwiftUI to leverage the best of both worlds and avoid potential headaches related to navigation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730052326239/0743399b-66fd-4c23-ba3a-fa1235f18d17.jpeg" alt class="image--center mx-auto" /></p>
<p>Migrating to SwiftUI for navigation currently brings limited benefits, particularly given the edge cases I could miss and added complexity.</p>
<blockquote>
<p><strong>NOTE</strong>: It’s worth mentioning that everything discussed in this article reflects the current state of SwiftUI Navigation. SwiftUI continues to improve each year, and I believe that in the coming years, it will reach a point where it meets our needs. For now, though, it is what it is.</p>
</blockquote>
<p>Can we use a Third-Party Library for SwiftUI Navigation? There are several quite popular libraries available that can simplify navigation in SwiftUI, such as <a target="_blank" href="https://github.com/rundfunk47/stinsen">Stinsen</a> or PointFree's <a target="_blank" href="https://github.com/pointfreeco/swiftui-navigation">SwiftUINavigation</a>. However, I prefer avoiding third-party dependencies for something as fundamental as navigation. Third-party libraries may stop receiving updates or break with new versions of iOS, leaving you at the mercy of someone else's timelines or requiring you to maintain your own fork with solutions that are quite complex.</p>
<p>Many large-scale applications has complex navigation engines which uses a path-based approach where everything is a deep link and are unlikely to migrate away from their powerful solutions to a more constrained SwiftUI-based navigation system anytime soon.</p>
<p>If you have insights or experiences with SwiftUI navigation, feel free to share them in the comments. You can also check out the code examples for this discussion.</p>
<p>You can find the example project code <a target="_blank" href="https://github.com/vitalybatrakov/SwiftUINavigationExample">here</a> if needed.</p>
]]></content:encoded></item><item><title><![CDATA[How to modularize Monolith iOS App]]></title><description><![CDATA[If you prefer to watch video you can find it here.  
Hey iOS folks!
If your application has grown in complexity over time, modularization can be a good strategy to improve development efficiency, build time, scalability, maintainability, and etc. Tod...]]></description><link>https://vbat.dev/how-to-modularize-monolith-ios-app</link><guid isPermaLink="true">https://vbat.dev/how-to-modularize-monolith-ios-app</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[modularization]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 12 May 2024 18:10:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715477207949/5964104f-5a48-4f35-b301-f19ca5a18464.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>If you prefer to watch video you can find it</strong> <a target="_blank" href="https://youtu.be/Xu4eyAh-aJw"><strong>here.</strong>  
</a><br />Hey iOS folks!</p>
<p>If your application has grown in complexity over time, modularization can be a good strategy to improve development efficiency, build time, scalability, maintainability, and etc. Today, we will precisely examine the path from monolith to modular app, step by step, and provide insights about each step along the way.</p>
<p><strong>From monolith to modularization</strong></p>
<p>Before transitioning to modularity, your app likely adhered to some monolithic architecture that followed some form of <strong>layered architecture</strong>, one of the most common and widely used software architecture patterns. Layered architecture organizes the components of an application into separate horizontal layers, with each layer serving a specific function or responsibility.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396446952/fbd0fcee-9cc6-4d58-b423-53fd6bca1254.png" alt class="image--center mx-auto" /></p>
<p>As a typical example for possible layers:</p>
<ul>
<li><p><strong>Data/Persistence layer</strong>: responsible for data storage and retrieval.</p>
</li>
<li><p><strong>Business Logic layer</strong>: contains the application's core functionality and logic.</p>
</li>
<li><p><strong>Presentation layer (UI)</strong>: handles user interface and communication.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396490055/57998372-c8e0-42cc-98c7-d0cb68d8cd3a.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE</strong>: For example, <a target="_blank" href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html"><strong>CLEAN</strong></a><strong>,</strong> <a target="_blank" href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/"><strong>Onion</strong></a><strong>, Service Oriented (SOA</strong>) architectures are examples of layered architecture.</p>
</blockquote>
<p>In addition to layer separation, we also need to keep in mind the <strong>dependency direction rule</strong>:</p>
<blockquote>
<p>Layers can depend <strong>UPWARD</strong> but <strong>NOT DOWNWARD</strong>.</p>
</blockquote>
<p>The dependency direction rule heavily relies on the <a target="_blank" href="https://en.wikipedia.org/wiki/Dependency_inversion_principle"><strong>Dependency Inversion principle</strong></a>, enabling us to maintain <a target="_blank" href="https://en.wikipedia.org/wiki/Coupling_(computer_programming)"><strong>loose coupling</strong></a> between our layers, providing encapsulation and scalability.</p>
<h3 id="heading-layered-modular-architecture">Layered Modular Architecture</h3>
<p>The concept of layered architecture is straightforward and can actually be applied to our modular architecture as well.</p>
<p>In essence, modularization involves dividing your app into separate modules. Thus, you can apply layered approach to modularization by integrating your modules into specific layers.</p>
<p>We'll structure our modularized app into three layers: <strong>Core</strong>, <strong>Features</strong>, and <strong>Composition Root</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396531739/687a8833-b93e-458b-a17e-c362fef8ed7b.png" alt class="image--center mx-auto" /></p>
<p><strong>Core Layer</strong>:</p>
<ul>
<li><p>Contains code that is used across various features of the app.</p>
</li>
<li><p>Offers shared functionality that is agnostic to the specific app, encompassing UI components, networking, analytics, utilities, metrics, authentication, logging, and more.</p>
</li>
<li><p>Core modules operate independently of each other; they do not depend on other core modules.</p>
</li>
</ul>
<p><strong>Feature Layer:</strong></p>
<ul>
<li><p>Feature modules encapsulate distinct features or functionalities of the app, such as specific screens, sets of related screens, or views - examples include payment processing, customer profiles, restaurant list, search, support chat and etc.</p>
</li>
<li><p>They contain all code relevant to specific feature, covering UI, logic, and data handling.</p>
</li>
<li><p>While feature modules may depend on core modules if necessary, they should not depend on other feature modules.</p>
</li>
<li><p>Feature modules promote separation of concerns, facilitating independent development, testing, and reusability across projects.</p>
</li>
</ul>
<p><strong>Composition Root:</strong></p>
<ul>
<li><p>The composition root serves as a central place within the application where the object graph is assembled/composed. Its primary responsibility lies in creating and injecting all the dependencies throughout the application.</p>
</li>
<li><p>Composition Root pattern enables the modules (and layers) to maintain loose coupling, facilitating seamless transitions between implementations if needed.</p>
</li>
<li><p>It's important to note that the composition root itself is not a module; rather, it resides within your app's main target.</p>
</li>
<li><p>Each feature will have its own designated composition folder within the composition root, referred to as the <code>Feature[X]Support</code> folder (or <code>Feature[X]Composition</code> if you like).</p>
</li>
<li><p>For those unfamiliar with Dependency Injection (DI) and the concept of Composition root, further details can be found <a target="_blank" href="https://medium.com/its-tinkoff/di-in-ios-complete-guide-cd76a079d2d">here</a>, <a target="_blank" href="https://simonbs.dev/posts/introducing-the-composition-root-pattern-in-a-swift-codebase/">here</a> and <a target="_blank" href="https://www.youtube.com/watch?v=bmIW1skJQFo&amp;ab_channel=TechConnection">here</a>.</p>
</li>
</ul>
<blockquote>
<p><strong>NOTE 1</strong>: The same <strong>dependency direction rule</strong> applies to modular layers, but we also need to enforce the restriction that modules within one layer should not depend on each other. Together with dependency direction rule this allows to avoid <strong>circular dependencies</strong>. We achieve this by applying Dependency Inversion principle and abstracting dependencies from other modules using public protocols. (This rule is applicable to both core and feature layers.)</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396577053/92476255-5ba4-43ea-b46b-23cdaf2b6a13.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE 2:</strong> We haven't forbidden feature modules from depending on core modules. This allowance is important, particularly in scenarios involving shared UI components, where direct dependencies can sometimes be preferable to avoid the added complexity of abstraction through dependency inversion. However, it's important to note that not all core packages need to be direct dependencies of features; in fact, the fewer direct dependencies your feature module has, the more flexibility it gets. Inverting the dependency on core modules fosters loose coupling between core and feature layers, making it easier to substitute/reuse modules in the future. It's beneficial to abstract core module dependencies and inject them via the composition root, but abstraction goes with related cost (will discuss it a bit later with example).</p>
<p><strong>NOTE 3</strong>: We haven't specified the method of implementing modularity—whether it's through SPM, CocoaPods, or any other means—because, in reality, it doesn't matter for the modularization strategy we're discussing here. You're free to choose whichever method you prefer.</p>
</blockquote>
<h3 id="heading-getting-started">Getting started</h3>
<p>So, now you're ready to begin migrating from a monolithic to a modular structure.</p>
<p>Logically, the first step is to migrate the core modules. How do you determine which code should be moved to the core layer? Look for functionality that is shared and required by multiple feature modules in your application. This may include UI components, networking, authentication, analytics, logging, utility functions, and so on.</p>
<p>Once all core modules are modularized, let's consider a scenario where you have a feature within your monolith that you want to modularize next. You need to create a new module and move the related code there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715477272837/bc02f6cf-72f9-4266-ab06-0ff06110548c.png" alt class="image--center mx-auto" /></p>
<p>Now, it's important to ensure that the <strong>dependency direction rule is not violated.</strong></p>
<p>You may find yourself in one of the three situations listed below:</p>
<p><strong>1) Your module depends on another feature (SAME LAYER)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396606529/9be1c9b1-58f5-4272-92d7-77b222ae71ec.png" alt class="image--center mx-auto" /></p>
<p>It’s not allowed by our <strong>dependency direction rule</strong>.</p>
<p>We need to do <strong>Dependency Inversion</strong> to comply with it.</p>
<p>This means that our module should depend on abstractions/interfaces/protocols, rather than concrete implementations that lies in another feature module.</p>
<p>Let's clarify how we can do it with a typical example. Imagine that we have a navigation scenario where one feature presents another.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// somewhere inside your feature module...</span>
<span class="hljs-keyword">import</span> AnotherFeature <span class="hljs-comment">// Can't depend on AnotherFeature module here</span>
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">YourFeatureViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showAnotherFeature</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">AnotherFeatureViewController</span>()
        present(vc, animated: <span class="hljs-literal">true</span>)
    }    
}
</code></pre>
<p>To get rid of dependency on <code>AnotherFeature</code>, we want to invert this dependency. We create the public routing protocol inside of our new feature module:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">YourFeatureRouting</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showAnotherFeature</span><span class="hljs-params">()</span></span>
}
</code></pre>
<p>Now we can use this protocol for navigation:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> UIKit

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">YourFeatureViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> featureRouter: <span class="hljs-type">YourFeatureRouting</span>

    <span class="hljs-keyword">init</span>(featureRouter: <span class="hljs-type">YourFeatureRouting</span>) {
        <span class="hljs-keyword">self</span>.featureRouter = featureRouter
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showAnotherFeature</span><span class="hljs-params">()</span></span> {
        featureRouter.showAnotherFeature()
    }    
}
</code></pre>
<p>For example, in this case, your feature interface is a factory that creates view controller for specific screen. We need to add routing protocol as a required dependency of our feature by putting it into factory params list.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> UIKit

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">YourFeatureFactory</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">make</span><span class="hljs-params">(
        featureRouter: YourFeatureRouting
    )</span></span> -&gt; <span class="hljs-type">UIViewController</span> {
        <span class="hljs-type">YourFeatureViewController</span>(featureRouter: featureRouter)
    }
}
</code></pre>
<p>Now, our module explicitly declares that it requires the user of the module (the caller side, in our case, the composition root) to provide an implementation for the <code>YourFeatureRouting</code> protocol, which our feature is intended to use.</p>
<p>Next, inside our composition root in <code>YourFeatureSupport</code> folder we will implement the router and conform to this routing protocol:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> AnotherFeature
<span class="hljs-keyword">import</span> UIKit
<span class="hljs-keyword">import</span> YourFeature

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">YourFeatureRouter</span>: <span class="hljs-title">YourFeatureRouting</span> </span>{
    <span class="hljs-keyword">var</span> source: <span class="hljs-type">UIViewController?</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showAnotherFeature</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">AnotherFeatureViewController</span>()
        source?.present(vc, animated: <span class="hljs-literal">true</span>)
    }
}
</code></pre>
<p>Now we need to compose everything together with our router using composition factory:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> UIKit
<span class="hljs-keyword">import</span> YourFeature

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">YourFeatureCompositionFactory</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">make</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIViewController</span> {
        <span class="hljs-keyword">let</span> featureRouter = <span class="hljs-type">YourFeatureRouter</span>()
        <span class="hljs-keyword">let</span> featureService = <span class="hljs-type">YourServiceFactory</span>.make()
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">YourFeatureFactory</span>.make(
            featureRouter: featureRouter,
            homeService: homeService
        )
        featureRouter.source = vc
        <span class="hljs-keyword">return</span> vc
    }
}
</code></pre>
<p>And that’s it! We inverted the dependency on <code>AnotherFeature</code>.</p>
<p><strong>2) Your module depends DOWNWARDS</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396743350/b106d29c-8d9a-4b1e-83fb-6b84c42052b2.png" alt class="image--center mx-auto" /></p>
<p>It’s not allowed by our <strong>dependency direction rule</strong>.</p>
<p>Decide the optimal location for the code you depend on:</p>
<ul>
<li><p>If it’s your new feature module:</p>
<ul>
<li>Move or copy the dependent code to your new feature module.</li>
</ul>
</li>
</ul>
<p>or</p>
<ul>
<li><p>if it’s another module (whether feature or core):</p>
<ul>
<li><p>Transfer it to another module (you can create a new module if required).</p>
</li>
<li><p>Invert the dependency using the same approach. Create a public protocol and specify what your new feature module expects to receive from the composition root. Inject the dependency through the feature interface.</p>
</li>
</ul>
</li>
</ul>
<p><strong>3) Your module depends UPWARDS</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396767946/99ce554d-55f7-4881-b14c-516684953083.png" alt class="image--center mx-auto" /></p>
<p>This aligns with our <strong>dependency direction rule</strong>.</p>
<p>As previously mentioned, depending on the core module from feature module can be essential like for the UI components core module (CompanyUIKit). For some core modules you need to consider whether to proceed with this direct dependency or to invert the dependency by injecting it from the composition root, as we have done previously.</p>
<p>Dependency inversion fosters <strong>loose coupling</strong> between core and feature layers, provides more <strong>flexibility</strong> and <strong>reusability</strong>.</p>
<p>For example, imagine that you depend on Networking core module from your feature module:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715396931107/30b2734b-0f08-4b57-80a9-450762c29140.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>(<strong>Flexibility</strong>) Imagine that you wish to replace the obsolete Networking module with the brand new NetworkingGPT module.</p>
<ul>
<li><p>If you directly depend on the networking module, you'll need to modify all networking-related code within the feature module and update imports in every file where you use code from networking module.</p>
</li>
<li><p>Conversely, if you've inverted the dependency and injected it via the composition root, it becomes easier to make the switch. By defining a public feature service protocol to be implemented within the composition root, you conceal the networking implementation details from the feature module. All that's required is to provide a new implementation for the feature service protocol within the composition root.</p>
</li>
</ul>
</li>
<li><p>(<strong>Reusability</strong>) If you aim to reuse your feature module in another project, it becomes simpler to move it.</p>
</li>
<li><p>(<strong>Cost of benefits</strong>) However, this approach introduces a bit more complexity and necessitates writing additional code.</p>
</li>
</ul>
<p>Consider these pros and cons in the context of your specific scenario.</p>
<h3 id="heading-final-thoughts"><strong>Final thoughts</strong></h3>
<p>The idea I wanted to convey here is that modularization isn't rocket science. It's not quick either, especially for larger projects. However, the positive aspect is that you can undertake it gradually. Rome wasn't built in a day, and with patience, you can succeed as well.</p>
<p>You can find a simple modular project example <a target="_blank" href="https://github.com/vitalybatrakov/ModularAppExample">here</a>. See you in the next post!</p>
]]></content:encoded></item><item><title><![CDATA[Adapting UIHostingController to changes in SwiftUI View size]]></title><description><![CDATA[Hey iOS folks! If you're using both UIKit and SwiftUI in your iOS project, you'll need to use UIHostingController as bridge between the two frameworks. UIHostingController - is a UIKit view controller that manages a SwiftUI view hierarchy. It's inten...]]></description><link>https://vbat.dev/adapting-uihostingcontroller-to-changes-in-swiftui-view-size</link><guid isPermaLink="true">https://vbat.dev/adapting-uihostingcontroller-to-changes-in-swiftui-view-size</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[UIkit]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sat, 30 Mar 2024 16:39:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711775368923/b2ba4e31-f33f-4524-95ac-3c8a13eecbdc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey iOS folks! If you're using both UIKit and SwiftUI in your iOS project, you'll need to use <a target="_blank" href="https://developer.apple.com/documentation/swiftui/uihostingcontroller"><code>UIHostingController</code></a> as bridge between the two frameworks. <code>UIHostingController</code> - is a UIKit view controller that manages a SwiftUI view hierarchy. It's intended for use when integrating SwiftUI views into a UIKit view hierarchy.</p>
<p>While the concept may seem straightforward for experienced UIKit developers, it's easy to fall into a trap and end up with a solution like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> SnapKit
<span class="hljs-keyword">import</span> SwiftUI

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addSwiftUIView</span>&lt;T: View&gt;<span class="hljs-params">(view: T)</span></span> {
        <span class="hljs-keyword">let</span> hostingController = <span class="hljs-type">UIHostingController</span>(rootView: view)
        addSubview(hostingController.view)
        hostingController.view.snp.makeConstraints {
            $<span class="hljs-number">0</span>.edges.equalToSuperview()
        }
    }
}
</code></pre>
<p>It can even appear to work fine until it doesn't. The issue here is that we don't retain <code>UIHostingController</code> after the hosting setup, and it is deallocated instantly. However, <code>UIHostingController</code> is intended to play an important role in UIKit/SwiftUI communication.</p>
<p>For example, the hosting controller can track changes to the size of its SwiftUI content using <a target="_blank" href="https://developer.apple.com/documentation/swiftui/uihostingcontroller/sizingoptions"><code>sizingOptions</code></a>:</p>
<pre><code class="lang-swift">@<span class="hljs-type">MainActor</span>
<span class="hljs-keyword">var</span> sizingOptions: <span class="hljs-type">UIHostingControllerSizingOptions</span> { <span class="hljs-keyword">get</span> <span class="hljs-keyword">set</span> }
</code></pre>
<p>From <a target="_blank" href="https://developer.apple.com/videos/play/wwdc2022/10072/">WWDC2022</a>, we know that since iOS 16, you can use the <code>.preferredContentSize</code> and <code>.intrinsicContentSize</code> options on <code>UIHostingController</code> to automatically update the ViewController’s <code>preferredContentSize</code> and the view’s <code>intrinsicContentSize</code>. This can be enabled using the <code>sizingOptions</code> property on the <code>UIHostingController</code>. The default sizing options value is empty.</p>
<blockquote>
<p><strong>NOTE 1:</strong> The <code>intrinsicContentSize</code> of a UIView refers to the ideal size that a view would like to occupy based on its content. This property allows a custom view to tell its content size to the layout system without explicitly setting it. It simplifies working with Auto Layout by reducing the need for explicit constraints, making layouts more flexible and dynamic.</p>
<p><strong>NOTE 2</strong>: The ViewController's <code>preferredContentSize</code> refers to the desired size for the content of the view controller. It allows the view controller to tell its preferred dimensions to the layout system when the view controller should be presented, especially in scenarios like popovers where specifying an ideal size is crucial for proper display. By setting the preferred content size, developers can control the presentation of view controllers and ensure that they are displayed optimally based on their content.</p>
</blockquote>
<p>Let’s return to our example now. Once <code>UIHostingController</code> is deallocated, it can't perform its job on sizing updates if the SwiftUI view's content is changed.</p>
<p>So, as a first step, we can try to at least retain <code>UIHostingController</code>:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addSwiftUIView</span>&lt;T: View&gt;<span class="hljs-params">(view: T)</span></span> -&gt; <span class="hljs-type">UIHostingController</span>&lt;<span class="hljs-type">T</span>&gt; {
    <span class="hljs-keyword">let</span> hostingController = <span class="hljs-type">UIHostingController</span>(rootView: view)
    addSubview(hostingController.view)
    hostingController.view.snp.makeConstraints {
        $<span class="hljs-number">0</span>.edges.equalToSuperview()
    }
    <span class="hljs-keyword">return</span> hostingController
}

<span class="hljs-keyword">let</span> hostingController = addSwiftUIView(<span class="hljs-type">SwiftUIView</span>())
<span class="hljs-comment">// retain UIHostingController somewhere</span>
<span class="hljs-keyword">self</span>.hostingController = hostingController
</code></pre>
<p>If we activate sizing options with <code>.intrinsicContentSize</code>, it will even start to work, and the hosting view will update its intrinsicContentSize when the SwiftUI view's size is changed. However, this is still not how <code>UIHostingController</code> is <a target="_blank" href="https://developer.apple.com/documentation/swiftui/uihostingcontroller">intended to be used</a>.</p>
<blockquote>
<p><strong>NOTE</strong>: Use the hosting controller like you would any other view controller, by presenting it or embedding it as a child view controller in your interface.</p>
</blockquote>
<p>So, we need to set it up like any other UIViewController by adding it as a child view controller:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> SnapKit
<span class="hljs-keyword">import</span> SwiftUI

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIViewController</span> </span>{
        <span class="hljs-meta">@discardableResult</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hostSwiftUIView</span>&lt;T: View&gt;<span class="hljs-params">(
        view: T,
        insideView hostView: UIView? = <span class="hljs-literal">nil</span>
    )</span></span> -&gt; <span class="hljs-type">UIHostingController</span>&lt;<span class="hljs-type">T</span>&gt; {
        <span class="hljs-keyword">let</span> hostingController = <span class="hljs-type">UIHostingController</span>(rootView: view)
        addChild(hostingController)
        hostingController.didMove(toParent: <span class="hljs-keyword">self</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> hostView {
            hostView.addSubview(hostingController.view)
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">self</span>.view.addSubview(hostingController.view)
        }
        hostingController.view.snp.makeConstraints {
            $<span class="hljs-number">0</span>.edges.equalToSuperview()
        }
        <span class="hljs-keyword">return</span> hostingController
    }
}
</code></pre>
<blockquote>
<p><strong>NOTE</strong>: We passed <strong>hostView</strong> for cases when we want to add the hosting view as a subview to a view in the hierarchy that is not the view controller’s view.</p>
</blockquote>
<p>That’s it! And don’t forget to setup sizing options of the hosting controller:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> hosting = hostSwiftUIView(view: <span class="hljs-type">SwiftUIView</span>())
<span class="hljs-keyword">if</span> #available(iOS <span class="hljs-number">16.0</span>, *) {
    hostingController.sizingOptions = [.intrinsicContentSize]
}
</code></pre>
<blockquote>
<p><strong>NOTE</strong>: We set up the sizing options outside of the <code>hostSwiftUIView</code> method because sizing options come with a performance cost, as the hosting controller asks for the ideal size of the content on every view update. You can move sizing options setup inside of the <code>hostSwiftUIView</code> if you are ready for that cost.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711815535081/88559a93-0061-4a3e-9ea1-08070f6fcb26.gif" alt class="image--center mx-auto" /></p>
<p>The happy path is complete. Let’s discuss a few edge cases next.</p>
<h3 id="heading-before-ios-16">Before iOS 16</h3>
<p>Sizing options are cool, but what if I need to support iOS 15 or even earlier versions?</p>
<p>In this case, you can still update the hosting controller’s view size explicitly by calling <code>setNeedsUpdateConstraints()</code> (or <code>invalidateIntrinsicContentSize()</code> that will eventually call <code>setNeedsUpdateConstraints()</code>).</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hostSwiftUIViewIfYouStillSupportOS15</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">let</span> swiftUIView = <span class="hljs-type">SwiftUIView</span> { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">self</span>?.hostingController?.view.setNeedsUpdateConstraints()
    }
    hostingController = hostSwiftUIView(
        view: swiftUIView, 
        insideView: view2
    )
}
</code></pre>
<p>We need to call <code>setNeedsUpdateConstraints</code> at the right time when view state is changed and intrinsic content size should be recalculated:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SwiftUIView</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">State</span> <span class="hljs-keyword">var</span> state: ...

    <span class="hljs-keyword">var</span> onUpdateContent: (() -&gt; <span class="hljs-type">Void</span>)?

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-comment">// some View...</span>
        .onChange(of: state) { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
            onUpdateContent?()
        }
        .onAppear {
            onUpdateContent?()
        }
    }
}
</code></pre>
<p>In this example I had to do it also on view appear to make it work correctly. For some reason initial intrinsic size is calculated incorrectly so need to update it when view content appears.</p>
<blockquote>
<p><strong>NOTE</strong>: This solution will work for iOS 16/17 as well, but using <code>sizingOptions</code> is obviously much simpler and convenient option.</p>
</blockquote>
<h3 id="heading-if-you-have-only-uiview">If you have only UIView</h3>
<p>If you have some UIKit legacy code and you need to add SwiftUI view inside <strong>UIView</strong> and have no reference to <strong>UIViewController</strong>, you have some options as well.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hostSwiftUIView</span>&lt;T: View&gt;<span class="hljs-params">(view: T)</span></span> -&gt; <span class="hljs-type">UIHostingController</span>&lt;<span class="hljs-type">T</span>&gt;? {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> vc = findViewController() <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }
        <span class="hljs-keyword">return</span> vc.hostSwiftUIView(
            view: view,
            insideView: <span class="hljs-keyword">self</span>
        )
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">findViewController</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIViewController?</span> {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> nextResponder = <span class="hljs-keyword">self</span>.next <span class="hljs-keyword">as</span>? <span class="hljs-type">UIViewController</span> {
            <span class="hljs-keyword">return</span> nextResponder
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> nextResponder = <span class="hljs-keyword">self</span>.next <span class="hljs-keyword">as</span>? <span class="hljs-type">UIView</span> {
            <span class="hljs-keyword">return</span> nextResponder.findViewController()
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
        }
    }
}
</code></pre>
<p>You can use the responder chain to get the next <code>UIViewController</code> in the hierarchy, but once finding the view controller is not guaranteed, that solution is not ideal. If you can avoid using it, prefer to handle this using the <code>UIViewController</code>.</p>
<h2 id="heading-popovers-and-preferredcontentsize"><strong>Popovers and preferredContentSize</strong></h2>
<p>Now we’ve managed to get <code>.intrinsicContentSize</code> sizing option to work properly, but let’s see how we can use <code>.preferredContentSize</code> option now. If you try to present <code>UIHostingController</code> as a popover you will face some issues.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIViewController</span>: <span class="hljs-title">UIPopoverPresentationControllerDelegate</span> </span>{
    <span class="hljs-meta">@discardableResult</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentPopover</span>&lt;T: View&gt;<span class="hljs-params">(
        withSwiftUIView view: T,
        sourceView: UIView,
        permittedArrowDirections: UIPopoverArrowDirection = .any
    )</span></span> -&gt; <span class="hljs-type">UIHostingController</span>&lt;<span class="hljs-type">T</span>&gt; {
        <span class="hljs-keyword">let</span> hostingController = <span class="hljs-type">UIHostingController</span>(rootView: view)
        <span class="hljs-keyword">if</span> #available(iOS <span class="hljs-number">16.0</span>, *) {
            hostingController.sizingOptions = [.preferredContentSize]
        }
        hostingController.modalPresentationStyle = .popover
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> popoverPC = hostingController.popoverPresentationController {
            popoverPC.permittedArrowDirections = permittedArrowDirections
            popoverPC.sourceView = sourceView
            popoverPC.delegate = <span class="hljs-keyword">self</span>
        }
        present(hostingController, animated: <span class="hljs-literal">true</span>)
        <span class="hljs-keyword">return</span> hostingController
    }
}
</code></pre>
<p>Let’s try a simple example again:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711815673019/017c03c5-4767-4f38-866a-311844b1c435.gif" alt class="image--center mx-auto" /></p>
<p>The first thing you can notice here is that the hosting controller doesn’t care about the minimal popover size. As you can see, it goes with the height that the hosted SwiftUI view returns to it.</p>
<p>You can help hosting controller by providing minimal height using frame modifier.</p>
<pre><code class="lang-swift"><span class="hljs-type">SwiftUIView</span>().frame(minHeight: <span class="hljs-number">40</span>)
</code></pre>
<p>It looks better now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711815695106/82fdb231-f5c1-4574-9950-094dfe0a2263.gif" alt class="image--center mx-auto" /></p>
<p>But you can see another two issues here:</p>
<ul>
<li><p>Animation doesn’t work properly with the popover mask. I'm not sure if we can do anything about it. Let me know if you have any ideas on how to fix it.</p>
</li>
<li><p>Content size updates don't work properly either. If the hosting controller returns a <code>preferredContentSize</code> that is bigger than the popover’s maximum size (aka screen size minus paddings), the popover will truncate the view’s size using this allowed maximum. In our case, it uses the returned preferred content height and truncates the preferred content width. To address this issue, we can provide some anchor width as the ideal width in the frame modifier. This ideal width will be used to calculate the <code>preferredContentSize</code> that will satisfy our expectations.</p>
</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-type">SwiftUIView</span>().frame(idealWidth: <span class="hljs-number">250</span>, minHeight: <span class="hljs-number">40</span>)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711815742595/c13d7738-2890-4e1c-9617-60aaa590360c.gif" alt class="image--center mx-auto" /></p>
<p>That’s it. Now popover updates the size when content size is changed as expected.</p>
<p>You can find example project <a target="_blank" href="https://github.com/vitalybatrakov/UIHostingControllerExample">here</a>.</p>
<p>Thanks for reading and see you in the next posts!</p>
]]></content:encoded></item><item><title><![CDATA[Behind the scenes of UI: Part 2 - SwiftUI]]></title><description><![CDATA[if you prefer to watch video you can find it here.
You can say that UIKit is great, but SwiftUI is a future. But don’t hurry up to burry UIKit, as you will understand soon, it is still very useful to understand how SwiftUI works under the hood.

Swif...]]></description><link>https://vbat.dev/behind-the-scenes-of-ui-part-2-swiftui</link><guid isPermaLink="true">https://vbat.dev/behind-the-scenes-of-ui-part-2-swiftui</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Mon, 19 Feb 2024 20:21:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317545073/a34b03be-e4a4-4810-8184-d84807979f8e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>if you prefer to watch video you can find it <a target="_blank" href="https://youtu.be/ue6tBz03cVQ">here</a>.</p>
<p>You can say that UIKit is great, but SwiftUI is a future. But don’t hurry up to burry UIKit, as you will understand soon, it is still very useful to understand how SwiftUI works under the hood.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317609788/8b17673d-4739-4f93-a720-5900bdfce6b7.png" alt class="image--center mx-auto" /></p>
<p>SwiftUI is a declarative data-driven UI framework.</p>
<ul>
<li><p><strong>Declarative</strong> means that you describe the desired UI and its behavior by providing the view’s body, and SwiftUI handles the internal implementation details.</p>
</li>
<li><p><strong>Data-driven (or state-driven)</strong> means that the UI is closely tied to the underlying data or state, and any changes in the state will be automatically reflected in the UI.</p>
</li>
</ul>
<p>SwiftUI conceals its entire render loop process behind the <strong>body</strong> of the view provided to us for constructing the View.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">Text</span>(<span class="hljs-string">"Hello, world!"</span>)
    }
}
</code></pre>
<p>Structure of the body defines both <strong>layout</strong> and <strong>content</strong> of the view. Let’s try to figure out what stands behind the body.</p>
<blockquote>
<p><strong>DISCLAIMER</strong>: Things we will discuss next are not documented and we have no access to the actual SwiftUI code/implementation. So I will try to give a guess how it can possibly work to help you build your own mental model about it.</p>
</blockquote>
<h3 id="heading-view-and-render-tree"><strong>View and Render tree</strong></h3>
<p>Similarly to UIKit, SwiftUI has its <strong>view tree</strong>. However, in contrast to UIKit, where UIView is an <strong>object</strong>, views in SwiftUI are <strong>structs</strong>, making the view tree <strong>ephemeral</strong>. This implies that view values in SwiftUI are <strong>transient</strong> and can be recreated whenever the <strong>state</strong> changes. Direct manipulation of the view tree isn't possible in SwiftUI. Instead of mutating the view tree directly as in UIKit, we modify the state, prompting SwiftUI to calculate a new view graph from this state. Views are recreated upon state changes, and the body is re-evaluated. SwiftUI optimizes this process by avoiding re-execution of a subview's body when it remains unchanged. To determine when to re-evaluate the body, SwiftUI utilizes the <strong>Attribute Graph</strong>. This is an internal component that builds a dependency graph for your data and its associated view tree. Unlike the view tree, the Attribute Graph is <strong>persistent</strong> and remains even when the body re-executes. And it has to, because it stores the state that must remain after view tree recreation. With each new generation of the view tree, SwiftUI performs a diff against the current state of the render tree, efficiently applying the necessary changes by adding and removing nodes from the render tree.</p>
<p>Chris Eidhof <a target="_blank" href="https://chris.eidhof.nl/presentations/day-in-the-life/">refers</a> to the <strong>Attribute Graph</strong> as the <strong>Render Tree</strong> to simplify the understanding of the concept. This makes sense to me because it serves as a sort of source of truth for rendering. Additionally, it's a fitting analogy with the render tree in UIKit.</p>
<h3 id="heading-view-render-loop">View Render Loop</h3>
<p>We can split render loop into several phases:</p>
<ul>
<li><p>Evaluation Phase</p>
</li>
<li><p>Layout Phase</p>
</li>
<li><p>Rendering Phase</p>
</li>
</ul>
<h3 id="heading-evaluation-phase"><strong>Evaluation Phase</strong></h3>
<p>Let’s highlight again: the view tree is <strong>ephemeral</strong>, while the render tree is <strong>persistent</strong>. Initially, the root node of the render tree should be created first, and it is probably something like node for ContentView to start with. Next, the view tree should be constructed. To initiate this process, ContentView's body should be evaluated. Step by step, from superviews to subviews in the hierarchy, SwiftUI prepares the view tree, composing it into a complex generic structure hidden beneath <code>some View</code>. View tree is used to construct the render tree. Once render tree construction is complete, SwiftUI can discard and throw away the entire view tree.</p>
<p>When the state is changed for a render tree node, the affected render sub-tree is invalidated. SwiftUI then rebuilds the corresponding view subtree and re-evaluates the bodies if necessary to update the render tree structure. As a result of this phase, SwiftUI should have the render tree rebuilt and ready for the subsequent phases.</p>
<h3 id="heading-layout-phase"><strong>Layout phase</strong></h3>
<p>After Attributed Graph is (re)constructed and ready, SwiftUI performs layout calculations to determine the position, size, and arrangement of each view within the parent view or container.</p>
<p>Layout phase is very well explained in articles from <a target="_blank" href="https://kean.blog/post/swiftui-layout-system">Alex Grebenyuk</a> and <a target="_blank" href="https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui">Paul Hudson</a>:</p>
<p>Layouts proceed top down: parent views offer their available space to their children, which decide their size based on that.</p>
<p>There are three steps in SwiftUI layout process:</p>
<p><strong>1. Parent Proposes Size for Child</strong></p>
<p>First, the root view offers the text a proposed size – in this case, the entire safe area of the screen, represented by an orange rectangle.</p>
<p><strong>2. Child Chooses its Size</strong></p>
<p>Text only requires that much size to draw its content. The parent has to respect the child's choice. It doesn't stretch or compress the child.</p>
<p><strong>3. Parent Places Child in Parent’s Coordinate Space</strong></p>
<p>And now the root view has to put the child somewhere, so it puts in right in the middle. That means views set the position of child’s, but not by SwiftUI externally.</p>
<blockquote>
<p><strong>NOTE</strong>: Layout in SwiftUI is easy until it's not.</p>
</blockquote>
<p>Steps sound pretty easy but what sometimes makes layouts in SwiftUI complex (especially after years of UIKit development) is that each view (or view modifier) behaves differently when it comes to determining the actual size from the proposed size. For example, a shape always fits itself into the proposed size; a horizontal stack takes on the size that’s needed for its children (up to the proposed size); and a text view takes on the size needed to render its text, unless its size exceeds the proposed size, in which case the text gets clipped. You need to keep all of that in mind.</p>
<h3 id="heading-rendering-phase">Rendering phase</h3>
<p>Once layout is completed views are supposed to be rendered on the screen. The exact process SwiftUI uses for rendering remains uncertain. Yet, it's reasonable to assume that SwiftUI relies on underlying UIKit, Core Animation, Core Graphics and Metal to do that. And keeping that in mind there should be some kind of mapping from SwiftUI view/render tree into UIKit view tree. In this case <strong>Rendering phase</strong> is probably can be better called <strong>Mapping to UIKit phase</strong>. We'll try to confirm this assumption in the next part of this post.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317905290/f0188dd0-ecb8-4bdd-b158-eb7514b2efcb.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-behind-the-render-loop">Behind the render loop</h3>
<p>That was a bunch of theory in the previous post. How about exploring some real details we can uncover from the debugger?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317930498/d76d9208-20b3-4c22-823b-39da4e4b529d.png" alt class="image--center mx-auto" /></p>
<p>Let’s create a new SwiftUI project in the Xcode and try to see what happens before body is called by placing a breakpoint inside the view body.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318107482/cfaef476-bd85-4d30-8778-1fe612eee5ac.png" alt class="image--center mx-auto" /></p>
<p>We can make a few observations here:</p>
<ul>
<li><p><code>AG::Graph</code> represents the Attribute Graph we previously mentioned. Occasionally, there might be a lengthy stack trace related to AG.</p>
</li>
<li><p><code>__lldb_unnamed_symbol123456</code> denotes parts of the SwiftUI code, which remain hidden from us.</p>
</li>
</ul>
<p>However, our primary interest lies in the lower part of the stack trace. There, we might observe something very interesting:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318121600/680d7ba1-ac02-4f47-995e-1d1e061c3142.png" alt class="image--center mx-auto" /></p>
<p>The bottom of the stack should actually provide you with a strong hint of how SwiftUI works internally:</p>
<ul>
<li><p><code>UIApplicationMain</code></p>
<ul>
<li><p><code>→ __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__</code></p>
<ul>
<li><p><code>→ CATransaction.commit()</code></p>
<ul>
<li><code>→ UIView(CALayerDelegate).layoutSublayersOfLayers</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Looks pretty familiar, doesn't it? It's just like the UIKit render loop we discussed in the previous post. This occurs in a pure SwiftUI application without a single UIView added by us. It actually proves that SwiftUI incorporates UIKit and CoreAnimation under the hood, inheriting plain old UIKit’s render loop we previously discussed.</p>
<p>During each runloop iteration, when the CALayer/UIView hierarchy traversal takes place as a part of the implicit CATransaction’s layout phase, SwiftUI conducts its render loop starting from the <code>layoutSublayers(of:)</code> call of the UIView that hosts the SwiftUI view hierarchy (to be precise, <code>_UIHostingView</code>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318190498/af510534-d8f4-47ad-bbb7-d8629c916b27.png" alt class="image--center mx-auto" /></p>
<p>Now let’s try to understand connection of SwiftUI and CATransaction better.</p>
<h3 id="heading-connection-with-catransaction"><strong>Connection with CATransaction</strong></h3>
<p>The same way as UIKit, in SwiftUI views will be rendered on screen when the implicit <code>CATransaction</code> is committed. You can observe connection of SwiftUI and CATransaction with simple example. We can add <code>CATransaction.begin()</code> into <code>onTapGesture</code>.</p>
<blockquote>
<p><strong>DISCLAIMER</strong>: I am strongly recommend you not to use CATransaction with SwiftUI in your codebase. It is just a demonstration example just for fun.</p>
</blockquote>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SwiftUIView</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">State</span> <span class="hljs-keyword">var</span> color: <span class="hljs-type">UIColor</span> = .red

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">Circle</span>()
            .fill(<span class="hljs-type">Color</span>(color))
            .onTapGesture {
                color = .yellow
                <span class="hljs-type">CATransaction</span>.begin()
                color = .green
            }
    }
}
</code></pre>
<p>As you probably remember from the post about UIKit, when you call <code>CATransaction.begin()</code>, a new transaction object is created and pushed into the shared transaction stack. So if you don’t do <code>CATransaction.commit()</code> after that, you simply break the transaction stack because of the way how transactions work. No layer tree changes will be committed from your app to the render server after that. So in our example we will not see nether yellow or green color update on the screen after tap. That shows us the connection between SwiftUI and CATransaction. SwiftUI relies on transactions the same way as UIKit does.</p>
<p>If we try to build similar example with explicit transaction as we did for UIKit we will discover another important aspect of SwiftUI.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SwiftUIView</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">State</span> <span class="hljs-keyword">var</span> color: <span class="hljs-type">UIColor</span> = .red

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">Circle</span>()
            .fill(<span class="hljs-type">Color</span>(color))
            .onTapGesture {
                <span class="hljs-type">CATransaction</span>.begin()
                color = .yellow
                <span class="hljs-type">CATransaction</span>.commit()
                sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
                color = .green
            }
    }
}
</code></pre>
<p>In similar UIKit example we would see yellow color in the middle once explicit transaction is commited before main thread had beed blocked by sleep().</p>
<pre><code class="lang-swift"><span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {
    <span class="hljs-type">CATransaction</span>.begin()
    button.backgroundColor = .yellow
    <span class="hljs-type">CATransaction</span>.commit()
    sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
    button.backgroundColor = .green
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318319968/7aa4994a-1587-4829-a255-75e6f0498892.png" alt class="image--center mx-auto" /></p>
<p>But in SwiftUI, it seems to not working the same way, but why? 🤔</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318330147/98522fd4-3dc7-415c-8fb4-13a79d94f1f2.png" alt class="image--center mx-auto" /></p>
<p>Let’s remember how CATransaction works. It commits the new changes of layer tree to the render server. But in our SwiftUI example we didn’t do any changes to any CALayer inside our explicit transaction (between begin and commit).</p>
<pre><code class="lang-swift"><span class="hljs-type">CATransaction</span>.begin()
color = .yellow <span class="hljs-comment">// changes only color state property, no CALayer changed</span>
<span class="hljs-type">CATransaction</span>.commit()
</code></pre>
<p>We only changed the state of SwiftUIView. And it needs to do another SwiftUI render loop to actually rebuild view tree and render tree to apply changes to underlying layers.</p>
<p>This is actually why none of the changes after the body evaluation or onAppear call aren’t rendered instantly. At the moment of body evaluation/onAppear/onChange underneath CALayer properties are not yet changed so at that moment nothing to commit yet. We need to do a full SwiftUI render loop first to change underlying layers that will be committed with implicit transaction and eventually rendered.</p>
<p>We can extend this example a bit. We already know that in the root of Swift hierarchy lies <code>_UIHostingView</code>. It is UIKit UIView and it has it’s CALayer under the hood. There is no way to access <code>_UIHostingView</code> from SwiftUI code but If we set <code>_UIHostingView</code> to a global variable from UIKit side, we will be able to access it inside <code>onTapGesture</code>. (Never do anything like that in your production code 🙈)</p>
<p>If we change the <code>backgroundColor</code> of hostingView inside the explicit transaction on tap gesture:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> hostingView: <span class="hljs-type">UIView?</span> <span class="hljs-comment">// Set it globally</span>

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SwiftUIView</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">State</span> <span class="hljs-keyword">var</span> color: <span class="hljs-type">UIColor</span> = .red

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">Circle</span>()
            .fill(<span class="hljs-type">Color</span>(color))
            .onTapGesture {
                <span class="hljs-type">CATransaction</span>.begin()
                hostingView?.backgroundColor = .yellow
                color = .yellow
                <span class="hljs-type">CATransaction</span>.commit()
                sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
                color = .green
            }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318420433/c33ed9a8-77b9-46fc-8509-22b1079a8e53.png" alt class="image--center mx-auto" /></p>
<p>Flow will be like that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318430377/19156198-2059-4ad0-91c7-fe349ab3e51d.png" alt class="image--center mx-auto" /></p>
<p>It shows us that CATransaction works with layer tree changes as expected even inside SwiftUI code. But it also shows that it makes no sense to use explicit CATransaction in SwiftUI views once you usually don’t access CoreAnimation layer tree directly from SwiftUI (and you should not).</p>
<p>We used hosting view in the last example, but didn’t talk about it much so far. Let’s discuss it next.</p>
<h3 id="heading-hostingview-as-a-host">HostingView as a host</h3>
<p>If we look in Xcode structure while debugging a new UIKit project we will see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318472336/2ab83e42-9cd8-49e4-a84e-c4a0cf68e593.png" alt class="image--center mx-auto" /></p>
<p>And in the root of a new SwiftUI application you will see exactly the same structure but with <code>HostingViewController</code> with <code>_UIHostingView</code> that hosts SwiftUI <code>ContentView</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318479953/fc65436e-bdae-4f73-906a-06d920a5ee3c.png" alt class="image--center mx-auto" /></p>
<p>All of SwiftUI's view tree is hosted by <code>_UIHostingView</code>, controlled in turn by the <code>UIHostingController</code>. Any changes to the view structure applied to <code>_UIHostingView</code> and its subviews by SwiftUI internally are rendered by the usual CoreAnimation render loop we discussed earlier.</p>
<p>When you add new SwiftUI views to your ContentView body, SwiftUI will add UIKit subviews to <code>_UIHostingView</code> if <strong>needed</strong>. I mentioned "<strong>if needed</strong>" because not every SwiftUI view corresponds directly to a UIKit UIView under the hood. There are multiple ways graphics can be rendered in SwiftUI. SwiftUI can internally create a <code>UIView</code> to display them, or it might not. For instance, while some views like <code>VStack</code> participate in layout, they don't have their own <code>UIView</code> under the hood.</p>
<p>Let’s consider simple SwiftUI view as example:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{    
        <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
            <span class="hljs-type">VStack</span> {
                <span class="hljs-type">Image</span>(uiImage: .strokedCheckmark)
                <span class="hljs-type">Text</span>(<span class="hljs-string">"Text"</span>)
                <span class="hljs-type">TextField</span>(<span class="hljs-string">"Title"</span>, text: .constant(<span class="hljs-string">"TextField"</span>))
            }
      }
}
</code></pre>
<p>View <strong>body</strong> generic type hidden behind <code>some View</code> will be following:</p>
<pre><code class="lang-swift"><span class="hljs-type">VStack</span>&lt;
    <span class="hljs-type">TupleView</span>&lt;
        (<span class="hljs-type">Image</span>, <span class="hljs-type">Text</span>, <span class="hljs-type">TextField</span>&lt;<span class="hljs-type">Text</span>&gt;)
    &gt; 
&gt;
</code></pre>
<p>Looking at the above code sample, it’s easy to jump to the conclusion that the resulting UI will consist of:</p>
<ul>
<li><p>Vertically aligned <code>UIStackView</code> which contains:</p>
<ul>
<li><p><code>UIImage</code></p>
</li>
<li><p><code>UILabel</code></p>
</li>
<li><p><code>UITextField</code></p>
</li>
</ul>
</li>
</ul>
<p>However, only part of those assumptions is actually true. Unlike CALayer within each UIView, not every SwiftUI view has a UIView behind it. Some SwiftUI views do utilize UIKit under the hood, although not all of them.</p>
<p>In our example we will see the following structure of subviews added to hosting view:</p>
<ul>
<li><p><code>_UIGraphicsView</code> for <code>Image</code></p>
</li>
<li><p><code>CGDrawingView</code> for <code>Text</code></p>
</li>
<li><p><code>ViewHost</code> (It’s actually just <code>UIViewRepresentable</code> ) with hosted <code>UITextField</code> for <code>TextField</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318788018/3dea615f-e113-4b0b-a841-82f8ee9c7eaf.png" alt class="image--center mx-auto" /></p>
<p>I'll attempt to group all views based on the way they are backed up by UIKit:</p>
<ul>
<li><p>No UIView behind</p>
<ul>
<li>Layout/structure based views - <code>Spacer</code>, <code>V(H/Z)Stack</code>, <code>LazyH(V)Stack</code></li>
</ul>
</li>
<li><p>Backed by <code>_UIGraphicsView</code></p>
<ul>
<li>Graphics based views - <code>Color</code>, <code>Image</code>, <code>Divider</code></li>
</ul>
</li>
<li><p>Backed by <code>CGDrawingView</code> class (subclass of <code>_UIGraphicsView</code>)</p>
<ul>
<li><p>Text based views - <code>Text</code>, <code>Button</code>, <code>TextEditor</code></p>
</li>
<li><p><code>CG</code> prefix gives us a hint that SwiftUI uses CoreGraphics for text rendering the same way as UIKit does it.</p>
</li>
</ul>
</li>
<li><p>Specific UIKit.UIView hosted by UIViewRepresentable</p>
<ul>
<li><code>TextField</code> (with UITextField), <code>ProgressView</code>, <code>Slider</code>, <code>List</code></li>
</ul>
</li>
<li><p>Composition of views listed above</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318823413/d8dedbf9-20cf-498b-b0e8-933d6204f1cf.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>IMPORTANT NOTE</strong>: You need to keep in mind that underlying implementation of SwiftUI views can change with time. I guess it is the reason why Apple keeps SwiftUI implementation private, they can change it with time but SwiftUI API will remain he same. For now, SwiftUI mostly wraps existing UI frameworks like UIKit, CoreAnimation, Core Graphics. In the future if Apple decides to, they can deprecate UIKit without changing the SwiftUI APIs and rewrite the foundation of it. I see that as a very long way though, so UIKit probably will be there for some time.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708318870510/7921f722-ef1c-4aff-893b-ac4a0ee5a63c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-recap">Recap</h3>
<ul>
<li><p>SwiftUI is a <strong>declarative</strong> and <strong>data-driven</strong> UI framework.</p>
</li>
<li><p><strong>The Role of the</strong><code>body</code>:</p>
<ul>
<li><p>The <code>body</code> of a view defines both its layout and content.</p>
</li>
<li><p>Behind the <code>body</code>, SwiftUI operates through a complex system involving <strong>view tree</strong> and <strong>attribute graph</strong> also known as <strong>render tree</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>View Tree and Render Tree:</strong></p>
<ul>
<li><p>SwiftUI has a view tree, but unlike UIKit, views in SwiftUI are transient structs.</p>
</li>
<li><p>Views are recreated when state changes, and SwiftUI optimizes this process by leveraging the attribute graph to track state changes.</p>
</li>
</ul>
</li>
<li><p><strong>View Render Loop:</strong></p>
<ul>
<li><p>The view render loop involves three phases:</p>
<ul>
<li><p>evaluation phase</p>
</li>
<li><p>layout phase</p>
</li>
<li><p>rendering phase (or mapping to UIKit phase)</p>
</li>
</ul>
</li>
<li><p><strong>Evaluation phase</strong> includes <strong>rebuilding view tree, diffing</strong> and <strong>body evaluation</strong>.</p>
</li>
<li><p><strong>Layout phase</strong> proceed goes top down: parent views offer their available space to their children, which decide their size based on that.</p>
</li>
<li><p>We don’t know much about SwiftUI <strong>rendering phase</strong>. But we know it relies on underlying UIKit, Core Animation and Core Graphics for that. <strong>Rendering phase</strong> is probably can be better called <strong>Mapping to UIKit phase</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>CATransaction and SwiftUI:</strong></p>
<ul>
<li>By relying on UIKit/CoreAnimation SwiftUI internally utilizes implicit <code>CATransaction</code> for managing changes to the layer tree. Although you don't use <code>CATransaction</code>s directly in <code>SwiftUI</code> apps, the framework internally still uses Core Animation and <code>CATransaction</code>s for drawing and animations. Together with the render server, Core Animation is very foundational to iOS and SwiftUI.</li>
</ul>
</li>
<li><p><strong>HostingView and UIKit Integration:</strong></p>
<ul>
<li><p>SwiftUI view tree is hosted by <code>_UIHostingView</code>, controlled by <code>UIHostingController</code>.</p>
</li>
<li><p>Some views internally use UIKit views (like i.e <code>UITextField</code> used and hosted by <code>TextField</code>). Even though not all SwiftUI views are mapped to UIKit views internally.</p>
</li>
<li><p>SwiftUI currently wraps existing UI frameworks like UIKit, CoreAnimation, and Core Graphics. SwiftUI abstracts away complexities and could potentially allow Apple to evolve underlying implementations in the future without changing the SwiftUI API.</p>
</li>
</ul>
</li>
</ul>
<p>Thank you for reading and see you in the next posts!</p>
]]></content:encoded></item><item><title><![CDATA[Behind the scenes of UI: Part 1 - UIKit]]></title><description><![CDATA[if you prefer to watch video you can find it here.
Intro
As iOS developers, we engage extensively with user interfaces. Understanding of various aspects of UI like lifecycle or layout is crucial for working with UIKit and SwiftUI. Do developers truly...]]></description><link>https://vbat.dev/behind-the-scenes-of-ui-part-1-uikit</link><guid isPermaLink="true">https://vbat.dev/behind-the-scenes-of-ui-part-1-uikit</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[UIkit]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Mon, 19 Feb 2024 20:13:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708311489060/e0d5a86c-7b09-42c6-8274-580e02c6d729.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>if you prefer to watch video you can find it <a target="_blank" href="https://youtu.be/Hxi97NK5wac">here</a>.</p>
<h3 id="heading-intro">Intro</h3>
<p>As iOS developers, we engage extensively with user interfaces. Understanding of various aspects of UI like lifecycle or layout is crucial for working with UIKit and SwiftUI. Do developers truly need to comprehend the underlying mechanics of UI, or they can solely rely on the provided APIs? I believe that understanding of framework's internal workings can offer significant benefits like avoiding poor performance, sporadic glitches, unexpected animations or layout bugs. Moreover, it is a lot of fun!</p>
<p>The primary objective of this series of posts is to provide you with a comprehensive overview of how UIKit and SwiftUI works under the hood. We will discuss UIKit first and save SwiftUI for the next post.</p>
<h3 id="heading-uikit">UIKit</h3>
<p>Let's consider an example that often can be seen in UIKit applications. At a certain point in your app, you want to add a subview within your view controller's view.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Not really long time ago in a far galaxy view controller...</span>
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
     <span class="hljs-keyword">super</span>.viewDidLoad()
     <span class="hljs-keyword">let</span> subview = <span class="hljs-type">QuestionView</span>()
     view.addSubview(subview)
     setupConstraints(<span class="hljs-keyword">for</span>: subview)
}
</code></pre>
<p>Example looks trivial, right? But how it actually works?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708313906963/e0933bf9-49ee-4da9-86f0-42bda9715755.png" alt class="image--center mx-auto" /></p>
<p>Let’s split the bigger how into smaller questions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708313974848/c9fb1571-b57f-45bf-92a5-95d9ea7c9a2a.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>What happens in the app before we update the UI in <code>viewDidLoad</code>?</p>
</li>
<li><p>How the change of UI is applied? How new subview appears on the screen?</p>
</li>
</ul>
<p>We will go step by step and first of all, let’s start with understanding what happens after we launch the app. Xcode Debugger will be our best friend today. Let’s create empty UIKit Swift project in Xcode and put a break point into <code>viewDidLoad</code>. By default you will see something like this but you can expand the call stack it by tapping on button in the bottom-right side of Debug navigator.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314066421/44eda020-bffb-4725-b950-2ba6815af790.png" alt class="image--center mx-auto" /></p>
<p>Tap on this button and you will see much more details in the call stack.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314127608/b6d800d0-7510-4dd1-978f-cd51e4883dfd.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE</strong>: Probably obvious and well known fact but we could see here that our UI related code is happening on the main thread. UIKit is not thread-safe, you should work with it on main thread only.</p>
</blockquote>
<h2 id="heading-uiapplicationmain">UIApplicationMain</h2>
<p>Let’s look on the bottom of the stack on previous screenshot:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314158251/397df6aa-1ccf-4c93-ba90-015fe0e72b5d.png" alt class="image--center mx-auto" /></p>
<p>What we see here is the main function. It is called by the operating system's runtime environment when your app launches. The process involves the system initializing your app's environment, setting up the app's main thread, and then calling the main function, which can be considered as an entry point for your app's execution .</p>
<p>For example, if you create an Objective-C (yep you still can) project in Xcode you will see something like this:</p>
<pre><code class="lang-objectivec"><span class="hljs-keyword">int</span> main(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> * argv[]) {
    <span class="hljs-built_in">NSString</span> * appDelegateClassName;
    <span class="hljs-keyword">@autoreleasepool</span> {
        <span class="hljs-comment">// Setup code that might create autoreleased objects goes here.</span>
        appDelegateClassName = <span class="hljs-built_in">NSStringFromClass</span>([AppDelegate <span class="hljs-keyword">class</span>]);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">UIApplicationMain</span>(argc, argv, <span class="hljs-literal">nil</span>, appDelegateClassName);
}
</code></pre>
<p>Just imagine, all that happens inside your app, happens inside main function. It’s funny but your entire app is nothing but a single giant call of main function.</p>
<p>But main function itself doesn’t do much. It’s main purpose is to call <code>UIApplicationMain()</code> function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314217530/3ebd0209-f317-4ca9-82fc-b7b16c002095.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>NOTE: You can read more about <code>UIApplicationMain</code><a target="_blank" href="https://developer.apple.com/documentation/uikit/1622933-uiapplicationmain">here</a> and <a target="_blank" href="https://oleb.net/blog/2012/02/app-launch-sequence-ios-revisited/">here</a></p>
</blockquote>
<p><strong>UIApplicationMain</strong> in it’s turn does a lot to setup our iOS app:</p>
<ul>
<li><p>creates the <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiapplication/">UIApplication</a> shared instance and application delegate;</p>
</li>
<li><p>runs the UIApplication that triggers creation of scene, scene delegate and etc;</p>
</li>
<li><p>[MOST IMPORTANT FOR US NOW] It runs the main event loop by calling <strong>Runloop.run()</strong>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314270452/7588c6b5-5732-4401-ba57-01830da4e99f.png" alt class="image--center mx-auto" /></p>
<p>If you haven’t heard about <strong>Runloop</strong> so far, next chapter is for you.</p>
<h2 id="heading-runloop">Runloop</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314301865/b8113037-a413-432a-bb69-0dccd4050b01.png" alt class="image--center mx-auto" /></p>
<p>Simply speaking, <a target="_blank" href="https://developer.apple.com/documentation/foundation/runloop"><strong>Runloop</strong></a> is the infinite cycle of events happening in the app.</p>
<p><strong>Runloop</strong> is the reason why <code>main()</code> and <code>UIApplicationMain()</code> calls will actually never return.</p>
<p>Every iteration <strong>Runloop</strong> has three main jobs to do:</p>
<ul>
<li><p>Handle the events;</p>
</li>
<li><p>Notify observers about runloop’s state changes;</p>
</li>
<li><p>Put the thread to a sleep when it have nothing to do.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314355563/af94fc51-febf-406b-b421-b507f577ad87.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE 1:</strong> Similar to many other things in high-level frameworks like UIKit or Foundation, <strong>Runloop</strong> (aka NSRunloop) is like a Foundation side proxy for <strong>CFRunloop</strong> (CF stands for Core Foundation), created to hide some interface CFRunloop have like adding event sources, observers or a new runloop mode.</p>
<p><strong>NOTE 2</strong>: You can read about the runloop iteration steps more <a target="_blank" href="https://khorbushko.github.io/article/2020/11/29/runloop-in-depth.html">here</a> and <a target="_blank" href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html">here</a>. If needed, you can find the detailed iteration steps directly in code <a target="_blank" href="https://opensource.apple.com/source/CF/CF-744.12/">here</a> once Core Foundation is open-source.</p>
<p><strong>NOTE 3</strong>: The <strong>Runloop</strong> is associated with a <strong>thread</strong>, with only one runloop linked to each thread. It automatically runs on the main thread from the app's start, but for other threads, you must explicitly run it.</p>
<p><strong>NOTE 4</strong>: <strong>CFRunloop</strong> is thread-safe, but Runloop (<strong>NSRunloop</strong>) is not.</p>
<p><strong>NOTE 5</strong>: You might be wondering how many runloop iterations occur per second. However, this question isn't correct to ask. The run loop frequency isn't fixed and isn't bound to the screen refresh rate either. The run loop frequency and the screen refresh rate operate independently. (Usually, run loop frequency surpasses potential screen refresh rates unless the main thread is blocked)</p>
</blockquote>
<h3 id="heading-runloop-events">Runloop events</h3>
<p>Runloop handles several different event types:</p>
<ul>
<li><p>Blocks of GCD main queue</p>
</li>
<li><p>Timers</p>
</li>
<li><p>Sources (0 and 1)</p>
</li>
<li><p>Observers</p>
</li>
</ul>
<p>If you place a breakpoint at nearly any line of code in your app, you'll likely encounter one of the functions listed below in the bottom of the call stack. These functions serve no purpose other than helping in debugging within the stack trace. RunLoop ensures that all main thread code is executed through one of these functions. They also allow you to understand <strong>the exact type of event</strong> processed by the runloop that triggered the execution of the code where you've set a breakpoint.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
<span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
<span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
<span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
<span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
<span class="hljs-keyword">static</span> void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
</code></pre>
<p>Main queue block or timer events are usually clear to developers who have ever created <a target="_blank" href="https://developer.apple.com/documentation/foundation/timer">timer</a> or used <a target="_blank" href="https://www.swiftbysundell.com/basics/grand-central-dispatch/">GCD</a> main queue. Conversely, <strong>sources</strong> and <strong>observers</strong> hold more mystery regarding their usage and purpose. Let’s shed a light on them next.</p>
<h3 id="heading-runloop-sources">Runloop Sources</h3>
<p>Simply speaking, runloop source is just a way to say to runloop that some event happened at some other point of the app or even in the different process and runloop should handle this event by calling related callback. Sources allow the run loop to be aware of external events and handle them appropriately.</p>
<p>As you probably noticed there are 2 type of <a target="_blank" href="https://developer.apple.com/documentation/corefoundation/cfrunloopsource-rhr">sources</a> - <code>source0</code> and <code>source1</code>. Not really a clear naming in terms of purpose, right? They named so because the <code>version</code> field of their context is <a target="_blank" href="https://developer.apple.com/documentation/corefoundation/cfrunloopsourcecontext">0</a> or <a target="_blank" href="https://developer.apple.com/documentation/corefoundation/cfrunloopsourcecontext1">1</a>.</p>
<p>But they serve different goals:</p>
<ul>
<li><p><strong>Version 0</strong> sources are for <strong>inner-app communication;</strong></p>
</li>
<li><p><strong>Version 1</strong> sources are for <strong>inter-process communication (IPC).</strong></p>
</li>
</ul>
<h3 id="heading-version-0-sources-inner-app-communication">Version 0 sources (<strong>inner-app communication)</strong></h3>
<p>They managed by the application (kind of inner-app messaging mechanism). When a source is ready to fire, some other part of the application, perhaps code <strong>on a separate thread</strong>, must call <a target="_blank" href="https://developer.apple.com/documentation/corefoundation/1543700-cfrunloopsourcesignal">CFRunLoopSourceSignal</a> function to tell the run loop that the source is ready to fire so the related callback must be called. RunLoop must be awaken (with <code>CFRunLoopWakeUp()</code>) for the source to be handled.</p>
<p>Examples of version 0 sources are:</p>
<ul>
<li><p><a target="_blank" href="https://developer.apple.com/documentation/objectivec/nsobject/1414900-performselector"><strong>performSelector(onMainThread:with:waitUntilDone:)</strong></a> of NSObject</p>
</li>
<li><p><a target="_blank" href="https://developer.apple.com/documentation/corefoundation/cfsocket-rg7"><strong>SFSocket</strong></a><strong>.</strong></p>
</li>
<li><p>UIKit’s <strong>touch events fetching.</strong> If you put a break point into UIButton tap selector you will notice that UIEvent that triggered this action was fetched using version 0 source.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314601296/5db51b8e-7f67-41b6-b8e1-a5f8eb3a3a72.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-version-1-sources-inter-process-communication-ipc">Version 1 s<strong>ources (inter-process communication - IPC)</strong></h3>
<p>Version 1 sources managed by the kernel. These sources are automatically signaled by the kernel when a message arrives on the source’s Mach port. Mach ports are primarily a mechanism for inter-process communication (IPC) within the macOS and iOS operating systems. They allow processes (and our app is one of them) to send messages and data between each other.</p>
<p><a target="_blank" href="https://developer.apple.com/documentation/quartzcore/cadisplaylink">CADisplayLink</a> which can be used to synchronize your code to the display refresh rate, uses a type 1 source under the hood.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> displayLink = <span class="hljs-type">CADisplayLink</span>(target: <span class="hljs-keyword">self</span>, selector: #selector(update))
displayLink.add(to: .current, forMode: .common)

<span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">update</span><span class="hljs-params">()</span></span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Updating!"</span>)
}
</code></pre>
<ul>
<li><p><code>CFMachPort</code> is another example of version 1 source.</p>
</li>
<li><p>Even though mach ports are not used directly for network communication, they may be used internally by the operating system or by networking components to facilitate communication between processes, which can indirectly impact network-related functionality.</p>
</li>
</ul>
<blockquote>
<p><strong>NOTE</strong>: You can also create <a target="_blank" href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW7">custom sources</a> for both versions 0 and 1 if needed.</p>
</blockquote>
<h3 id="heading-run-loop-observers">Run Loop Observers</h3>
<p>We already know from runloop iteration discussion that runloop has <strong>observers</strong> it needs to notify when runloop’s state has changed.</p>
<p>The <code>CFRunLoopObserver</code> API lets you observe the behavior of <code>CFRunLoop</code> and be notified of its activity: when it processes events, when it goes to sleep, etc.</p>
<p>You can associate run loop observers with the following <code>CFRunLoopActivity</code> events in your run loop:</p>
<ul>
<li><p>The entrance to the run loop. (<code>kCFRunLoopEntry</code>)</p>
</li>
<li><p>When the run loop is about to process a timer. (<code>kCFRunLoopBeforeTimers</code>)</p>
</li>
<li><p>When the run loop is about to process an input source.(<code>kCFRunLoopBeforeSources</code>)</p>
</li>
<li><p>When the run loop is about to go to sleep. (<code>kCFRunLoopBeforeWaiting</code>)</p>
</li>
<li><p>When the run loop has woken up, but before it has processed the event that woke it up. (<code>kCFRunLoopAfterWaiting</code>)</p>
</li>
<li><p>The exit from the run loop. (<code>kCFRunLoopExit</code>)</p>
</li>
</ul>
<p>You probably won’t ever need to create runloop observers in your app, but being aware of the existence and purpose of runloop observers can be useful for understanding how UIKit operates. But before we will try to understand connection between runloop observers and UIKit, we need to understand what is hidden behind the UIKit.</p>
<p>Under the hood of UIKit lies a lower-level framework called <strong>Core Animation</strong>, responsible for numerous aspects of UI, animations and rendering. Let’s look on CoreAnimation more closely now.</p>
<h2 id="heading-coreanimation">CoreAnimation</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314754901/f4f9f80f-dc5c-4d7b-83b0-b555a32cf5b0.png" alt class="image--center mx-auto" /></p>
<p>UIKit is a well known framework from Apple. We use it every day to create UI in our apps. UIKit contains all kinds of components. But surprisingly, UIKit delegates a significant portion of UI-related tasks such as animations, layout, and rendering to <strong>Core Animation</strong> under the hood.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314857997/e00ccfc3-fb41-4653-89a0-bb1a98c5fe5d.png" alt class="image--center mx-auto" /></p>
<p>Behind every <code>UIView</code> hides <code>CALayer</code>. When UIView is created it creates <code>CALayer</code> under the hood and sets itself as a <code>CALayerDelegate</code> which is responsible for several things such as providing the layer's content, subviews layout and animating property changes. For example, UIView is actually the reason why UIView properties are not animatable by default (opposite to CALayer), UIView disabled default animations as a CALayerDelegate to it’s layer. Properties and methods of UIView that somehow change the view appearance are actually just proxied properties of CALayer. For example, <code>frame</code>, <code>bounds</code>, <code>backgroundColor</code> or <code>isHidden</code> are actually proxied properties of CALayer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314924985/1f5c8267-6944-4c10-a07d-63fca9da1adb.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE</strong>: The several things that truly handled by UIKit are user interaction (like touches and gestures) and accessibility.</p>
</blockquote>
<p>Views on the screen together create the <strong>view hierarchy</strong> or <strong>view tree</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708314981869/562f19df-752a-4535-9bb3-1a48b9859374.png" alt class="image--center mx-auto" /></p>
<p>Similarly, CALayers form the layer hierarchy in parallel with views, so behind the view tree, there exists a layer tree. In fact, Core Animation handles <a target="_blank" href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html">not only one layer tree but three of them</a>:</p>
<ul>
<li><p>Model layer tree;</p>
</li>
<li><p>Presentation layer tree;</p>
</li>
<li><p>Render layer tree (not accessible from the app).</p>
</li>
</ul>
<p>You can access model tree and presentation nodes by using <code>model()</code> and <code>presentation()</code> methods of CALayer.</p>
<p>Each layer tree has a different role in making the content of your app appear onscreen:</p>
<ul>
<li><p><strong>Model layer tree</strong> (or simply “layer tree”) is the one your app interacts with the most. The objects in this tree are the model objects that store the target (final state) values for any animations. Whenever you change the property of a layer, you use one of these objects.</p>
</li>
<li><p><strong>Presentation tree</strong> contains the in-flight values for any running animations. Whereas the layer tree objects contain the target values for an animation, the objects in the presentation tree reflect the current values as they appear onscreen. You should never modify the objects in this tree. Instead, you use these objects to read current animation values, perhaps to create a new animation based at those values. You should access objects in the presentation tree only while an animation is in flight. While an animation is in progress, the presentation tree contains the layer values as they appear onscreen at that instant. This behavior differs from the model layer tree, which always reflects the last value set by your code and is equivalent to the final state of the animation.</p>
</li>
<li><p><strong>Render tree</strong> perform the actual animations and are private to Core Animation (not accessible from the app). It is the closest layer tree to the actual screen state.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315102716/cb35a3cc-b76f-41a6-bd11-fe69ca10069c.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>REMEMBER</strong>: The presentation layer is not the original layer, just a temporary ‘ghost‘ layer. It is approximated state of render tree layer. Can be useful to use its current values to create new animations or to interact with layers while an animation is taking place.</p>
<p><strong>NOTE</strong>: The initial structure of each tree matches the structure of the view hierarchy exactly. However, the app can add additional layer objects (layers not associated with a view) into the layer hierarchy if needed.</p>
</blockquote>
<p>Ok, now we know that view we have 3 layer trees behind view tree but once the render tree is the closest tree to the screen state how changes of the model layer tree are actually applied to the render tree?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315143935/2cb75baa-8533-4aaf-bc3a-a9a2d7698a90.png" alt class="image--center mx-auto" /></p>
<p><strong>Important</strong> to understand that UI changes are not applied to be updated on screen every time something is changed. CoreAnimation groups changes to the layer tree into <code>CATransaction</code> - a mechanism for grouping multiple layer-tree operations into atomic updates to the render tree.</p>
<h2 id="heading-catransaction">CATransaction</h2>
<p>When we want to modify a layer and there are several properties that require changes, applying these changes one by one wouldn't be efficient due to optimization reasons. Even if we modify a property multiple times consecutively, Core Animation consolidates these updates into a single transaction update sent to the render tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315266247/8688b181-383b-4b30-b820-3ef9f9792967.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315279229/b6c7fcc1-cab7-46a1-ae0f-39fcc6437ce3.png" alt class="image--center mx-auto" /></p>
<p>But when and where exactly is this CATransaction happening? Do you remember I mentioned the connection between UIKit and Runloop observers? Well, it’s actually a connection between CoreAnimation and Runloop observers. The thing is that Core Animation observes the <code>CFRunLoopActivity.kCFRunLoopBeforeWaiting</code> event; when this event fires at the end of every runloop iteration, i<strong>f any changes happened to model layer tree</strong> (like bounds changed or animations added)<strong>,</strong> CoreAnimation groups a bunch of changes into single <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/catransaction">transaction</a> (called <strong>implicit</strong><a target="_blank" href="https://developer.apple.com/documentation/quartzcore/catransaction">CATransaction</a> ) and <strong>commits</strong> it to be applied to the <strong>render tree</strong> altogether at once. Implicit transaction is created only if there were changes to model layer tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315357571/cbacb5de-5511-4b4a-91c1-a7dfb6c27a20.png" alt class="image--center mx-auto" /></p>
<p>If you put a breakpoint into <code>layoutSubviews()</code> method of your view, you will see that it is called from runloop observer callback function by discovering corresponding function on the call stack: <code>__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315408812/8c88886f-4e1b-4843-9832-2b1e8444d268.png" alt class="image--center mx-auto" /></p>
<p>Let’s update our scheme with several new steps:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315404020/6e11ee9c-9c8b-43e4-81f4-1353c7c255b3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-catransaction-by-examples">CATransaction by examples</h3>
<p>To understand <strong>CATransaction</strong> better lets consider few examples inspired by <a target="_blank" href="https://fabernovel.github.io/2021-01-04/uikit-rendering-part-2">this cool post</a>. Lets imagine we have a button with background color initially set to <code>.red</code>:</p>
<pre><code class="lang-swift">button.backgroundColor = .red
...
<span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {
    button.backgroundColor = .yellow <span class="hljs-comment">// implicit transaction begins here</span>
    sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
    button.backgroundColor = .green
}
</code></pre>
<p>When we tap on button we:</p>
<ul>
<li><p>change the <code>backgroundColor</code> to <code>.yellow</code></p>
</li>
<li><p>blocks the main thread for 3 seconds with <code>sleep()</code> function</p>
</li>
<li><p>change the <code>backgroundColor</code> to <code>.green</code></p>
</li>
</ul>
<p>How do you think this code will work?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315461457/521c7650-8af3-4a7d-bf40-3edb4a9fc9c6.png" alt class="image--center mx-auto" /></p>
<p>It's not immediately clear why the yellow color wasn't visible, right? This occurred because after setting the yellow color and changing the model layer tree, we blocked the main thread, suspending the runloop iteration. So, the implicit <code>CATransaction</code> wasn't committed until a 3-second interval had elapsed. However, immediately after these 3 seconds, we changed the color to <code>.green</code>. This "green" change is committed with the implicit <code>CATransaction</code> at the runloop cycle's end. So the yellow color has never become visible.</p>
<h3 id="heading-explicit-transactions">Explicit transactions</h3>
<p>We mentioned earlier that CoreAnimation sends <strong>implicit transaction on every runloop iteration</strong> but transaction can be also <strong>explicit</strong> if you create it in your code like this:</p>
<pre><code class="lang-swift"><span class="hljs-type">CATransaction</span>.begin()
button.backgroundColor = .yellow
<span class="hljs-comment">// change anything else here if you want</span>
<span class="hljs-type">CATransaction</span>.commit()
</code></pre>
<p>Any changes made to layer properties between <code>CATransaction.begin()</code> and <code>CATransaction.commit()</code> are grouped together and committed atomically.</p>
<blockquote>
<p><strong>NOTE 1</strong>: Explicit and implicit transactions are the same CATransaction. The only difference is that whether we begin and commit it manually or it is done automatically by CoreAnimation on any layer change.</p>
<p><strong>NOTE 2</strong>: Now you know about <code>begin</code> and <code>commit</code> methods of CATransaction we can address implicit transaction logic with more details. An implicit transaction is begun by CoreAnimation when you change the layer tree for the first time during the current runloop iteration. It then groups any subsequent changes during the runloop iteration inside of this transaction. Finally, at the end of the current runloop iteration when kCFRunLoopBeforeWaiting fires, the implicit transaction is committed.</p>
</blockquote>
<p>Let’s see how our previous example result will change with explicit transaction:</p>
<pre><code class="lang-swift">button.backgroundColor = .red
...
<span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {
    <span class="hljs-type">CATransaction</span>.begin()
    button.backgroundColor = .yellow
    <span class="hljs-type">CATransaction</span>.commit()
    sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
    button.backgroundColor = .green <span class="hljs-comment">// implicit transaction begins here</span>
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315577518/92076220-81ca-4e81-8cd1-c0e0a49fb3bc.png" alt class="image--center mx-auto" /></p>
<p>Now we see yellow color state. Cool, but why?</p>
<p>We committed the “yellow state” change with <strong>explicit transaction</strong> right before blocking the main thread so it was changed instantly after the tap. Then after 3 seconds we do “green state” change that goes with <strong>implicit transaction</strong> at the end of runloop cycle.</p>
<blockquote>
<p><strong>NOTE 3</strong>: You are probably wondering now what is the case for using explicit transaction. I guess it was more useful long time ago when UIKit animation API was not as developed as it is today. For example, <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiview/1622418-animate">animate(withDuration:)</a> became available only since iOS 4. Before that, explicit transaction was a good way to commit animation and easily change the duration, timing function, or other parameters of animation. But actually, explicit transactions still can be useful today in some rare <a target="_blank" href="https://medium.com/@joncardasis/better-ios-animations-with-catransaction-72a7425673a6">cases</a> of animating layer and view properties together.</p>
</blockquote>
<h3 id="heading-nested-transactions">Nested transactions</h3>
<p>Let’s update our example once again. Let’s put our explicit transaction inside another explicit transaction.</p>
<pre><code class="lang-swift">button.backgroundColor = .red
...
<span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {
    <span class="hljs-type">CATransaction</span>.begin()
    <span class="hljs-type">CATransaction</span>.begin()
    button.backgroundColor = .yellow
    <span class="hljs-type">CATransaction</span>.commit()
    sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
    button.backgroundColor = .green
    <span class="hljs-type">CATransaction</span>.commit()
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315673190/91b5a8c3-650d-4daa-aa2c-9c27cf9d4738.png" alt class="image--center mx-auto" /></p>
<p>As you can see it produces the same result as the first example. No yellow state appeared.</p>
<p>That occurred because transactions can be <strong>nested</strong>. When one transaction is nested within another, the commit of the "internal" (child) transaction only takes effect when the parent commits. To better understand why nested transactions function this way, consider that transactions are managed by a transaction stack (LIFO - last in, first out). When you begin a transaction, it's added to the transaction stack, and upon committing, it's removed. If you insert several transactions inside transaction stack they become nested and those transaction(s) will really be committed when the stack becomes empty.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315752826/99ba9b73-a2e9-4bfa-94d5-65d3de49fb49.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE</strong>: Implicit transaction can’t be nested inside explicit one once implicit transaction begins when you change layer tree outside of explicit transaction. So it also means that implicit transaction can be added to the transaction stack only when the stack is empty.</p>
</blockquote>
<h3 id="heading-implicit-transaction">Implicit transaction</h3>
<p>Last example shows us how implicit transaction works.</p>
<pre><code class="lang-swift">button.backgroundColor = .red
...
<span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {
    button.backgroundColor = .white <span class="hljs-comment">// implicit transaction begins here</span>
    <span class="hljs-type">CATransaction</span>.begin()
    button.backgroundColor = .yellow
    <span class="hljs-type">CATransaction</span>.commit()
    sleep(<span class="hljs-number">3</span>) <span class="hljs-comment">// blocks the main thread for 3 seconds</span>
    button.backgroundColor = .green
}
</code></pre>
<p>Looks very similar to the example with explicit transaction when yellow state was visible. Except one detail - we changed the model layer tree by assigning <code>button.backgroundColor = .white</code> first which triggered implicit transaction to begin so our explicit transaction was nested into implicit one and was not sent to the render tree until the implicit one was committed later. So that’s why we saw only green state after 3 seconds in this case.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315845044/fa5d8335-d2d1-4f81-ac7f-560adf30cd82.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE 1</strong>: How many transactions created per runloop cycle?<br />0 - if nothing changed;<br />1 - if layer tree changed outside of explicit transaction <strong>OR</strong> 1 explicit transaction was created;<br />N+1 - if layer tree changed outside of explicit transaction <strong>AND</strong> N explicit transactions were created.</p>
<p><strong>NOTE 2</strong>: You could notice that transaction has also a <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/catransaction/1448270-flush">flush</a> method. It commits any extant implicit transaction. <strong>Flush is typically called automatically at the end of the current runloop iteration</strong>, regardless of the runloop mode. You should attempt to avoid calling <code>flush</code> explicitly. By allowing <code>flush</code> to execute during the runloop your application will achieve better performance, atomic screen updates will be preserved, and transactions and animations that work from transaction to transaction will continue to function.</p>
</blockquote>
<p>Next thing we need to understand is the <strong>phases of transaction</strong>.</p>
<h3 id="heading-phases-of-transaction">Phases of transaction</h3>
<p>The CATransaction commit method is separated in four phases:</p>
<ol>
<li><p>Layout phase</p>
</li>
<li><p>Display phase</p>
</li>
<li><p>Prepare phase</p>
</li>
<li><p>Commit phase</p>
</li>
</ol>
<p>All of them are actually happening inside <code>CA::Transaction::commit()</code> method. We can see this method inside the callstack if we put a breakpoint inside <code>layoutSubviews()</code> method of your view.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708315993720/d229306b-1292-4a40-a7b2-24ed9bf729c0.png" alt class="image--center mx-auto" /></p>
<p>Lets go quickly over every phase.</p>
<h3 id="heading-layout-and-display-phases">Layout and display phases</h3>
<p>In the screenshot above, you can observe the method <code>CA::Layer::layout_and_display_if_needed(CA::Transaction*)</code>, responsible for executing two phases: <strong>Layout</strong> and <strong>Display</strong>. It's intuitive that the layout phase handles the layout update of the layer tree before the transaction is committed. Similarly, the display phase is responsible for updating the displayed contents within the layers. Core Animation initiates these phases before committing the transaction to ensure the layer tree's state is up-to-date and can be applied accurately to the render tree.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316025621/c5ba4a7c-7e5e-46e0-9d1f-407f7088beb3.png" alt class="image--center mx-auto" /></p>
<p>Both phases function in a similar manner. Core Animation traverses the layer tree using Depth-First Search (DFS), starting from the top superview (UIWindow) down to the bottom leaf layers. If the layer's corresponding flag (returned by <code>needsLayout()</code> / <code>needsDisplay()</code>) is activated, the <code>layoutSublayers()</code> / <code>display()</code> methods are called accordingly. These flags can be activated implicitly by modifying the layer's properties (such as bounds, for instance) or explicitly by invoking <code>setNeedsLayout()</code> / <code>setNeedsDisplay()</code>.</p>
<blockquote>
<p><strong>NOTE</strong>: You don’t ever need to call <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410935-layoutsublayers">layoutSublayers</a> / <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410926-display">display</a> directly.</p>
</blockquote>
<h3 id="heading-layout-phase">Layout phase</h3>
<p>The layout methods in UIView, frequently used in UIKit - such as <code>layoutIfNeeded</code>, <code>setNeedsLayout</code>, and <code>layoutSubviews</code> essentially serve as proxies for the corresponding methods in CALayer. While UIKit incorporates additional logic to ensure the proper functioning of autolayout constraints, it consistently depends on CoreAnimation's fundamental layout process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316181603/4957f209-86bb-4241-bbf9-68001b463f20.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316188808/c37a635f-94cd-4e35-b444-a2c51c461f9d.png" alt class="image--center mx-auto" /></p>
<p>During the layout phase, Core Animation calls the <code>layoutSublayers()</code> method of CALayer from top to bottom on each layer within the current layer tree if they were invalidated by implicit or explicit changes to the <code>needsLayout</code> flag. Ultimately, at the completion of this chain execution, each layer acquires its final position and size.</p>
<p>If the entire layer tree is invalidated using <code>setNeedsLayout()</code>, during the traversal in the layout phase, <code>layoutSublayers()</code> will be invoked for every layer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316245342/8a213f71-0dc2-4ffe-9771-81cef558f63e.gif" alt class="image--center mx-auto" /></p>
<p>Most of the time tree is invalidated only partially. In that case layer tree will be fully traversed too but <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410935-layoutsublayers">layoutSublayers(</a>) will be called only for invalidated layers (with <code>needsLayout</code> flag enabled).</p>
<p>As we mentioned above UIView sets itself as a CALayerDelegate of it’s backing layer.</p>
<p>Default implementation of <code>layoutSublayers</code> calls the <code>layoutSublayers(of:)</code> method of the layer’s delegate object if it is implemented.</p>
<p>UIView implements <code>layoutSublayers(of:)</code> method and calls UIView’s <code>layoutSubviews()</code> method, that you probably already familiar with and often use to customise layout of views.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span>: <span class="hljs-title">CALayerDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">layoutSublayers</span><span class="hljs-params">(of layer: CALayer)</span></span> {
        layoutSubviews()
    }
}
</code></pre>
<p><code>layoutSubviews()</code> is meant to help UIKit to support AutoLayout and it adds one addition step to layout phase - <strong>Constraints pass</strong>. This is the step UIKit does to solve constraints. It goes over the view tree bottom-up from leaf subviews to the root view and calls <code>updateConstraints()</code> method of the view if <code>needsUpdateConstraints()</code> returns true. It usually becomes true automatically when constraints where changed or if you call <code>setNeedsUpdateConstraints()</code> explicitly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316378893/160a97fd-934a-47ae-96b8-96243bf901bd.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>NOTE 1</strong>: <code>layoutSubviews()</code> do the constraints pass <strong>if needed</strong> every time it is called.</p>
<p><strong>NOTE 2</strong>: One important difference of <strong>Constraints pass</strong> from layoutSublayers traversal logic is that it’s traversal is happening in opposite direction - bottom-up, from leafs to superview.</p>
</blockquote>
<h3 id="heading-display-phase">Display phase</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316416136/5d528d7a-c0f9-4a71-b6a6-0189afbe4b4c.png" alt class="image--center mx-auto" /></p>
<p>During the display phase, Core Animation invokes the <code>display()</code> method of each layer within the current layer tree, proceeding from top to bottom (from super to sublayers). This method is responsible for updating the layer's content. By the end of this phase, every layer has its content refreshed. Subclasses have the option to override the <code>display()</code> method, utilizing it to directly set the layer's <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410773-contents">contents</a> property.</p>
<p>The mechanism involving <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410855-setneedsdisplay">setNeedsDisplay</a> and <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410958-needsdisplay">needsDisplay</a> is the same as for the layout phase.</p>
<p>Let’s dive deeper into layer’s display logic:</p>
<ul>
<li><p>Default implementation of layer’s <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410926-display">display</a> calls the <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayerdelegate/2097261-display">display(_ layer:</a>) method of the layer’s delegate object if it is implemented.</p>
</li>
<li><p>Otherwise this method creates a backing store (pixel bitmap, like an image) and calls CALayer's <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayer/1410757-draw">draw(in ctx: CGContext)</a>.</p>
</li>
<li><p><code>draw(in ctx: CGContext)</code> by default does nothing except calling CALayerDelegate's <a target="_blank" href="https://developer.apple.com/documentation/quartzcore/calayerdelegate/2097262-draw"><code>draw(layer: CALayer, in ctx: CGContext)</code></a> if it is implemented to fill the backing store with content using Core Graphics for drawing via CGContext.</p>
</li>
<li><p>UIView as a CALayerDelegate of it’s backing layer doesn’t implement <code>display(_ layer:)</code>. But it implements <code>draw(layer, in ctx: CGContext)</code> where it calls UIView’s <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiview/1622529-draw"><code>draw(rect:)</code></a> method.</p>
</li>
<li><p>Default implementation if UIView’s <code>draw(rect:)</code> does nothing. But can be overridden by subclasses to implement custom drawing with Core Graphics.</p>
</li>
</ul>
<p>Understanding it all at once can be challenging. But the most important aspect here is that, in essence, UIKit empowers us to perform <strong>custom drawing</strong> using Core Graphics by overriding the <code>draw(rect:)</code> method. Let's build a diagram to create a clearer understanding:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316782485/8cd72d67-5f51-4d86-82ad-6d20ba1eb986.png" alt class="image--center mx-auto" /></p>
<p>Even if you have never known about <strong>display phase</strong> you probably used <code>draw(rect:)</code> for drawing custom graphics in custom components in your apps. You can draw lines and shapes in the Core Graphics context. For example you can draw 100 ellipses to see a torus like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">draw</span><span class="hljs-params">(<span class="hljs-number">_</span> rect: CGRect)</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> context = <span class="hljs-type">UIGraphicsGetCurrentContext</span>() <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

    context.setLineWidth(<span class="hljs-number">1.0</span>)
    context.setStrokeColor(<span class="hljs-type">UIColor</span>.orange.cgColor)

    context.translateBy(x: frame.size.width / <span class="hljs-number">2.0</span>,
                        y: frame.size.height / <span class="hljs-number">2.0</span>)

    <span class="hljs-keyword">let</span> numberOfEllipses = <span class="hljs-number">100</span>
    <span class="hljs-keyword">let</span> amount = <span class="hljs-type">Double</span>.pi * <span class="hljs-number">2</span> / <span class="hljs-type">Double</span>(numberOfEllipses)

    <span class="hljs-keyword">for</span> <span class="hljs-number">_</span> <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>...numberOfEllipses {
        context.rotate(by: <span class="hljs-type">CGFloat</span>(amount))
        <span class="hljs-keyword">let</span> rect = <span class="hljs-type">CGRect</span>(x: -(frame.size.width * <span class="hljs-number">0.25</span>),
                          y: -(frame.size.height / <span class="hljs-number">2.0</span>),
                          width: frame.size.width * <span class="hljs-number">0.5</span>,
                          height: frame.size.height)
        context.addEllipse(<span class="hljs-keyword">in</span>: rect)
    }
    context.strokePath()
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316847158/9cdb9295-c4e2-44e3-88f0-56b8f68f5d42.png" alt class="image--center mx-auto" /></p>
<p>But custom graphics drawing is not the only case. For example, <code>UILabel</code> use <strong>display phase</strong> and <code>draw(rect:)</code> to draw text (calls <code>drawText(in rect: CGRect)</code> there). You can override <code>draw(*rect:)</code> method for label and you see no text drawn.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomLabel</span>: <span class="hljs-title">UILabel</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">draw</span><span class="hljs-params">(<span class="hljs-number">_</span> rect: CGRect)</span></span> {
        <span class="hljs-comment">// super.draw(rect)</span>
    }
}
</code></pre>
<blockquote>
<p><strong>NOTE</strong>: Keep in mind that Core Graphics drawing is performed by the CPU. CoreGraphics draws in CALayer backing store - it is a bitmap data, that later will be pushed to the GPU.</p>
</blockquote>
<p>Now we know that committing transaction triggers <strong>Layout and Display phases</strong> to happen. Layout an display are phases where updates to UI usually happen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316955017/e6a6edb2-7e7c-49f0-bdc9-1108223fafe3.png" alt class="image--center mx-auto" /></p>
<p>CATransactions actually has 2 more phases. Usually developers don’t interact with them but they are important because of what they do.</p>
<p><strong>Prepare phase</strong> - does additional Core Animation work like image decoding (JPGs, PNGs) and image conversion (if in format not supported by GPU, e.g. index bitmap).</p>
<p><strong>Commit phase</strong> - packages up layer tree and sends the transaction to the <code>Render server</code> to be applied to the render tree.</p>
<p>You probably noticed a new keyword here - <code>Render server</code>. Until this moment we didn’t discuss where exactly render tree is located. Surprisingly, not inside the application. Render tree is managed by separate process called <strong>Render server</strong> that issues drawing calls for the GPU using Metal. This process performs most of the magic of the Core Animation framework.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708316998009/0358765f-f294-4b83-9e2b-8c724dc2b541.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-render-server">Render server</h1>
<p>After transaction is committed it will be sent over IPC (Inter-Process Communication) to the <strong>Render server</strong> process.</p>
<blockquote>
<p><strong>NOTE</strong>: <strong>Render server</strong> for simplicity can be considered as Core Animation back end.</p>
</blockquote>
<p>Maybe you were wondering when you debug your application why progress indicator is continuing to spin even though your application is paused. Now you can guess why - it is spinning in another process - <strong>Render Server</strong>. It will happen with any other animation as well. If you commit the transaction with animation added before you block your main thread, you will see how it continues to work.</p>
<p>Once render server receives the transaction, it decodes it to apply updates to render tree.</p>
<p>For every display update iteration render server need to provide next frame for the GPU so it does the following:</p>
<ul>
<li><p>Calculates the intermediate values for the render tree’s layers properties if related animation is in progress.</p>
</li>
<li><p>Uses the Metal commands to perform the layer tree rendering.</p>
<ul>
<li><strong>NOTE</strong>: How Metal works is a great topic for the next post, but for now to explain it simply Metal has a commands queue and it puts commands there to be rendered by GPU.</li>
</ul>
</li>
<li><p>The output of rendering on GPU is a pixel bitmap to be shown on the screen.</p>
</li>
</ul>
<p>So we can update our scheme of questions into the final state:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317117613/ad438e20-647b-4e5a-8af7-c550fc2b30cb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-recap">Recap</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317162619/539e274c-0917-470f-aa0e-451a5dc9d156.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>UIApplicationMain</strong>:</p>
<ul>
<li><p><code>main()</code> is the app “entry point” called by the operating system's runtime environment during app launch. main function itself doesn’t do much. It’s main purpose is to call <code>UIApplicationMain()</code> function.</p>
</li>
<li><p><code>UIApplicationMain()</code> manages app setup and runs the main event loop - <strong>Runloop</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Runloop</strong>:</p>
<ul>
<li><p>Infinite cycle managing app events.</p>
</li>
<li><p>Manages events handling - <strong>runloop Events</strong>:</p>
<ul>
<li><p>GCD main queue blocks</p>
</li>
<li><p>timers</p>
</li>
<li><p>sources (version 0 and 1)</p>
</li>
<li><p>observers.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>CoreAnimation</strong>:</p>
<ul>
<li><p>UIKit internally delegates UI-related work to Core Animation.</p>
</li>
<li><p>UIViews are backed by CALayers, handling rendering, layout, and animations.</p>
</li>
<li><p>CoreAnimation holds in parallel three layer trees: model tree, presentation tree, and render tree.</p>
</li>
<li><p><code>CoreAnimation</code> renders our app view hierarchy at each run loop pass and not at each view hierarchy modification. To do so, it observes <code>CFRunLoopActivity.kCFRunLoopBeforeWaiting</code> event and begins an implicit <code>CATransaction</code> if layer tree was changed. In the end of every runloop iteration, <code>CoreAnimation</code> finally commits this transaction just before the thread is put to sleep.</p>
</li>
</ul>
</li>
<li><p><strong>CATransaction</strong>:</p>
<ul>
<li><p><code>CATransaction</code> is Core Animation's tool for combining multiple layer-tree operations into atomic updates to the render tree.</p>
</li>
<li><p><strong>Explicit Transactions</strong> can be created manually with <code>begin</code> and <code>commit</code> methods.</p>
</li>
<li><p>Transactions can be <strong>nested</strong>, but when nested within an ongoing transaction, changes within the nested one only commit when the parent transaction does.</p>
</li>
<li><p>Transactions consist of four phases: <strong>Layout</strong>, <strong>Display</strong>, <strong>Prepare</strong>, and <strong>Commit</strong>.</p>
</li>
<li><p>At the end of every runloop iteration CoreAnimation commits implicit CATransaction through IPC (Inter Process Communication) to another process - <strong>Render Server.</strong></p>
</li>
</ul>
</li>
<li><p><strong>Render Server</strong>:</p>
<ul>
<li><p>The Render server manages the <strong>render tree</strong> and is responsible for animations and rendering.</p>
</li>
<li><p>The Render server uses Metal commands to perform the render tree rendering. The output of rendering on GPU is a pixel bitmap to be shown on the screen.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317267937/d2ba221c-bebf-4b10-8146-ac1a867f2a68.png" alt class="image--center mx-auto" /></p>
<p><strong>BONUS</strong>: if you like to have the full picture, here is a complete scheme:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708317317018/fcf95ad4-e58e-42ed-9621-ba2d64459384.png" alt class="image--center mx-auto" /></p>
<p><strong>ONE MORE BONUS</strong>: Best book about UIKit is… actually the book about Core Animation 🤣</p>
<ul>
<li><a target="_blank" href="https://www.oreilly.com/library/view/ios-core-animation/9780133440744/"><strong>iOS Core Animation: Advanced Techniques</strong></a> by Nick Lockwood</li>
</ul>
<p>It was published back in 2013 but it is still relevant if you want to master UIKit.</p>
<p>Thank you for reading and see you in the next post about SwiftUI.</p>
]]></content:encoded></item><item><title><![CDATA[DI in SwiftUI]]></title><description><![CDATA[In the previous posts about Dependency Injection, we learned about the importance of DI in building scalable, maintainable, and testable iOS applications. We also learned that the Factory library can be a modern solution for DI.
NOTE: In this post, I...]]></description><link>https://vbat.dev/di-in-swiftui</link><guid isPermaLink="true">https://vbat.dev/di-in-swiftui</guid><category><![CDATA[dependency injection]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sat, 01 Jul 2023 03:00:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688179940917/e5f5e862-80ad-48a1-ae9a-2afb6e675e95.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous posts about Dependency Injection, we learned about the importance of DI in building scalable, maintainable, and testable iOS applications. We also <a target="_blank" href="https://vbat.dev/factory-for-di">learned</a> that the <a target="_blank" href="https://github.com/hmlongco/Factory">Factory library</a> can be a modern solution for DI.</p>
<p><strong>NOTE</strong>: In this post, I assume you are already familiar with the concept of DI (but you can learn more about it from this <a target="_blank" href="https://medium.com/its-tinkoff/di-in-ios-complete-guide-cd76a079d2d">post</a>).</p>
<p>In SwiftUI, Apple introduces a slightly different approach to DI compared to traditional UIKit development. In this article, we will explore the fundamentals of how DI works in the context of SwiftUI.</p>
<h1 id="heading-swiftui-two-chairs-for-di">SwiftUI - two chairs for DI</h1>
<p>We will cover two different approaches for DI we have in SwiftUI:</p>
<ul>
<li><p>Constructor injection</p>
</li>
<li><p>Environment injection</p>
</li>
</ul>
<p>Let’s consider both options more carefully.</p>
<h1 id="heading-constructor-injection">Constructor injection</h1>
<p>This approach should already be familiar to you from previous posts. It is a reliable and widely-used method for implementing DI, which you likely have utilized extensively in iOS development prior to the introduction of SwiftUI.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginViewModel</span> </span>{
    <span class="hljs-keyword">let</span> authService: <span class="hljs-type">AuthenticationService</span>

    <span class="hljs-keyword">init</span>(authService: <span class="hljs-type">AuthenticationService</span>) {
        <span class="hljs-keyword">self</span>.authService = authService
    }
}

<span class="hljs-comment">// Somewhere in your code later</span>
<span class="hljs-type">LoginViewModel</span>(authService: authservice)
</code></pre>
<p>This is a classic example of <strong>constructor injection</strong>, where the dependency is injected through the constructor (also known as the initializer).</p>
<p>Even though we may feel confident with this pattern, when we begin using views in SwiftUI, we realize that it becomes slightly more complex due to the various property wrappers available. However, the good news is that the main principle remains the same for views.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">LoginView</span>: <span class="hljs-title">View</span> </span>{

    @<span class="hljs-type">ObservedObject</span> <span class="hljs-keyword">var</span> viewModel: <span class="hljs-type">LoginViewModel</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        &lt;...&gt;
    }
}

<span class="hljs-comment">// Somewhere in your code later</span>
<span class="hljs-type">LoginView</span>(viewModel: vm)
</code></pre>
<p>Even though we didn't explicitly create the initializer to pass the viewModel into LoginView, Swift automatically generates the default initializer for view structs. However, you can still write your own initializer if needed. The same approach can be applied to any view dependency, regardless of its type or the property wrapper used.</p>
<h1 id="heading-environment-injection">Environment Injection</h1>
<p>Environment injection allows us to inject dependencies into a view within the view hierarchy and access them from any lower-level view if necessary.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688180121717/641f94fd-33fe-4393-902d-3e7c27df8f06.png" alt class="image--center mx-auto" /></p>
<p>In fact, there are two property wrappers available for this purpose: <code>@EnvironmentObject</code> and <code>@Environment</code>.</p>
<ul>
<li><p><code>@EnvironmentObject</code> enables the injection of an object of a specific type into the environment.</p>
</li>
<li><p><code>@Environment</code> allows the injection of dependencies using a key/value approach.</p>
</li>
</ul>
<p>If this is still unclear, don't worry. We will provide examples to further explain these concepts.</p>
<h2 id="heading-environmentobject">EnvironmentObject</h2>
<p>The <code>@EnvironmentObject</code> property wrapper allows you to inject an object instance, identified by its type, into the environment.</p>
<p>To inject a dependency using environment objects, follow these steps:</p>
<ol>
<li><p>Create an instance of your dependency that conforms to the <code>ObservableObject</code> protocol (meaning it should be a class).</p>
</li>
<li><p>Inject the dependency into the environment by using the <code>.environmentObject()</code> modifier on the root view or an intermediate container view.</p>
</li>
<li><p>Access the injected dependency in any child view that requires it by using the <code>@EnvironmentObject</code> property wrapper.</p>
</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyDependency</span>: <span class="hljs-title">ObservableObject</span> </span>{
    &lt;...&gt;
}

@main
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MyApp</span>: <span class="hljs-title">App</span> </span>{
    <span class="hljs-keyword">let</span> dependency = <span class="hljs-type">MyDependency</span>() <span class="hljs-comment">// 1: Create an instance</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">Scene</span> {
        <span class="hljs-type">WindowGroup</span> {
            <span class="hljs-type">ContentView</span>()
                .environmentObject(dependency) <span class="hljs-comment">// 2: Inject the instance</span>
        }
    }
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">SubView</span>()
    }
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SubView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">EnvironmentObject</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> myDependency: <span class="hljs-type">MyDependency</span> <span class="hljs-comment">// injected</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        &lt;...&gt;
        <span class="hljs-comment">// 3: Access the injected dependency in your view</span>
    }
}
</code></pre>
<p>The <code>@EnvironmentObject</code> property wrapper proves to be extremely useful when dealing with complex views where you need to inject dependencies into the lowest view within the hierarchy. Unlike the constructor injection approach, which requires passing the dependency through multiple views in the chain, <code>@EnvironmentObject</code> simplifies the process significantly.</p>
<h3 id="heading-important-notes"><strong>Important notes</strong></h3>
<p><strong>NOTE 1</strong>: If you need to inject a value type, <code>@EnvironmentObject</code> is not suitable. It only works with <code>ObservableObject</code>. However, you can use <code>@Environment</code> for injecting value types.</p>
<p><strong>NOTE 2</strong>: It's crucial to be cautious when using environment objects as their usage can be unsafe. If an object is not available in the environment because you forget to inject it, the <strong>app will crash</strong>. This scenario is similar to using reflection-based libraries like <strong>Swinject</strong>, where forgetting to register a dependency before resolving it leads to a crash in runtime. Therefore, make sure you use <code>@EnvironmentObject</code> carefully.</p>
<p><strong>NOTE 3</strong>: The environment can hold only one type of <code>environmentObject</code> instance at a time. If you inject an object of the same type lower in the hierarchy, it will replace the old one.</p>
<h2 id="heading-environment">Environment</h2>
<p>The <code>@Environment</code> property wrapper allows you to inject key/value pairs into the environment.</p>
<p>Here's a summary of how you can use it:</p>
<ol>
<li><p>Create an <code>EnvironmentKey</code> with a default value.</p>
</li>
<li><p>Add the value property to an extension of <code>EnvironmentValues</code>.</p>
</li>
<li><p>Inject the dependency into the environment by using the <code>.environment(\.{key}, value)</code> modifier on the root view or an intermediate container view.</p>
</li>
<li><p>Access the injected dependency in any child view that requires it by using the <code>@Environment</code> property wrapper with the corresponding <strong>key</strong>.</p>
</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ValueKey</span>: <span class="hljs-title">EnvironmentKey</span> </span>{ <span class="hljs-comment">// 1: Created EnvironmentKey</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> defaultValue = <span class="hljs-number">1</span>
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">EnvironmentValues</span> </span>{ <span class="hljs-comment">// 2: Add EnvironmentValues property</span>
    <span class="hljs-keyword">var</span> value: <span class="hljs-type">Int</span> {
        <span class="hljs-keyword">get</span> { <span class="hljs-keyword">self</span>[<span class="hljs-type">ValueKey</span>.<span class="hljs-keyword">self</span>] }
        <span class="hljs-keyword">set</span> { <span class="hljs-keyword">self</span>[<span class="hljs-type">ValueKey</span>.<span class="hljs-keyword">self</span>] = newValue }
    }
}

@main
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">MyApp</span>: <span class="hljs-title">App</span> </span>{
    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">Scene</span> {
        <span class="hljs-type">WindowGroup</span> {
            <span class="hljs-type">ContentView</span>()
                .environment(\.value, <span class="hljs-number">123</span>) <span class="hljs-comment">// 3: Inject the dependency</span>
        }
    }
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">SubView</span>()
    }
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SubView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">Environment</span>(\.value) <span class="hljs-keyword">var</span> value <span class="hljs-comment">// injected</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        &lt;...&gt;
        <span class="hljs-comment">// 4: Access the injected dependency in your view</span>
    }
}
</code></pre>
<h3 id="heading-important-notes-1">Important notes</h3>
<p><strong>NOTE 1</strong>: The <code>@Environment</code> property wrapper is particularly useful for accessing existing system-related values such as the app's appearance mode (light or dark), Core Data's managed object context, size classes, and more. Refer to the documentation for <a target="_blank" href="https://developer.apple.com/documentation/swiftui/environmentvalues"><strong>EnvironmentValues</strong></a> to see the full list of available properties. In this article, we focus more on injecting our own dependencies.</p>
<p><strong>NOTE 2</strong>: With the <code>@Environment</code> property wrapper, you can add both <strong>value</strong> and <strong>reference</strong> types to the environment. This makes it more versatile compared to <code>@EnvironmentObject</code>.</p>
<p><strong>NOTE 3</strong>: An <code>@Environment</code> dependency always has a default value, so if you forget to inject it at the root level, your app will not crash. This makes it more safe compared to <code>@EnvironmentObject</code>.</p>
<p><strong>NOTE 4</strong>: <code>@Environment</code> injection can hold multiple instances of the same type but with different keys.</p>
<h1 id="heading-recap">Recap</h1>
<ul>
<li><p>Two approaches for dependency injection in SwiftUI were discussed: <strong>Constructor injection</strong> and <strong>Environment injection</strong>.</p>
</li>
<li><p><strong>Constructor injection</strong> involves injecting dependencies via the initializer, following a similar pattern as in traditional UIKit development.</p>
</li>
<li><p><strong>Environment injection</strong> allows for injecting dependencies into the view hierarchy and accessing them from any view lower in the hierarchy.</p>
</li>
<li><p>The <code>@EnvironmentObject</code> property wrapper is used to inject an object of a specific type into the environment.</p>
</li>
<li><p>The <code>@Environment</code> property wrapper is used to inject dependencies using key/value pairs.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Dependency Injection is a simple concept that can greatly benefit SwiftUI code, just like any other codebase. While there may be other approaches to DI in SwiftUI, mastering the two options described above is sufficient for effective DI implementation.</p>
]]></content:encoded></item><item><title><![CDATA[Factory for DI]]></title><description><![CDATA[In my previous post about Dependency Injection in iOS, I mentioned that factories can be used for DI and it's straightforward to implement. Today, we will figure out how to do it, what options are available, and why it may be more advantageous than u...]]></description><link>https://vbat.dev/factory-for-di</link><guid isPermaLink="true">https://vbat.dev/factory-for-di</guid><category><![CDATA[dependency injection]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Factory Design Pattern]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 30 Apr 2023 22:35:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682893367958/640c685f-e42f-44b2-a875-7364ba78e30c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my <a target="_blank" href="https://medium.com/its-tinkoff/di-in-ios-complete-guide-cd76a079d2d">previous post about Dependency Injection in iOS</a>, I mentioned that factories can be used for DI and it's straightforward to implement. Today, we will figure out how to do it, what options are available, and why it may be more advantageous than using libraries like Swinject.</p>
<p><strong>NOTE</strong>: In this post, I assume you are already familiar with the concept of DI (but you can learn more about it from my previous post referenced above).</p>
<h1 id="heading-factory-pattern">Factory pattern</h1>
<p>The <strong>Factory</strong> pattern is a design pattern commonly used in software development for object creation. It encapsulates the logic for creating objects within a separate class or method, based on specific parameters or conditions.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">ViewFactory</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">createView</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIView</span> {
        <span class="hljs-type">UIView</span>()
    }
}
<span class="hljs-keyword">let</span> view = <span class="hljs-type">ViewFactory</span>.createView()
</code></pre>
<p>It allows to extract the object creation logic from the calling point and abides by <strong>Single Responsibility Principle</strong>. This helps to keep the object creation logic organized and centralized, making it easier to maintain, update, and test.</p>
<p>The Factory pattern promotes <strong>loose coupling</strong> between the client code and the objects being created. Clients only depend on the factory, not on concrete classes, which allows for easier maintenance and reduces the dependencies between different parts of the codebase.</p>
<h1 id="heading-factory-pattern-for-di">Factory pattern for DI</h1>
<p>Factories can be very useful for implementing DI in your application. For example, you can create a login screen with the factory and inject all required dependencies there:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">AuthService</span> </span>{}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">AuthServiceFactory</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">create</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">AuthService</span> {
        <span class="hljs-type">AuthServiceImpl</span>()
    }
}
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">LoginScreenFactory</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">createLoginScreen</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIViewController</span> {
        <span class="hljs-keyword">let</span> authService = <span class="hljs-type">AuthServiceFactory</span>.create()
        <span class="hljs-keyword">let</span> loginVM = <span class="hljs-type">LoginViewModel</span>(authService: authService)
        <span class="hljs-keyword">return</span> <span class="hljs-type">LoginViewController</span>(viewModel: viewModel)
    }
}
<span class="hljs-keyword">let</span> loginVC = <span class="hljs-type">LoginScreenFactory</span>.create()
<span class="hljs-comment">// presenting your screen</span>
</code></pre>
<p>As you can see, the factory creates all dependencies for this MVVM module and inject them so we have the view controller ready to present as a result.</p>
<p>You can use factories to create screens, services or anything you need in your application.</p>
<p>In smaller iOS applications, using factories can be a viable solution for DI. Factories can provide a <strong>simple</strong> and <strong>lightweight</strong> way to handle DI <strong>without the need for external third-party</strong> libraries or frameworks.</p>
<h1 id="heading-when-its-not-enough">When it’s not enough?</h1>
<p>While factories can be a suitable approach for managing dependencies in smaller iOS applications, they may not be sufficient for larger, more complex projects.</p>
<p>In large iOS projects with numerous dependencies and complex dependency graphs, managing dependencies through factories can become more challenging. You need to <strong>control the lifecycle</strong> of your dependencies to manage them properly and it’s not what we have out of the box with the <strong>factory pattern</strong>. On top of that, it would be useful to <strong>detect dependency cycles</strong> and <strong>mock dependencies easily for unit testing</strong>. Although it is possible to implement these features, it can be tricky, especially for those not familiar with how DI containers work under the hood.</p>
<p>Let’s summarize what we expect from a proper DI solution and what factories actually capable of:</p>
<ul>
<li><p><strong>Compile-time safety</strong></p>
<p>  When you do DI we expect it should be compile-time safe. This means that the code will not compile if there are any errors related to dependency injection, such as missing or mismatched dependencies, or if dependencies are not registered with the container. We want to avoid runtime crashes, and this can be accomplished by avoiding runtime resolvers like Swinject. As the application grows larger, it becomes increasingly difficult to ensure that all modules use only registered dependencies.</p>
</li>
<li><p><strong>Laziness</strong></p>
<p>  We want our dependencies to be lazy. It means that objects are created and initialized when they are actually needed, rather than creating and initializing all objects at once on application start. This can help improve the performance of an application, as it reduces the amount of memory and processing power required at startup. This is especially important in larger applications, where there may be a large number of objects and dependencies to manage.</p>
</li>
<li><p><strong>Control the scope/lifecycle (+ easily reset the scope when needed)</strong></p>
<p>  DI container should be responsible for creating and initializing objects, managing their state during runtime, and destroying them when they are no longer needed. It can help reduce memory usage by avoiding to let unused dependencies exist in memory and releasing other system resources when they are no longer needed.</p>
</li>
<li><p><strong>Easily mock/replace implementations for testing</strong></p>
<p>  We expect that we will be able to easily substitute the real dependencies of an object with test-specific dependencies, known as <strong>mocks</strong> or <strong>test double</strong>s. This can help isolate the behavior of an object or function during testing and ensure that it is functioning correctly.</p>
</li>
<li><p><strong>Detect dependency cycles</strong></p>
<p>  We expect that it can identify situations where two or more objects have circular dependencies, where each object depends on another object in the cycle. This can be a problem because it can lead to infinite loops or other errors that can cause the application to crash. The larger an application grows, the more crucial it becomes.</p>
</li>
</ul>
<p>Using the <strong>Factory pattern</strong> gives us only 2 out of 5 required features:</p>
<ul>
<li><p>✅ Compile-time safety</p>
</li>
<li><p>✅ Laziness</p>
</li>
<li><p>❌ Control the scope/lifecycle</p>
</li>
<li><p>❌ Easily mock/replace implementations for testing</p>
</li>
<li><p>❌ Detect dependency cycles</p>
</li>
</ul>
<p>How can we achieve all of them?</p>
<p>One option is to implement a custom solution, but a more straightforward approach is to use a ready-to-go solution. What if I tell you that the solution is a <strong>Factory</strong> again, but not the pattern but the <strong>library</strong>? - <a target="_blank" href="https://github.com/hmlongco/Factory">Factory library</a>. Created in 2022, this library takes the factory pattern to the next level which I would call <strong>Factory pattern on steroids.</strong></p>
<h1 id="heading-how-to-use-factory-library">How to use Factory library</h1>
<p>A simple example of using Factory:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Container</span> </span>{
    <span class="hljs-keyword">var</span> service1: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">Service1</span>&gt; { 
        <span class="hljs-keyword">self</span> { <span class="hljs-type">ServiceImpl1</span>() }
            .singleton
    }
    <span class="hljs-keyword">var</span> service2: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">Service2</span>&gt; { 
        <span class="hljs-keyword">self</span> { <span class="hljs-type">ServiceImpl2</span>(service1: <span class="hljs-keyword">self</span>.service1()) }
    }
}
<span class="hljs-comment">// you can use it like that later for DI </span>
<span class="hljs-keyword">let</span> vm = <span class="hljs-type">ScreenViewModel</span>(service1: <span class="hljs-type">Container</span>.shared.service1(),
                         service2: <span class="hljs-type">Container</span>.shared.service2())
</code></pre>
<p>Looks pretty and simple, right? You declare your dependencies as properties of the Container. You wrap your actual dependency into Factory type and simply address this property with <code>()</code> later to resolve the instance. It works due to the <strong>callAsFunction</strong> feature introduced in Swift 5.2.</p>
<h1 id="heading-features">Features</h1>
<p>Let’s check our requirements list again for the Factory library:</p>
<ul>
<li><p>✅ Compile-time safety</p>
<ul>
<li>If you declare a Factory property, you can be certain that you will be able to resolve the instance.</li>
</ul>
</li>
<li><p>✅ Laziness</p>
<ul>
<li>As you can see, we used calculated properties there, that gives as laziness.</li>
</ul>
</li>
<li><p>✅ Control the lifecycle/scope</p>
<ul>
<li><p>Factory <a target="_blank" href="https://hmlongco.github.io/Factory/documentation/factory/scopes/">provides</a> us with 5 scopes: <strong>Singleton, Cached, Shared, Graph, Unique.</strong></p>
</li>
<li><p>You can create your own custom scopes.</p>
</li>
<li><p>You can reset individual scope caches.</p>
</li>
</ul>
</li>
<li><p>✅ Using mocks for testing is so easy</p>
<ul>
<li>Just register the mock to update the original instance</li>
</ul>
</li>
</ul>
<pre><code class="lang-swift">    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView_Previews</span>: <span class="hljs-title">PreviewProvider</span> </span>{
        <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> previews: some <span class="hljs-type">View</span> {
            <span class="hljs-keyword">let</span> <span class="hljs-number">_</span> = <span class="hljs-type">Container</span>.myService.register { <span class="hljs-type">MockService2</span>() }
            <span class="hljs-type">ContentView</span>()
        }
    }
</code></pre>
<ul>
<li><p>✅ Detect dependency cycles</p>
<ul>
<li>Factory provides the ability to catch dependency graph cycles but <strong>only in runtime</strong>. When your dependency will be resolved more than <code>dependencyChainTestMax = 8</code> times during one <a target="_blank" href="https://hmlongco.github.io/Factory/documentation/factory/cycle">resolution cycle</a>, the library will trigger the <strong>fatal error</strong>. Max value can be changed if needed.</li>
</ul>
</li>
</ul>
<p>On top of that, <strong>Factory</strong> is lightweight and easy to understand. It also provides cool <code>InjectedObject</code> property wrapper for <strong>SwiftUI</strong> views if you use it in your project.</p>
<h1 id="heading-what-is-under-the-hood">What is under the hood?</h1>
<p>As you noticed we have a <strong>Factory</strong> wrapper for the closure that creates the dependency instance as a result. It provides us with some syntactic sugar. We simply ask the enclosing container to make a properly bound Factory for us using <code>self.callAsFunction { ... }</code>.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Container</span> </span>{
    <span class="hljs-keyword">var</span> sugared: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">MyServiceType</span>&gt; { 
        <span class="hljs-keyword">self</span> { <span class="hljs-type">MyService</span>() }
    }
    <span class="hljs-keyword">var</span> formal: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">MyServiceType</span>&gt; { 
        <span class="hljs-type">Factory</span>(<span class="hljs-keyword">self</span>) { <span class="hljs-type">MyService</span>() }
    }
    <span class="hljs-comment">// result is the same for both properties</span>
}
</code></pre>
<p>The <strong>Container</strong> is basically a namespace for the factory properties you can create in the extension.</p>
<p><strong>Container</strong> owns the <strong>ContainerManager</strong> instance that manages the registration and scope caching storage mechanisms for a given container.</p>
<p><strong>Factory</strong> is just a wrapper for <strong>FactoryRegistration</strong> instance inside it.</p>
<p><strong>FactoryRegistration</strong> is used internally to manage the <strong>registration</strong> and <strong>resolution</strong> process.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Factory</span>&lt;<span class="hljs-title">T</span>&gt;: <span class="hljs-title">FactoryModifying</span> </span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">init</span>(<span class="hljs-number">_</span> container: <span class="hljs-type">ManagedContainer</span>, 
                key: <span class="hljs-type">String</span> = #function, 
                <span class="hljs-number">_</span> factory: @escaping () -&gt; <span class="hljs-type">T</span>) {
        <span class="hljs-keyword">self</span>.registration = <span class="hljs-type">FactoryRegistration</span>&lt;<span class="hljs-type">Void</span>,<span class="hljs-type">T</span>&gt;(id: <span class="hljs-string">"\(key)&lt;\(T.self)&gt;"</span>,
                                                        container: container, 
                                                        factory: factory)
    }
}
</code></pre>
<p>As you can see in code, <strong>registration</strong> <strong>key</strong> is a concatenation of <strong>function name</strong> (<strong>property name</strong> in this case) and <strong>dependency type</strong>.</p>
<p><strong>FactoryRegistration</strong> uses the corresponding <strong>scope</strong> to resolve the dependency. <strong>Scopes</strong> are basically different types of caches (Each scope has its own level of caching).</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FactoryRegistration</span>&lt;<span class="hljs-title">P</span>,<span class="hljs-title">T</span>&gt; </span>{
    <span class="hljs-keyword">internal</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">resolve</span><span class="hljs-params">(with parameters: P)</span></span> -&gt; <span class="hljs-type">T</span> {
        ...
        <span class="hljs-keyword">return</span> scope?.resolve(
            using: manager.cache, 
            id: id, 
            factory: { factory(parameters) }
        ) ?? factory(parameters)
    }
}
</code></pre>
<p><strong>Scope</strong> stores <strong>factory closures</strong> in dictionaries by <strong>registration</strong> <strong>keys</strong>:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Scope</span> </span>{
    <span class="hljs-keyword">internal</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">resolve</span>&lt;T&gt;<span class="hljs-params">(using cache: Cache, id: String, factory: <span class="hljs-params">()</span></span></span> -&gt; <span class="hljs-type">T</span>) -&gt; <span class="hljs-type">T</span> {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> cached: <span class="hljs-type">T</span> = unboxed(box: cache.value(forKey: id)) {
            <span class="hljs-keyword">return</span> cached
        }
        <span class="hljs-keyword">let</span> instance = factory()
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> box = box(instance) {
            cache.<span class="hljs-keyword">set</span>(value: box, forKey: id)
        }
        <span class="hljs-keyword">return</span> instance
    }
}
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Scope</span> </span>{
    <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cache</span> </span>{
        <span class="hljs-keyword">typealias</span> <span class="hljs-type">CacheMap</span> = [<span class="hljs-type">String</span>: <span class="hljs-type">AnyBox</span>] <span class="hljs-comment">// Dictionary for reflection</span>
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">value</span><span class="hljs-params">(forKey key: String)</span></span> -&gt; <span class="hljs-type">AnyBox?</span> {
            cache[key]
        }
        <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">set</span><span class="hljs-params">(value: AnyBox, forKey key: String)</span></span>  {
            cache[key] = value
        }
    }
}
</code></pre>
<p><strong>Factory</strong> implementation idea is quite similar to <strong>reflection based libraries</strong> like <strong>Swinject</strong>. <strong>Reflection</strong> means mapping a set of <strong>keys</strong> to a set of <strong>objects</strong>. So, for example, Factory and Swinject both use a <strong>Dictionary</strong> to map the <strong>keys</strong> (instance types) to <strong>values</strong> of instances.</p>
<h1 id="heading-factory-vs-swinject">Factory vs. Swinject</h1>
<p><strong>Factory</strong> library is similar to <strong>Swinject</strong> in terms of how it works under the hood. It is a <strong>reflection-based library</strong>.</p>
<p>But why then <strong>Factory</strong> is more preferable than <strong>Swinject</strong>?</p>
<p>The main reason to prefer <strong>Factory</strong> over <strong>Swinject</strong> is <strong>compile-time safety</strong>.</p>
<p><strong>Swinject</strong> fully provides all other features that we expect from DI solution <strong>except</strong> <strong>compile-time safety</strong> and it is crucial. <strong>Swinject</strong> resolves instances <strong>on demand</strong> at <strong>runtime</strong> with <strong>no safety</strong> to avoid crashes.</p>
<p>On the contrary, <strong>Factory</strong> is <strong>compile-time safe</strong>.</p>
<p>There is a big difference in the <strong>approach of</strong> <strong>how they register and resolve</strong> instances.</p>
<p>When <strong>Swinject</strong> resolves the instance there is <strong>no guarantee</strong> that this instance was registered before that.</p>
<p>With <strong>Factory</strong> when you call the property as a function to resolve the instance, property declaration is already a guarantee that the instance was registered. This is why it is compile-time safe.</p>
<h1 id="heading-recap">Recap</h1>
<p>To summarize, using the <strong>Factory pattern</strong> can be a suitable solution for <strong>Dependency Injection</strong> in smaller iOS applications, as it provides a simple and lightweight way to handle DI without external third-party libraries or frameworks. However, for larger and more complex projects, managing dependencies through factories can become challenging, and it may not be enough to fulfill all the requirements of proper DI. While factories can ensure compile-time safety and laziness, they cannot control the scope or lifecycle of dependencies, nor easily mock or replace implementations for testing, nor detect dependency cycles. To address these issues, one can use a ready-to-go solution such as the <strong>Factory</strong> library, which can provide all the required features.</p>
<p><strong>Factory pattern</strong></p>
<ul>
<li><p>✅ Compile-time safety</p>
</li>
<li><p>✅ Laziness</p>
</li>
<li><p>❌ Control the lifecycle (Control the scope/ easily reset scope when needed - scopes which allow for better memory management)</p>
</li>
<li><p>❌ Easily mock/replace implementations for testing</p>
</li>
<li><p>❌ Detect dependency cycles</p>
</li>
</ul>
<p><strong>Swinject</strong></p>
<ul>
<li><p>❌ Compile-time safety</p>
</li>
<li><p>✅ Laziness</p>
</li>
<li><p>✅ Control the lifecycle</p>
</li>
<li><p>✅ Easily mock/replace implementations for testing</p>
</li>
<li><p>✅ Detect dependency cycles (but only at runtime)</p>
</li>
</ul>
<p><strong>Factory library</strong> - nice new approach to DI for Swift and SwiftUI</p>
<ul>
<li><p>✅ Compile-time safety</p>
</li>
<li><p>✅ Laziness</p>
</li>
<li><p>✅ Control the lifecycle</p>
</li>
<li><p>✅ Easily mock/replace implementations for testing</p>
</li>
<li><p>✅ Detect dependency cycles (but only at runtime)</p>
</li>
</ul>
<p>Thank you for reading this post, hope it was useful.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Swift Closures]]></title><description><![CDATA[Intro
The Swift documentation provides a comprehensive introduction to Closures. However, in this session, we will cover some interesting aspects of Swift closures that are not fully covered in the documentation.
DEFINITION: Closures are self-contain...]]></description><link>https://vbat.dev/understanding-swift-closures</link><guid isPermaLink="true">https://vbat.dev/understanding-swift-closures</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[ios app development]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Sun, 26 Mar 2023 23:06:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679869917218/e3eba221-30b0-4d11-9243-c737283e87fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>The Swift documentation <a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures">provides</a> a comprehensive introduction to Closures. However, in this session, we will cover some interesting aspects of Swift closures that are not fully covered in the documentation.</p>
<p><strong>DEFINITION</strong>: Closures are self-contained blocks of functionality that can be passed around and used in your code.</p>
<p>Closures take one of three forms:</p>
<ul>
<li><p>Global functions and class/struct/enum methods are closures that have a name and don’t capture any values.</p>
</li>
<li><p>Nested functions are closures that have a name and can capture values from their enclosing function.</p>
</li>
<li><p>Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.</p>
</li>
</ul>
<p><strong>NOTE</strong>: Function <em>is</em> a special case of closure.</p>
<p><strong>NOTE</strong>: In the first two forms of closures listed above, the name actually refers to a function signature, which includes the function name and argument names.</p>
<p>The <strong>closure</strong> is a typed value and the type of closure is written like this:</p>
<p><code>(parameter-tuple) -&gt; return-value</code></p>
<p>We can consider function declaration as a variable of closure type once the following are equivalent in Swift:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">isPositive</span><span class="hljs-params">(x: Int)</span></span> -&gt; <span class="hljs-type">Bool</span> { <span class="hljs-keyword">return</span> x &gt; <span class="hljs-number">0</span> }
<span class="hljs-keyword">let</span> isPositive: (<span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Bool</span> = { x <span class="hljs-keyword">in</span> <span class="hljs-keyword">return</span> x &gt; <span class="hljs-number">0</span> }
</code></pre>
<p><code>{ param in ... }</code> - is called a <strong>closure expression.</strong></p>
<p><strong>Closure expressions</strong> can be considered as anonymous literals of closure type.</p>
<p>In the same way that <code>1234</code> and <code>"hello"</code> are <strong>integer</strong> and <strong>string</strong> literals. They’re also anonymous - they aren’t named. The only way they can be used is to assign them to a variable when they’re created, or to pass them to another function or method.</p>
<p>In the nutshell, closures provide a way to encapsulate a piece of functionality or behavior as a first-class object that can be passed around and used in different parts of your code.</p>
<h3 id="heading-how-to-create-a-reference-cycle-with-closure"><strong>How to create a reference cycle with closure?</strong></h3>
<p>The most dangerous thing about the closures is that you can create a reference cycle using them. <strong>Reference cycle</strong> (also known as a <strong>retain cycle</strong>) occurs when two or more objects hold strong references to each other, preventing them from being deallocated even when they're no longer needed. This can lead to memory leaks and negatively impact the performance of your app.</p>
<p>Let’s consider an example:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestObject</span> </span>{
    <span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>
    <span class="hljs-keyword">var</span> closure: () -&gt; <span class="hljs-type">Void</span> = {}

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"No reference cycles!"</span>) <span class="hljs-comment">// will never be called</span>
    }
}

<span class="hljs-keyword">var</span> object = <span class="hljs-type">TestObject</span>()

object.closure = {
    object.counter += <span class="hljs-number">1</span> <span class="hljs-comment">// reference cycle is created</span>
}
</code></pre>
<p>In this example, <strong>object</strong> holds the reference to the <strong>closure</strong> and <strong>closure</strong> captures the reference to the <strong>object</strong>. Object will never be deallocated and <strong>deinit</strong> will never be called.</p>
<p>Now that we have identified the problem, let's explore some solutions to fix it.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestObject</span> </span>{
    <span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>
    <span class="hljs-keyword">var</span> closure: () -&gt; <span class="hljs-type">Void</span> = {}

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"No reference cycles!"</span>) <span class="hljs-comment">// will be called at the end of the scope</span>
    }
}

<span class="hljs-keyword">var</span> object = <span class="hljs-type">TestObject</span>()

object.closure = { [<span class="hljs-keyword">weak</span> object] <span class="hljs-keyword">in</span> <span class="hljs-comment">// or [unowned object]</span>
    object?.counter += <span class="hljs-number">1</span>
}
</code></pre>
<p>Expression <code>[weak object]</code> in the begining of the closure is called a <strong>capture list</strong>.</p>
<p>Using a <strong>capture list with a weak modifier</strong> tells the closure that the object will be captured by weak reference to avoid retain cycle creation. Let’s take a look on the capture list more closely.</p>
<h3 id="heading-what-is-a-capture-list"><strong>What is a capture list?</strong></h3>
<p>When you define a closure, you can also specify a <strong>capture list</strong>, which tells the closure which variables and constants it should capture from the surrounding context.</p>
<p><strong>DEFINITION</strong>: A capture list is simply a comma-separated list (or just an array) of variable or constant names, enclosed in square brackets, like this:</p>
<pre><code class="lang-swift">{ [variable1, variable2, variable3] <span class="hljs-keyword">in</span>
    <span class="hljs-comment">// class object variables will be captured by strong reference</span>
    <span class="hljs-comment">// closure code goes here</span>
}
</code></pre>
<p>You can use <strong>weak</strong> or <strong>unowned</strong> modifiers to capture variables by weak or unowned reference correspondingly to prevent the retain cycle:</p>
<pre><code class="lang-swift">{ [<span class="hljs-keyword">weak</span> variable1, <span class="hljs-keyword">unowned</span> variable2] <span class="hljs-keyword">in</span>
    <span class="hljs-comment">// variable1 will be captured by weak reference</span>
    <span class="hljs-comment">// variable2 will be captured by unowned reference</span>
    <span class="hljs-comment">// closure code goes here</span>
}
</code></pre>
<p>But what is the difference between <strong>weak</strong> and <strong>unowned</strong>?</p>
<h3 id="heading-weak-vs-unowned">Weak vs unowned</h3>
<p>To prevent a retain cycle, you can use either the <code>weak</code> or <code>unowned</code> modifier. However, <code>weak</code> is generally considered safer because if the referenced object is deallocated, the <code>weak</code> reference becomes nil. On the other hand, an <code>unowned</code> reference would result in a runtime error.</p>
<p>Typically, <code>unowned</code> is used when there is a one-to-one relationship between two objects, and both are expected to be deallocated at the same time. However, using <code>unowned</code> is similar to using <strong>implicitly unwrapped optional</strong>. If the object is nil, a crash will occur. In general, it is recommended not to use the <code>!</code> operator and this advice can be considered for <code>unowned</code> as well.</p>
<p>Even though <code>unowned</code> is faster and allows for immutability and non-optionality, <code>weak</code> is safer, and the performance gain from using <code>unowned</code> may not be as valuable, especially considering the risk of crashes. This is just my perspective, and ultimately, the choice of which modifier to use depends on the specific context and needs of the code.</p>
<h3 id="heading-understanding-capture-lists"><strong>Understanding capture lists</strong></h3>
<p>The concept of a capture list may appear straightforward, but there are nuances to consider.</p>
<p>One example is when capturing a value type in a capture list. In this case, the value type will be copied into the closure.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">returnClosureWithCapturedInteger</span><span class="hljs-params">()</span></span> -&gt; () -&gt; <span class="hljs-type">Void</span> {
    <span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>

    <span class="hljs-keyword">let</span> closure = { [counter] <span class="hljs-keyword">in</span> <span class="hljs-comment">// capture list</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"counter ="</span>, counter)
    }

    counter = <span class="hljs-number">1</span>
    closure() <span class="hljs-comment">// counter = 0</span>
    counter = <span class="hljs-number">2</span>
    closure() <span class="hljs-comment">// counter = 0</span>
    counter = <span class="hljs-number">3</span>

    <span class="hljs-keyword">return</span> closure
}

<span class="hljs-keyword">let</span> closure = returnClosureWithCapturedInteger()
closure() <span class="hljs-comment">// counter = 0</span>
</code></pre>
<p>As demonstrated, even though we captured the value of the <strong>counter</strong> and later changed it, the captured value remained equal to 0 when the closure was called. You can say that this is expected because value types are copied every time they are passed to a new location. However, what happens if we do not include the value type in the capture list?</p>
<p>In this case, the behavior is not as straightforward as in the previous example:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">returnClosureWithoutCapturedInteger</span><span class="hljs-params">()</span></span> -&gt; () -&gt; <span class="hljs-type">Void</span> {
    <span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>

    <span class="hljs-keyword">let</span> closure = { <span class="hljs-comment">// nothing in capture list</span>
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"counter ="</span>, counter)
    }

    counter = <span class="hljs-number">1</span>
    closure() <span class="hljs-comment">// counter = 1</span>
    counter = <span class="hljs-number">2</span>
    closure() <span class="hljs-comment">// counter = 2</span>
    counter = <span class="hljs-number">3</span>

    <span class="hljs-keyword">return</span> closure
}

<span class="hljs-keyword">let</span> closure = returnClosureWithoutCapturedInteger()
closure() <span class="hljs-comment">// counter = 3</span>
</code></pre>
<p>What happened in this case? Even though we did not capture <code>counter</code> in the capture list, we still used it within the closure.</p>
<p>In this scenario, the closure captures a reference to enclosing function context and <code>counter</code> inside it as well so closure can use it within its own body. By capturing context by reference, the closure ensures that the <code>counter</code> does not disappear after the call to <code>returnClosureWithoutCapturedInteger</code> ends and <code>counter</code> remains available the next time the returned <code>closure()</code> is called. This means that when we change <code>counter</code> later and call the closure, the updated version of <code>counter</code> is printed, even though it should not exist anymore as a local variable.</p>
<h3 id="heading-what-about-capturing-reference-type">What about capturing reference type?</h3>
<p>When it comes to reference types, the outcome will be the same regardless of whether you include them in the capture list or not. However, it's recommended to capture reference types in the capture list as it's the only way to resolve any reference cycles that may be created when capturing reference values. As mentioned earlier, it's advisable to always use a capture list with a weak or unowned modifier to prevent reference cycles and ensure safe memory management.</p>
<h3 id="heading-understanding-escapingnon-escaping">Understanding escaping/non-escaping</h3>
<p>When closures are passed as arguments to a function, they need to be marked as <code>@escaping</code> to indicate the possibility of reference cycles. This annotation serves as a warning to the developer using the function. If closure is stored to be called later, it is considered <strong>escaping</strong>, while closures that don't leave the function's local scope are <strong>non-escaping</strong>. When using escaping closures, the compiler requires explicit usage of <code>self</code> in closure expressions to prevent unintentional strong capturing of self, which can cause reference cycles. Non-escaping closures cannot create permanent reference cycles since they are automatically destroyed when the function they're defined in returns.</p>
<p>By default, closures passed as function arguments are <strong>non-escaping</strong>. Remember that this default only applies to function parameters, and functions used as parameters in tuples or optionals are automatically marked as <strong>escaping</strong>. Therefore, a function that takes a function argument where the parameter is both optional and non-escaping cannot be written.</p>
<p><strong>NOTE</strong>: <code>@escaping</code> is explicit signal of potential creating of reference cycles</p>
<h3 id="heading-nested-functions">Nested functions</h3>
<p>As was already mentioned, <strong>nested functions</strong> are closures as well. This means the nested function captures variables from the surrounding context as well.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">returnNestedFunction</span><span class="hljs-params">()</span></span> -&gt; () -&gt; <span class="hljs-type">Void</span> {
    <span class="hljs-keyword">var</span> counter = <span class="hljs-number">0</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">nestedFunc</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Void</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"counter ="</span>, counter)
    }

    counter = <span class="hljs-number">1</span>
    nestedFunc() <span class="hljs-comment">// counter = 1</span>
    counter = <span class="hljs-number">2</span>
    nestedFunc() <span class="hljs-comment">// counter = 2</span>
    counter = <span class="hljs-number">3</span>

    <span class="hljs-keyword">return</span> nestedFunc
}

<span class="hljs-keyword">var</span> nestedFunc = returnNestedFunction()
nestedFunc() <span class="hljs-comment">// counter = 3</span>
</code></pre>
<p>As you can see nested function works in the same way as closures <strong>without capture list</strong>. Nested function captures a reference to enclosing function context so when we change <code>counter</code> later and call the closure, the updated version of <code>counter</code> is printed.</p>
<h3 id="heading-unobvious-cases-of-retain">Unobvious cases of retain</h3>
<p>Capturing the object in closure not the only way you can introduce reference cycle in your code.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AlertView</span> </span>{
    <span class="hljs-keyword">var</span> buttonTapped: (() -&gt; <span class="hljs-type">Void</span>)?
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewController</span> </span>{
    <span class="hljs-keyword">let</span> alert: <span class="hljs-type">AlertView</span>

    <span class="hljs-keyword">init</span>() {
        alert = <span class="hljs-type">AlertView</span>()
        alert.buttonTapped = buttonTapped <span class="hljs-comment">// self will be captured!!!</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buttonTapped</span><span class="hljs-params">()</span></span> {}

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"No reference cycles!"</span>) <span class="hljs-comment">// Will never be called</span>
    }
}

<span class="hljs-keyword">let</span> vc = <span class="hljs-type">ViewController</span>()
</code></pre>
<p>The <code>alert.buttonTapped = buttonTapped</code> line looks like an innocent assignment, but beware: we’ve just created a reference cycle! Every reference to a method of an object (like <code>buttonTapped</code> in the example) implicitly captures the method's self object.</p>
<p>When the <code>AlertView</code> object calls the closure that’s stored in its buttonTapped property, the property must somehow know which object’s instance method it needs to call - it’s not enough to just store a reference to <code>ViewController.buttonTapped</code> method without referencing the instance. If <code>ViewController</code> object will be deallocated at some moment <code>AlertView</code> object will lose access to the method. This is why it captures the self in this case.</p>
<p>Under the hood, instance methods are modeled as closures that, given an instance, return another closure that then operates on the instance. Implicit type of the expression <code>ViewController.buttonTapped</code> is actually <code>(ViewController) -&gt; (Int) -&gt; ()</code>.</p>
<p>And <code>vc.buttonTapped</code> is actually just another way of writing <code>ViewController.buttonTapped(vc)</code>.</p>
<p>Both expressions return a closure of type <code>(Int) -&gt; ()</code>, and this closure strongly captures the <code>vc</code> instance.</p>
<p>In this scenario, to avoid a strong reference retaining, you need to wrap the method call in another closure that captures the object weakly:</p>
<pre><code class="lang-swift">alert.buttonTapped = { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] index <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">self</span>?.buttonTapped(atIndex: index)
}
</code></pre>
<h3 id="heading-is-closure-reference-or-value-type">Is closure reference or value type?</h3>
<p>In Swift, reference types are not limited to classes only. Functions and closures are also considered reference types.</p>
<p>Unlike value types, which are stored in the stack, reference types are stored in the heap. Hence, closures are also stored in the heap. Additionally, any captured value type is moved to the heap so that the closure can access it for the duration of its lifetime.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeIncrementer</span><span class="hljs-params">(forIncrement amount: Int)</span></span> -&gt; () -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-keyword">var</span> runningTotal = <span class="hljs-number">0</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">incrementer</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Int</span> {
        runningTotal += amount
        <span class="hljs-keyword">return</span> runningTotal
    }
    <span class="hljs-keyword">return</span> incrementer
}
</code></pre>
<p>This is an example from the Swift book and as you can already guess:</p>
<ul>
<li><p><code>incrementer</code> as a closure and it will be stored in the heap.</p>
</li>
<li><p><code>runningTotal</code> is a value type but it will be moved to the heap as well.</p>
</li>
</ul>
<h3 id="heading-recap">Recap</h3>
<ul>
<li><p>Closures are blocks of functionality that can be passed around and used in your code;</p>
</li>
<li><p>Function <em>is</em> a special case of closure;</p>
</li>
<li><p>You can create a retain cycle using closures;</p>
</li>
<li><p>You can use capture list and weak/unowned modifiers to prevent a retain cycle.</p>
</li>
<li><p>Closure can catch value type by reference or copy it;</p>
</li>
<li><p><code>@escaping</code> is an explicit signal of potential creating of reference cycles;</p>
</li>
<li><p>Nested functions are closures as well;</p>
</li>
<li><p>You should be careful with assigning a class method to closure because it retains the class object as well;</p>
</li>
<li><p>Closure is a reference type.</p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Overall, closures are powerful and essential tool for Swift developers, and understanding how to use them effectively can significantly reduce problems that can be introduced in your app in process of development.</p>
<p>Even though we discussed a lot about closures in this post, there are still a lot of things we didn’t cover that you can read about like for example <a target="_blank" href="https://www.hackingwithswift.com/swift/5.3/multiple-trailing-closures">multiple trailing closures</a>(<strong>Swift 5.3</strong>) and <a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Autoclosures">autoclosures</a>.</p>
<h3 id="heading-links">Links</h3>
<p><a target="_blank" href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/">https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/</a></p>
]]></content:encoded></item><item><title><![CDATA[Combine: Yet One Another Quick Start Guide]]></title><description><![CDATA[Apple announced the Combine framework at the WWDC 2019, which allows the processing of values over time. It is the native Swift implementation of the functional reactive programming paradigm (FRP). Despite its usefulness, many developers still don't ...]]></description><link>https://vbat.dev/combine-yet-one-another-quick-start-guide</link><guid isPermaLink="true">https://vbat.dev/combine-yet-one-another-quick-start-guide</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Combine]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Tue, 14 Feb 2023 00:53:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676419463085/a76f68e0-ffe5-44b3-924b-b2899b56b444.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Apple announced the Combine framework at the WWDC 2019, which allows the processing of values over time. It is the native Swift implementation of the <strong>functional reactive programming paradigm (FRP)</strong>. Despite its usefulness, many developers still don't use it in their projects. I didn't use it either before I joined my current project where Combine is actively used for a lot of things. So I had to jump in Combine quickly. This article is meant to help others understand the basic concepts of Combine and start using it, even if they have never worked with FRP frameworks before.</p>
<p>FRP is a combination of functional and reactive programming that manages time-varying values, such as user inputs and server responses, in a clear and straightforward way. In FRP, values and events are represented as streams of values that change over time, and reactive functions are used to show how changes in input streams affect output streams. The aim of FRP is to provide a straightforward method for describing the behavior of a system over time.</p>
<h1 id="heading-observer-pattern">Observer pattern</h1>
<p>A good way to start comprehending Combine is by familiarizing oneself with the <a target="_blank" href="https://refactoring.guru/design-patterns/observer">Observer</a> design pattern.</p>
<p>The <strong>Observer</strong> pattern allows an object, called the <strong>Subject</strong> (aka <strong>Observable</strong> aka <strong>Publisher</strong>), to notify a set of dependent objects, called <strong>Observers</strong> (aka <strong>Subscribers</strong>), about changes in its state.</p>
<p>An example should help clarify the concept:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Observer</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">update</span><span class="hljs-params">(value: Int)</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Subject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addObserver</span><span class="hljs-params">(<span class="hljs-number">_</span> observer: Observer)</span></span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeObserver</span><span class="hljs-params">(<span class="hljs-number">_</span> observer: Observer)</span></span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">notifyObservers</span><span class="hljs-params">()</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcreteObserver</span>: <span class="hljs-title">Observer</span> </span>{

    <span class="hljs-keyword">let</span> id: <span class="hljs-type">String</span> <span class="hljs-comment">// printed in console to identify observers </span>

    <span class="hljs-keyword">init</span>(id: <span class="hljs-type">String</span>) {
        <span class="hljs-keyword">self</span>.id = id
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">update</span><span class="hljs-params">(value: Int)</span></span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"\(id): Value changed to \(value)"</span>)
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcreteSubject</span>: <span class="hljs-title">Subject</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> observers: [<span class="hljs-type">Observer</span>] = []

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> value: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span> {
        <span class="hljs-keyword">didSet</span> {
            notifyObservers()
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addObserver</span><span class="hljs-params">(<span class="hljs-number">_</span> observer: Observer)</span></span> {
        observers.append(observer)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeObserver</span><span class="hljs-params">(<span class="hljs-number">_</span> observer: Observer)</span></span> {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> index = observers.firstIndex(<span class="hljs-keyword">where</span>: { $<span class="hljs-number">0</span> === observer }) {
            observers.remove(at: index)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">notifyObservers</span><span class="hljs-params">()</span></span> {
        observers.forEach { $<span class="hljs-number">0</span>.update(value: value) }
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">setValue</span><span class="hljs-params">(value: Int)</span></span> {
        <span class="hljs-keyword">self</span>.value = value
    }
}

<span class="hljs-keyword">let</span> subject = <span class="hljs-type">ConcreteSubject</span>()
<span class="hljs-comment">// Subject will add observers to notify them </span>
<span class="hljs-comment">// later about it's value change</span>

<span class="hljs-keyword">let</span> firstObserver = <span class="hljs-type">ConcreteObserver</span>(id: <span class="hljs-string">"First"</span>)
<span class="hljs-keyword">let</span> secondObserver = <span class="hljs-type">ConcreteObserver</span>(id: <span class="hljs-string">"Second"</span>)

subject.addObserver(firstObserver)

subject.setValue(value: <span class="hljs-number">1</span>)
<span class="hljs-comment">// First: Value changed to 1</span>

subject.addObserver(secondObserver)

subject.setValue(value: <span class="hljs-number">2</span>)
<span class="hljs-comment">// First: Value changed to 2</span>
<span class="hljs-comment">// Second: Value changed to 2</span>

<span class="hljs-comment">// remove observer to stop notifing this observer about value changes</span>
subject.removeObserver(firstObserver)

subject.setValue(value: <span class="hljs-number">3</span>)
<span class="hljs-comment">// Second: Value changed to 3</span>
</code></pre>
<p>The <strong>Observer</strong> pattern establishes a one-to-many relationship between the subject and its observers, such that any changes in the subject are automatically notified to all its observers.</p>
<p>The <strong>Observer</strong> provides a way to achieve loose coupling between objects in a system, allowing objects to interact without having direct references to each other. This makes it easier to add or remove observers, as well as to modify the subject or observer objects independently, without affecting the rest of the system.</p>
<p>The <strong>Observer</strong> allows you to follow the <a target="_blank" href="https://en.wikipedia.org/wiki/SOLID"><strong><em>Open/Closed Principle</em></strong></a>. You can introduce new subscriber classes without having to change the publisher’s code and vice versa.</p>
<p><strong>TIP</strong>: You can consider Combine as the advanced Observer (or Observer on steroids).</p>
<p>Let's take a look at a simple example of the Observer pattern implemented using Combine:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> subject = <span class="hljs-type">CurrentValueSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt;(<span class="hljs-string">"Hello World!"</span>)

subject.sink { <span class="hljs-comment">// addObserver and get current value via the closure</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Value:"</span>, $<span class="hljs-number">0</span>)
} 
<span class="hljs-comment">// Value: Hello World!</span>

subject.value = <span class="hljs-string">"Brand New World!"</span>
<span class="hljs-comment">// Value: Brand New World!</span>
</code></pre>
<p>As you can see it is very similar and easy to use, maybe even easier than our Observer implementation above. Combine allows you not only to subscribe for data updates but also to use different operators to manipulate the stream of updates. In this example we will map the value from the subject to the new one:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> subject = <span class="hljs-type">CurrentValueSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt;(<span class="hljs-string">"Hello World!"</span>)

subject
    .<span class="hljs-built_in">map</span> {
        $<span class="hljs-number">0</span> + <span class="hljs-string">" Here I am!"</span>
    }
    .sink {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Value:"</span>, $<span class="hljs-number">0</span>)
    }
<span class="hljs-comment">// Value: Hello World! Here I am!</span>
</code></pre>
<h1 id="heading-core-concepts">Core concepts</h1>
<p>There are three core Combine concepts: <strong>Publisher, Subscriber</strong> and <strong>Operator.</strong> You already know about publishers and subscribers from the Observer pattern example above but let’s look at them more deeply from the Combine perspective.</p>
<h1 id="heading-publisher"><strong>Publisher</strong></h1>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Publisher</span> </span>{
    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Output</span>
    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Failure</span> : <span class="hljs-type">Error</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span>&lt;S&gt;<span class="hljs-params">(subscriber: S)</span></span> <span class="hljs-keyword">where</span> <span class="hljs-type">S</span> : <span class="hljs-type">Subscriber</span>, <span class="hljs-type">Self</span>.<span class="hljs-type">Failure</span> == <span class="hljs-type">S</span>.<span class="hljs-type">Failure</span>, <span class="hljs-type">Self</span>.<span class="hljs-type">Output</span> == <span class="hljs-type">S</span>.<span class="hljs-type">Input</span>
}
</code></pre>
<p>The <strong>Publisher</strong> is a protocol describing an object which can transmit values over time. It is generic over two types:</p>
<ul>
<li><p><strong>Publisher.Output</strong> is the type of the output values of the publisher. If it’s an <strong>Int</strong> publisher, it can never emit a <strong>String</strong> or a <strong>Data</strong> value.</p>
</li>
<li><p><strong>Publisher.Failure</strong> is the type of error the publisher can throw if it fails. If the publisher can never fail, you specify that by using a <strong>Never</strong> failure type.</p>
</li>
</ul>
<p>As you can see, every publisher must be able to receive new subscribers so you need to implement the <strong>receive</strong> method, in which you are given access to the <strong>Subscriber</strong> when it subscribes to the publisher.</p>
<h1 id="heading-subscriber"><strong>Subscriber</strong></h1>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Subscriber</span> </span>{
    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Input</span>
    <span class="hljs-keyword">associatedtype</span> <span class="hljs-type">Failure</span>: <span class="hljs-type">Error</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(subscription: Subscription)</span></span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(<span class="hljs-number">_</span> input: <span class="hljs-keyword">Self</span>.Input)</span></span> -&gt; <span class="hljs-type">Subscribers</span>.<span class="hljs-type">Demand</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(completion: Subscribers.Completion&lt;<span class="hljs-keyword">Self</span>.Failure&gt;)</span></span>
}
</code></pre>
<p>The subscriber’s <code>Subscriber.Input</code> and <code>Subscriber.Failure</code> associated types must match the <code>Publisher.Output</code> and <code>Publisher.Failure</code> types declared by the publisher.</p>
<p>Subscribers are usually created implicitly using 2 methods of <code>Publisher</code> protocol:</p>
<ul>
<li><p><strong>sink</strong> (closure);</p>
</li>
<li><p><strong>assign</strong> (bind key path).</p>
</li>
</ul>
<pre><code class="lang-swift">publisher
    .sink(
      receiveCompletion: {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received completion"</span>, $<span class="hljs-number">0</span>)
      },
      receiveValue: {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received value"</span>, $<span class="hljs-number">0</span>)
      }
    ) <span class="hljs-comment">// closure based</span>

publisher
     .assign(to: \.value, on: object) <span class="hljs-comment">// assign to objects KeyPath</span>

publisher
     .assign(to: &amp;object.$value) <span class="hljs-comment">// assign to publisher</span>
</code></pre>
<p>Apple provides the classes <code>Sink</code> and <code>Assign</code> which implement the <code>Subscriber</code> protocol, but you'll likely never need to create them manually. These objects are created under the hood in the <code>sink</code> and <code>assign</code> methods and they are returned as type-erased <code>AnyCancellable</code> objects, allowing you to cancel the subscription without knowing the details about the subscription.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Sink</span>&lt;<span class="hljs-title">Input</span>, <span class="hljs-title">Failure</span>&gt;: <span class="hljs-title">Subscriber</span>, <span class="hljs-title">Cancellable</span> <span class="hljs-title">where</span> <span class="hljs-title">Failure</span>: <span class="hljs-title">Error</span> </span>{
    <span class="hljs-comment">// Subscriber</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(subscription: Subscription)</span></span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(<span class="hljs-number">_</span> value: Input)</span></span> -&gt; <span class="hljs-type">Subscribers</span>.<span class="hljs-type">Demand</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span><span class="hljs-params">(completion: Subscribers.Completion&lt;Failure&gt;)</span></span>

    <span class="hljs-comment">// Cancellable</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">cancel</span><span class="hljs-params">()</span></span>
}
</code></pre>
<p>You can read more about type erasures in Swift <a target="_blank" href="https://www.donnywals.com/understanding-type-erasure-in-swift/">here</a> if you want.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AnyCancellable</span>: <span class="hljs-title">Cancellable</span>, <span class="hljs-title">Hashable</span> </span>{ <span class="hljs-comment">// type erasure</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">cancel</span><span class="hljs-params">()</span></span>
}
</code></pre>
<h2 id="heading-memory-management">Memory Management</h2>
<p>With the help of the <strong>Cancellable protocol,</strong> you don’t need to specifically memory manage a subscription. You saw that your subscription code returns a <strong>Cancellable</strong> object. Whenever you release that object from memory, it cancels the whole subscription and releases its resources from memory. This means you can easily “bind” the lifespan of a subscription by storing it in a property on your view controller, for example. This way, any time the user dismisses the view controller from the view stack, that will deinitialize its properties and will also cancel your subscription.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> cancellable1 = publisher
    .sink {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received value"</span>, $<span class="hljs-number">0</span>)
    }

<span class="hljs-keyword">let</span> cancellable2 = publisher <span class="hljs-comment">// assign to objects KeyPath</span>
     .assign(to: \.value, on: object)

cancellable1.cancel() <span class="hljs-comment">// cancel the subscriprion</span>
<span class="hljs-comment">// cancellable2 will be cancelled when local variable cancellable2 stops to exist</span>
</code></pre>
<p>To automate this process, you can just have a <code>Set&lt;AnyCancellable&gt;</code> property and throw as many subscriptions inside it as you want. They’ll all be automatically canceled and released when the property is released from memory. (And this is why AnyCancellable conforms to Hashable - to store it in a Set).</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> cancellables = <span class="hljs-type">Set</span>&lt;<span class="hljs-type">AnyCancellable</span>&gt;()

publisher
    .sink {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received value"</span>, $<span class="hljs-number">0</span>)
    }
    .store(<span class="hljs-keyword">in</span>: &amp;cancellables)

publisher <span class="hljs-comment">// assign to objects KeyPath</span>
    .assign(to: \.value, on: object)
    .store(<span class="hljs-keyword">in</span>: &amp;cancellables)
</code></pre>
<p><strong>NOTE:</strong> The assign(to:) (second one) method doesn’t return an <strong>AnyCancellable</strong> token, because it manages the lifecycle internally and cancels the subscription when the <code>@Published</code> property deinitializes (we will discuss <code>@Published</code> later in this post).</p>
<h1 id="heading-subject"><strong>Subject</strong></h1>
<p><strong>Subject</strong> is an extended publisher which knows how to send values into a stream, by calling its <code>send(_:)</code> method. Subject also has methods to send <code>completion</code> and <code>subscription</code> to the subscribers. Subject can be useful for adapting existing imperative code to the Combine model.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Subject</span>&lt;<span class="hljs-title">Output</span>, <span class="hljs-title">Failure</span>&gt;: <span class="hljs-title">AnyObject</span>, <span class="hljs-title">Publisher</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">send</span><span class="hljs-params">(<span class="hljs-number">_</span> value: <span class="hljs-keyword">Self</span>.Output)</span></span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">send</span><span class="hljs-params">(completion: Subscribers.Completion&lt;<span class="hljs-keyword">Self</span>.Failure&gt;)</span></span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">send</span><span class="hljs-params">(subscription: Subscription)</span></span>
}
</code></pre>
<p>In your daily work instead of implementing the <code>Publisher</code> or <code>Subject</code> protocols yourself, you can create a publisher by using one of several types provided by the Combine framework:</p>
<ol>
<li><p>Use a concrete subclass of <code>Subject</code>, such as <code>PassthroughSubject</code>, to publish values on-demand by calling its <code>Subject.send(_:)</code> method.</p>
</li>
<li><p>Use a <code>CurrentValueSubject</code> to publish whenever you update the subject’s underlying value.</p>
</li>
<li><p>Add the <code>@Published</code> property wrapper to a property. In doing so, the property gains a publisher that emits an event whenever the property’s value changes.</p>
</li>
<li><p>Use one of Apple’s Convenience Publishers like <strong>Empty</strong>, <strong>Just</strong>, <strong>Deferred</strong> or <strong>Future</strong>.</p>
</li>
</ol>
<p>We will take a look at them in practice a little bit later.</p>
<h1 id="heading-subscription">Subscription</h1>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Subscription</span>: <span class="hljs-title">Cancellable</span> </span>{
    <span class="hljs-comment">// Tells a publisher that it may send more values to the subscriber.</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">request</span><span class="hljs-params">(<span class="hljs-number">_</span> demand: Subscribers.Demand)</span></span>
}
</code></pre>
<p><code>Subscription</code> is the protocol representing the connection of a subscriber to a publisher.</p>
<p><code>Subscription.request</code> method is needed to provide demand for new values. After publisher calls <code>Subscriber.receive(subscription:)</code> to give the subscription to the subscriber, the subscriber can use this subscription to tell a publisher that it may send more values.</p>
<p><code>Subscription</code> is cancellable. The subscription is canceled after you call cancel() of the subscriber. Canceling a subscription frees up any resources previously allocated.</p>
<p><strong>TIP</strong>: You can only cancel a <code>Subscription</code> once.</p>
<h1 id="heading-lifecycle-of-subscription"><strong>Lifecycle of subscription</strong></h1>
<ol>
<li><p>The subscriber subscribes to the publisher.</p>
</li>
<li><p>The publisher creates a subscription and gives it to the subscriber.</p>
</li>
<li><p>The subscriber requests values via subscription.</p>
</li>
<li><p>The publisher sends values.</p>
</li>
<li><p>The publisher sends a completion.</p>
</li>
</ol>
<p><strong>TIP</strong>: Once a publisher emits a completion event (whether it’s a normal completion or an error), it’s finished and can <strong>no longer emit</strong> any more events.</p>
<h1 id="heading-publishers-in-practice">Publishers in practice</h1>
<p>Let’s take a look at publishers provided by Apple out of the box.</p>
<h3 id="heading-passthroughsubject">PassthroughSubject</h3>
<p><code>PassthroughSubject</code> allows you to publish new values on demand (when the subject asked to send them). Use <strong>send</strong> method to send the new values to the subscribers.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> subject = <span class="hljs-type">PassthroughSubject</span>&lt;<span class="hljs-type">Int</span>, <span class="hljs-type">Never</span>&gt;()

<span class="hljs-keyword">let</span> subscription = subject.sink {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Received value:"</span>, $<span class="hljs-number">0</span>)
}

subject.send(<span class="hljs-number">1</span>) <span class="hljs-comment">// Received value: 1</span>
subject.send(<span class="hljs-number">2</span>) <span class="hljs-comment">// Received value: 2</span>
</code></pre>
<h3 id="heading-currentvaluesubject">CurrentValueSubject</h3>
<p><code>CurrentValueSubject</code> stores the current value. It broadcasts the current value to subscribers whenever they subscribe. Calling send(:) on a current value subject is one way to send a new value. Another way is to assign a new value to its value property. You still need to send completion events by using send(completion:).</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> subject = <span class="hljs-type">CurrentValueSubject</span>&lt;<span class="hljs-type">Int</span>, <span class="hljs-type">Never</span>&gt;(<span class="hljs-number">0</span>)

<span class="hljs-keyword">let</span> subscription1 = subject.sink {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Subscriber 1 received value:"</span>, $<span class="hljs-number">0</span>)
} 
<span class="hljs-comment">// Subscriber 1 received value: 0</span>

subject.send(<span class="hljs-number">1</span>) 
<span class="hljs-comment">// Subscriber 1 received value: 1</span>

<span class="hljs-keyword">let</span> subscription2 = subject.sink {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Subscriber 2 received value:"</span>, $<span class="hljs-number">0</span>)
} 
<span class="hljs-comment">// Subscriber 2 received value: 1</span>

subject.value = <span class="hljs-number">2</span> <span class="hljs-comment">// Same if call send(2) </span>
<span class="hljs-comment">// Subscriber 1 received value: 2</span>
<span class="hljs-comment">// Subscriber 2 received value: 2</span>
</code></pre>
<h3 id="heading-published"><code>@Published</code></h3>
<p><code>@Published</code> is a property wrapper that allows you to easily turn a property into a publisher.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClass</span> </span>{
    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> myValue: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>
}

<span class="hljs-keyword">let</span> myInstance = <span class="hljs-type">MyClass</span>()

myInstance.$myValue.sink {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"new value:"</span>, $<span class="hljs-number">0</span>)
}
<span class="hljs-comment">// new value: 0</span>

myInstance.myValue = <span class="hljs-number">1</span>
<span class="hljs-comment">// new value: 1</span>
</code></pre>
<p>Basically, <code>@Published</code> is very similar to <code>CurrentValueSubject</code>. It can be more simple in use sometimes and is intended to be used in SwiftUI. However, there are some differences:</p>
<ul>
<li><p><code>@Published</code> fires on <strong>willSet</strong> of its wrapped property, while CurrentValueSubject fires on <strong>didSet;</strong></p>
</li>
<li><p><code>@Published</code> is not supported in a protocol declaration;</p>
</li>
<li><p><code>@Published</code> is class-constrained, you can only use it in classes.</p>
</li>
</ul>
<h2 id="heading-hot-and-cold-signals"><strong>Hot and cold signals</strong></h2>
<p>In reactive programming, <strong>hot</strong> and <strong>cold signals</strong> are two types of signals that describe the behavior of data streams.</p>
<p>A hot signal is a signal that starts emitting events as soon as it is created, regardless of whether there are any subscribers. For example, a hot signal could be a continuous stream of data from a sensor, such as the accelerometer of a device, or a live feed from a stock market.</p>
<p>In contrast, a cold signal is a signal that only starts emitting events when there is a subscriber. When the subscriber cancels its subscription, the signal stops emitting events. For example, a cold signal could be a file download, where the download only starts when a subscriber subscribes to the signal and stops when the subscriber cancels its subscription.</p>
<p>The distinction between hot and cold signals is important because it affects the behavior of subscribers, and the way events are emitted and processed. It can be useful to know about a hot and cold signal when you try to understand or explain how a specific data stream works.</p>
<p><strong>TIP</strong>: <code>CurrentValueSubject</code> and <code>PassthroughSubject</code> are hot signals.</p>
<h2 id="heading-just"><strong>Just</strong></h2>
<p>Just is a publisher that emits an output to each subscriber just once after the subscription and then it completes. Just can’t fail with an error.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> justPublisher = <span class="hljs-type">Just</span>(<span class="hljs-string">"Hello World"</span>)

justPublisher
    .sink {
         <span class="hljs-built_in">print</span>(<span class="hljs-string">"First subscriber"</span>, $<span class="hljs-number">0</span>)
     }
<span class="hljs-comment">// First subscriber Hello World</span>

justPublisher
    .sink {
         <span class="hljs-built_in">print</span>(<span class="hljs-string">"Second subscriber"</span>, $<span class="hljs-number">0</span>)
     }
<span class="hljs-comment">// Second subscriber Hello World</span>
</code></pre>
<p><strong>TIP</strong>: Just is an example of a cold signal in Combine.</p>
<p>For example, it can be useful in writing stubs for your unit tests when you need to mock the method that returns a publisher.</p>
<h2 id="heading-futures"><strong>Futures</strong></h2>
<p>Futures in Combine is another example of a cold publisher. A future is a publisher that emits a single value or an error on a new subscription and then completes.</p>
<p>It does this by invoking a closure when a value or error is available, and that closure is, in fact, the <strong>promise</strong>. If <strong>the promise</strong> will not be called, the subscriber never gets the value.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> future = <span class="hljs-type">Future</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt; { promise <span class="hljs-keyword">in</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"First future started"</span>)
    promise(.success(<span class="hljs-string">"First success"</span>))
}
<span class="hljs-comment">// First future started</span>

future.sink { value <span class="hljs-keyword">in</span>
    <span class="hljs-built_in">print</span>(value)
}
<span class="hljs-comment">// First success</span>

<span class="hljs-keyword">let</span> future2 = <span class="hljs-type">Future</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt; { promise <span class="hljs-keyword">in</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Second future started"</span>)
    <span class="hljs-comment">// promise is not called</span>
}
<span class="hljs-comment">// Second future started</span>

future2.sink { value <span class="hljs-keyword">in</span>
    <span class="hljs-built_in">print</span>(value) <span class="hljs-comment">// will never be called</span>
}
</code></pre>
<p><strong>TIP</strong>: <strong>Future</strong> is <em>greedy</em>, meaning executes as soon as it’s created and doesn’t wait for a subscription. It does not require a subscriber like regular publishers that are <strong><em>lazy</em></strong>.</p>
<p><strong>TIP</strong>: <strong>Future</strong> does not re-execute its promise; instead, it replays its output.</p>
<p>Futures are great to use for network requests because it’s a great abstraction to the asynchronous nature of network requests that can succeed with value or fail. By using futures, you can ensure that the network request is only made once, even if multiple parts of your application need the data.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">NetworkError</span>: <span class="hljs-title">Error</span> </span>{
    <span class="hljs-keyword">case</span> invalidResponse
    <span class="hljs-keyword">case</span> decodingError
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">User</span>: <span class="hljs-title">Decodable</span> </span>{
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> email: <span class="hljs-type">String</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchUser</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">AnyPublisher</span>&lt;<span class="hljs-type">User</span>, <span class="hljs-type">NetworkError</span>&gt; {
    <span class="hljs-type">Future</span> { promise <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> url = <span class="hljs-type">URL</span>(string: <span class="hljs-string">"&lt;https://api.example.com/user&gt;"</span>) <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

        <span class="hljs-type">URLSession</span>.shared.dataTask(with: url) { data, response, error <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> error = error {
                promise(.failure(error))
                <span class="hljs-keyword">return</span>
            }
            <span class="hljs-keyword">if</span> response.statusCode != <span class="hljs-number">200</span> {
                promise(.failure(<span class="hljs-type">NetworkError</span>.invalidResponse))
                <span class="hljs-keyword">return</span>
            }
            <span class="hljs-keyword">do</span> {
                <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">try</span> <span class="hljs-type">JSONDecoder</span>().decode(<span class="hljs-type">User</span>.<span class="hljs-keyword">self</span>, from: data)
                promise(.success(user))
            } <span class="hljs-keyword">catch</span> {
                promise(.failure(<span class="hljs-type">NetworkError</span>.decodingError))
            }
        }.resume()
    }
    .eraseToAnyPublisher()
}
</code></pre>
<p>You probably noticed something new in the code above - <strong>AnyPublisher</strong>.</p>
<h2 id="heading-anypublisher"><strong>AnyPublisher</strong></h2>
<p>AnyPublisher is a type-erased struct that conforms to the Publisher protocol. It allows you to hide details about the publisher that you may not want to expose to subscribers, which is good for encapsulation and user access control. It does the same job for Publisher as <strong>AnyCancellable</strong> for Subscriber.</p>
<p><strong>TIP</strong>: <strong>AnyPublisher</strong> does not have a send(_:) operator or value property, so you cannot add new values to that publisher directly.</p>
<p>You can call <code>eraseToAnyPublisher()</code> operator to map your publisher to AnyPublisher and erase its type.</p>
<h1 id="heading-operators"><strong>Operators</strong></h1>
<p>Combine operators are a set of functions provided by Apple that allow you to process and transform streams of values over time. These operators can be intuitive and already known to you from using Swift like filter, map, first and etc. But operators can be also quite complex and hard to understand. Will will take a look at some of them.</p>
<p>Each operator is the function in the Publisher extension:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Publisher</span> </span>{
    <span class="hljs-comment">// map operator</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">map</span>&lt;T&gt;<span class="hljs-params">(<span class="hljs-number">_</span> transform: @escaping <span class="hljs-params">(<span class="hljs-keyword">Self</span>.Output)</span></span></span> -&gt; <span class="hljs-type">T</span>) -&gt; <span class="hljs-type">Publishers</span>.<span class="hljs-type">Map</span>&lt;<span class="hljs-type">Self</span>, <span class="hljs-type">T</span>&gt;
}
</code></pre>
<p>As you can see it returns <a target="_blank" href="http://Publishers.Map"><code>Publishers.Map</code></a> as a result.</p>
<p><code>Publishers</code> is the enum and it is the namespace for publishers structs like <code>Map</code>.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Map</span>&lt;<span class="hljs-title">Upstream</span>, <span class="hljs-title">Output</span>&gt;: <span class="hljs-title">Publisher</span> <span class="hljs-title">where</span> <span class="hljs-title">Upstream</span>: <span class="hljs-title">Publisher</span> </span>{

    <span class="hljs-keyword">typealias</span> <span class="hljs-type">Failure</span> = <span class="hljs-type">Upstream</span>.<span class="hljs-type">Failure</span>

    <span class="hljs-comment">// The publisher from which this publisher receives elements.</span>
    <span class="hljs-keyword">let</span> upstream: <span class="hljs-type">Upstream</span>

    <span class="hljs-comment">// The closure that transforms elements from the upstream publisher.</span>
    <span class="hljs-keyword">let</span> transform: (<span class="hljs-type">Upstream</span>.<span class="hljs-type">Output</span>) -&gt; <span class="hljs-type">Output</span>

    <span class="hljs-comment">// Creates a publisher that transforms all elements from the upstream publisher with a provided closure.</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">init</span>(upstream: <span class="hljs-type">Upstream</span>, transform: @escaping (<span class="hljs-type">Upstream</span>.<span class="hljs-type">Output</span>) -&gt; <span class="hljs-type">Output</span>)

    <span class="hljs-comment">// Attaches the specified subscriber to this publisher.</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">receive</span>&lt;S&gt;<span class="hljs-params">(subscriber: S)</span></span> <span class="hljs-keyword">where</span> <span class="hljs-type">Output</span> == <span class="hljs-type">S</span>.<span class="hljs-type">Input</span>, <span class="hljs-type">S</span>: <span class="hljs-type">Subscriber</span>, <span class="hljs-type">Upstream</span>.<span class="hljs-type">Failure</span> == <span class="hljs-type">S</span>.<span class="hljs-type">Failure</span>
}
</code></pre>
<p>As you might guess already, under the hood, operators create a new publisher that transforms the elements from the upstream publisher and returns this new publisher as the result. This explains why you can chain many operators in a row.</p>
<p>To give you a quick introduction, we'll focus on some of the most useful examples of operators. I won't go into detail about all of them, as there are many, but I'll provide the best use cases that you're likely to encounter in your daily work.</p>
<h2 id="heading-flatmap"><strong>flatMap</strong></h2>
<p>The <code>flatMap</code> is used to transform a stream of values into a new stream of values. It works by taking the values emitted by one publisher and transforming them into new publishers using a closure.</p>
<p>The most common use case for <code>flatMap</code> is network requests. For example, you might have a publisher that emits API request, and you want to make another API request when the initial one completes.</p>
<p>In the example below we call the <code>getUserData</code> request when <code>authenticateUser</code> request completes. So <code>flatMap</code> can be very useful to chain the API requests.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">API</span> </span>{
    <span class="hljs-keyword">typealias</span> <span class="hljs-type">Token</span> = <span class="hljs-type">String</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">authenticateUser</span><span class="hljs-params">(userId: Int)</span></span> -&gt; <span class="hljs-type">AnyPublisher</span>&lt;<span class="hljs-type">Token</span>, <span class="hljs-type">Error</span>&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-type">Future</span>&lt;<span class="hljs-type">Token</span>, <span class="hljs-type">Error</span>&gt; { promise <span class="hljs-keyword">in</span>
            <span class="hljs-comment">// Simulate a network request</span>
            <span class="hljs-type">DispatchQueue</span>.global().asyncAfter(deadline: .now() + <span class="hljs-number">0.5</span>) {
                promise(.success(<span class="hljs-string">"Token"</span>))
            }
        }
        .eraseToAnyPublisher()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getUserData</span><span class="hljs-params">(token: Token)</span></span> -&gt; <span class="hljs-type">AnyPublisher</span>&lt;<span class="hljs-type">Data</span>, <span class="hljs-type">Error</span>&gt; {
        <span class="hljs-keyword">return</span> <span class="hljs-type">Future</span>&lt;<span class="hljs-type">Data</span>, <span class="hljs-type">Error</span>&gt; { promise <span class="hljs-keyword">in</span>
            <span class="hljs-comment">// Simulate a network request</span>
            <span class="hljs-type">DispatchQueue</span>.global().asyncAfter(deadline: .now() + <span class="hljs-number">0.5</span>) {
                promise(.success(<span class="hljs-type">Data</span>()))
            }
        }
        .eraseToAnyPublisher()
    }
}

<span class="hljs-keyword">var</span> cancellable = <span class="hljs-type">Set</span>&lt;<span class="hljs-type">AnyCancellable</span>&gt;()
<span class="hljs-keyword">let</span> api = <span class="hljs-type">API</span>()

api.authenticateUser(userId: <span class="hljs-number">1234</span>)
    .flatMap { token <span class="hljs-keyword">in</span>
        api.getUserData(token: token)
    }
    .sink(receiveCompletion: { completion <span class="hljs-keyword">in</span>
        <span class="hljs-comment">// handle error if needed</span>
    }, receiveValue: { data <span class="hljs-keyword">in</span>
        <span class="hljs-comment">// handle user data</span>
    })
    .store(<span class="hljs-keyword">in</span>: &amp;cancellable)
</code></pre>
<h2 id="heading-combinelatest"><strong>combineLatest</strong></h2>
<p><code>combineLatest</code> is used to combine the latest values from multiple publishers into a single publisher. The resulting publisher emits a <strong>tuple</strong> of the latest values from each of the input publishers whenever any of the input publishers emits a new value.</p>
<p>A common use case for <code>combineLatest</code> is in user interface development, where you want to update the UI based on multiple values that are changing over time. For example, you might have two publishers, one that emits the user's current location and one that emits the user's current heading. You can use <code>combineLatest</code> to combine the latest values from these publishers and update the UI with the combined information.</p>
<p>Another use case for <code>combineLatest</code> is in data processing, where you want to process multiple streams of data in parallel. For example, you might have two publishers that emit data from different sensors, and you want to process the data in real time as it arrives. You can use <code>combineLatest</code> to combine the latest values from these publishers and process the data in a single stream.</p>
<p>Imagine that you have two text fields with first name and family name and you need to get a full name when the user changes one of them. It can be easily done with <code>combineLatest</code> :</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> firstNamePublisher = <span class="hljs-type">CurrentValueSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt;(<span class="hljs-string">"John"</span>)
<span class="hljs-keyword">let</span> familyNamePublisher = <span class="hljs-type">CurrentValueSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Never</span>&gt;(<span class="hljs-string">"Snow"</span>)

firstNamePublisher
    .combineLatest(familyNamePublisher)
    .sink { firstName, familyName <span class="hljs-keyword">in</span>
        <span class="hljs-built_in">print</span>(firstName, familyName) <span class="hljs-comment">// prints: John Snow</span>
    }
</code></pre>
<p><strong>TIP</strong>: combineLatest emits the tiple only when both of combined publishes emitted at least one value.</p>
<h2 id="heading-merge"><strong>merge</strong></h2>
<p>The <code>merge</code> operator is used to merge the values from multiple publishers into a single publisher, but it doesn’t emit a tuple, values are merged in the single flow.</p>
<p>A common use case for <code>merge</code> is when you might want to get data from multiple sources (API, WebSocket, cache, UI events) in parallel, and you don’t care which one will happen first, all you need is the event. It will not wait for events from both sources as <code>combineLatest</code> does. First comes - first received as a new value.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> apiPublisher = <span class="hljs-type">PassthroughSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Error</span>&gt;()
<span class="hljs-keyword">let</span> websocketPublisher = <span class="hljs-type">PassthroughSubject</span>&lt;<span class="hljs-type">String</span>, <span class="hljs-type">Error</span>&gt;()

apiPublisher
    .merge(with: websocketPublisher)
    .sink { completion <span class="hljs-keyword">in</span>
         <span class="hljs-comment">// handle the error if needed</span>
     } receiveValue: { value <span class="hljs-keyword">in</span>
         <span class="hljs-built_in">print</span>(<span class="hljs-string">"Merged value recieved:"</span>, value)
     }

apiPublisher.send(<span class="hljs-string">"New value"</span>)
<span class="hljs-comment">// Merged value recieved: New value</span>
websocketPublisher.send(<span class="hljs-string">"Another value"</span>)
<span class="hljs-comment">// Merged value recieved: Another value</span>
</code></pre>
<h2 id="heading-share"><strong>share</strong></h2>
<p><code>share</code> is a useful operator that enables you to share a single subscription to a publisher among multiple subscribers, which can help to reduce resource usage and improve the efficiency of your reactive programming system. For example, by using <code>share</code>, you can ensure that the network request is only decoded to the domain model once, even if multiple parts of your application need the data.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> request = api.makeRequest()
    .<span class="hljs-built_in">map</span> { data <span class="hljs-keyword">in</span>
        <span class="hljs-comment">// called only once with share</span>
        <span class="hljs-keyword">try</span>? <span class="hljs-type">JSONDecoder</span>().decode(<span class="hljs-type">MyDecodable</span>.<span class="hljs-keyword">self</span>, from: data) 
    }
    .share()

<span class="hljs-comment">// one place</span>
request
    .sink(...)

<span class="hljs-comment">// another place</span>
request
    .sink(...)
</code></pre>
<p><strong>TIP</strong>: Also note that <a target="_blank" href="https://developer.apple.com/documentation/combine/publishers/share"><code>Publishers.Share</code></a> is a class rather than a structure like most other publishers. This means you can use this operator to create a publisher instance that uses reference semantics.</p>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>In conclusion, Combine offers a robust collection of tools for implementing reactive programming in Swift and working with asynchronous events. However, a solid understanding of reactive programming is still necessary, and there is a steep learning curve to master Combine. It is important to use it responsibly and not blindly. Keep in mind the phrase, "With great power comes great responsibility.”</p>
<p>What's next? Although we have covered a lot of information about Combine in this article, if you want to take your understanding of it to the next level, I would recommend reading the <a target="_blank" href="https://www.kodeco.com/books/combine-asynchronous-programming-with-swift/v3.0">Kodeco book</a> for a deeper understanding.</p>
]]></content:encoded></item><item><title><![CDATA[How to create Bottom Sheet with UIKit]]></title><description><![CDATA[BottomSheet has been present in iOS applications for a while, with Apple first implementing it in the Maps app in iOS 10. However, it was not available for developers to use as a standard component, so if they wanted to include it in their apps, they...]]></description><link>https://vbat.dev/how-to-create-bottom-sheet-with-uikit</link><guid isPermaLink="true">https://vbat.dev/how-to-create-bottom-sheet-with-uikit</guid><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Swift]]></category><category><![CDATA[UIkit]]></category><dc:creator><![CDATA[Vitaly Batrakov]]></dc:creator><pubDate>Thu, 19 Jan 2023 01:09:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XJXWbfSo2f0/upload/9c0faaf22609f8c00178479b3639ee9e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>BottomSheet has been present in iOS applications for a while, with Apple first implementing it in the Maps app in iOS 10. However, it was not available for developers to use as a standard component, so if they wanted to include it in their apps, they had to create their implementation of it.</p>
<p>Apple provided an easy-to-use solution since iOS 15 with <a target="_blank" href="https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller">UISheetPresentationController</a>.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> vc = <span class="hljs-type">UIViewController</span>()
<span class="hljs-comment">// can also use .formSheet and result will be the same on the iPhone</span>
vc.modalPresentationStyle = .pageSheet
<span class="hljs-keyword">if</span>
    #available(iOS <span class="hljs-number">15.0</span>, *),
    <span class="hljs-keyword">let</span> sheet = vc.sheetPresentationController
{
    sheet.detents = [.medium(), .large()]
}
present(vc, animated: <span class="hljs-literal">true</span>, completion: <span class="hljs-literal">nil</span>)
</code></pre>
<p>But on iOS 15 UISheetPresentationController was able to show the bottom sheet only with 2 sizes (detents):</p>
<ul>
<li><p>large (almost full-screen height);</p>
</li>
<li><p>medium (approximately half of the screen height).</p>
</li>
</ul>
<p>Check <a target="_blank" href="https://sarunw.com/posts/bottom-sheet-in-ios-15-with-uisheetpresentationcontroller/">this post</a> for more information.</p>
<p>Only since the iOS 16 it became possible to create <a target="_blank" href="https://stuartbreckenridge.net/custom-detent-height-in-ios-16/">custom detents</a> with specific preferred heights.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> smallDetentId = <span class="hljs-type">UISheetPresentationController</span>.<span class="hljs-type">Detent</span>.<span class="hljs-type">Identifier</span>(<span class="hljs-string">"small"</span>)
<span class="hljs-keyword">let</span> smallDetent = <span class="hljs-type">UISheetPresentationController</span>.<span class="hljs-type">Detent</span>.custom(identifier: smallDetentId) { context <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">100</span>
}
sheetPresentationController?.detents = [smallDetent, .medium(), .large()]
</code></pre>
<p>Using custom detents has given developers more flexibility in implementing bottom sheets in their apps, but it is still not perfect. The <strong>pageSheet</strong> and <strong>formSheet</strong> styles on iPads look different than they do on iPhones (as modals), which can be an issue if you want your component to have the same appearance on all devices. Additionally, the size of the bottom sheet is not automatically determined by its content size, which would be more convenient. Today, let's explore how to implement a desired bottom sheet using UIKit.</p>
<p>(It would be interesting to implement it with SwiftUI as well, but most of the apps still use UIKit, so we will do it with SwiftUI in one of the next articles).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674097772571/d9498123-d45b-4813-b827-7a6a516f9888.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-quick-intro"><strong>Quick Intro</strong></h3>
<p>If you want to implement Bottom Sheet using custom transition, you need:</p>
<ol>
<li><p>Set its <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle">modalPresentationStyle</a> property to <code>custom</code>;</p>
</li>
<li><p>Assign an object that conforms to <code>UIViewControllerTransitioningDelegate</code> protocol to <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621421-transitioningdelegate">transitioningDelegate</a> property of UIViewController you want to present.</p>
</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> vc = <span class="hljs-type">ExampleViewController</span>()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = delegate <span class="hljs-comment">// UIViewControllerTransitioningDelegate</span>
present(vc, animated: <span class="hljs-literal">true</span>)
</code></pre>
<p><strong>UIViewControllerTransitioningDelegate</strong> is a protocol in UIKit that defines methods for supporting custom transitions between view controllers.</p>
<p><strong>TIP</strong>: The property <code>transitioningDelegate</code> of the controller is <strong>weak</strong>, so it is necessary to additionally retain the object with a strong reference.</p>
<p>To implement your transitioning delegate object, you will need to provide to UIKit with different objects related to the animation process, interaction and presentation, such as:</p>
<ol>
<li><p><strong>Transition animator object</strong> that conforms to the <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning">UIViewControllerAnimatedTransitioning</a> protocol and is responsible for performing the actual animation for the transition.</p>
<p> (<strong>TIP</strong>: You can even provide separate animator objects for presenting and dismissing the view controller).</p>
</li>
<li><p><strong>Interactive animator object</strong> that conforms to the <a target="_blank" href="https://developer.apple.com/documentation/uikit/uiviewcontrollerinteractivetransitioning">UIViewControllerInteractiveTransitioning</a> protocol <strong>optionally</strong> if you want to use touch input or other user interactions to control the timing of the animation.</p>
</li>
<li><p>The <a target="_blank" href="https://developer.apple.com/documentation/uikit/uipresentationcontroller">UIPresentationController</a> object is used to manage the presentation and dismissal process, including the position, size, and certain aspects of the presentation, such as displaying a shadow view during a custom transition. The presentation controller acts as a container for the presentation and its lifespan extends beyond the animator objects involved in the presentation, making it very useful in the transition process.</p>
</li>
</ol>
<h3 id="heading-lets-go-to-the-code">Let’s go to the code</h3>
<p>Let’s see how we can implement all objects described above.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BottomSheetTransitioningDelegate</span>: <span class="hljs-title">NSObject</span>, <span class="hljs-title">UIViewControllerTransitioningDelegate</span> </span>{

    <span class="hljs-keyword">let</span> transition: <span class="hljs-type">BottomSheetTransition</span>    

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animationController</span><span class="hljs-params">(
        forPresented presented: UIViewController,
        presenting: UIViewController,
        source: UIViewController
    )</span></span> -&gt; <span class="hljs-type">UIViewControllerAnimatedTransitioning?</span> {
        transition.isPresenting = <span class="hljs-literal">true</span>
        transition.wantsInteractiveStart = <span class="hljs-literal">false</span>
        <span class="hljs-keyword">return</span> transition
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animationController</span><span class="hljs-params">(forDismissed dismissed: UIViewController)</span></span> -&gt; <span class="hljs-type">UIViewControllerAnimatedTransitioning?</span> {
        transition.isPresenting = <span class="hljs-literal">false</span>
        <span class="hljs-keyword">return</span> transition
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">interactionControllerForDismissal</span><span class="hljs-params">(using animator: UIViewControllerAnimatedTransitioning)</span></span> -&gt; <span class="hljs-type">UIViewControllerInteractiveTransitioning?</span> {
        transition.isPresenting = <span class="hljs-literal">false</span>
        <span class="hljs-keyword">return</span> transition
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentationController</span><span class="hljs-params">(
        forPresented presented: UIViewController,
        presenting: UIViewController?,
        source: UIViewController
    )</span></span> -&gt; <span class="hljs-type">UIPresentationController?</span> {
        <span class="hljs-type">BottomSheetPresentationController</span>(presentedViewController: presented,
                                          presenting: presenting,
                                          configuration: configuration)
    }
}
</code></pre>
<p>As you can see from the code we provide <code>BottomSheetTransition</code> object named <strong>transition</strong> as a:</p>
<ol>
<li><p><strong>Transition animator object</strong> for presentation transition (<code>animationController(forPresented:</code>) and for dismissal transition (<code>animationController(forDismissed:</code>)</p>
</li>
<li><p><strong>Interactive animator object</strong> for interactive dismissal (with user’s swipe gesture). We implement only method <code>interactionControllerForDismissal</code> for providing interactive animator object for dismiss transition because we don’t need interactive presentation transition.</p>
</li>
</ol>
<p>This is how we implement <code>BottomSheetTransition</code>:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BottomSheetTransition</span>: <span class="hljs-title">UIPercentDrivenInteractiveTransition</span> </span>{
    <span class="hljs-comment">/// `true` if the transition is for the presentation.</span>
    <span class="hljs-comment">/// `false` if the transition is for the dismissal.</span>
    <span class="hljs-keyword">var</span> isPresenting = <span class="hljs-literal">true</span>

    <span class="hljs-comment">/// Set this to `false` in order to start an interruptible transition non</span>
    <span class="hljs-comment">/// interactively (for example when tap on overlay view)</span>
    <span class="hljs-keyword">var</span> wantsInteractiveStart: <span class="hljs-type">Bool</span>

    <span class="hljs-comment">/// The interactive animator used for the dismiss transition.</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> dismissAnimator: <span class="hljs-type">UIViewPropertyAnimator?</span>

    <span class="hljs-comment">/// The animator used for the presentation animation.</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> presentationAnimator: <span class="hljs-type">UIViewPropertyAnimator?</span>
}
</code></pre>
<p><code>UIPercentDrivenInteractiveTransition</code> is a class from UIKit that conforms to <code>UIViewControllerInteractiveTransitioning</code> and provides all the necessary functionality for an interactive transition. It is percent-driven, meaning that the progress of the transition can be accessed and tracked through the <code>percentComplete</code> property, which is expressed as a percentage.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UIPercentDrivenInteractiveTransition</span>: <span class="hljs-title">UIViewControllerInteractiveTransitioning</span> </span>{
     <span class="hljs-keyword">var</span> percentComplete: <span class="hljs-type">CGFloat</span> { <span class="hljs-keyword">get</span> }
}
</code></pre>
<p>On top of that <code>BottomSheetTransition</code> also implements <code>UIViewControllerAnimatedTransitioning</code> protocol:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">BottomSheetTransition</span>: <span class="hljs-title">UIViewControllerAnimatedTransitioning</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">transitionDuration</span><span class="hljs-params">(using <span class="hljs-number">_</span>: UIViewControllerContextTransitioning?)</span></span> -&gt; <span class="hljs-type">TimeInterval</span> {
        animationDuration
    }

    <span class="hljs-comment">/// Will get called when the transition is not interactive </span>
    <span class="hljs-comment">/// to animate presenting or dismissing of the controller.</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animateTransition</span><span class="hljs-params">(
        using transitionContext: UIViewControllerContextTransitioning
    )</span></span> {
        interruptibleAnimator(using: transitionContext).startAnimation()
    }

    <span class="hljs-comment">/// Will get called when the transition is interactive.</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">interruptibleAnimator</span><span class="hljs-params">(
        using transitionContext: UIViewControllerContextTransitioning
    )</span></span> -&gt; <span class="hljs-type">UIViewImplicitlyAnimating</span> {
        <span class="hljs-keyword">if</span> isPresenting {
            <span class="hljs-keyword">return</span> presentationAnimator(using: transitionContext)
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> dismissAnimator(using: transitionContext)
        }
    }
}
</code></pre>
<p>As you can see <code>UIViewControllerAnimatedTransitioning</code> methods provide <code>animationDuration</code> and implement actual transition animations in <code>animateTransition(using transitionContext:)</code>and <code>interruptibleAnimator(using transitionContext:)</code>.</p>
<h3 id="heading-uipresentationcontroller">UIPresentationController</h3>
<p>Another important object we need to provide in <code>BottomSheetTransitioningDelegate</code> is <code>UIPresentationController</code>. Our presentation controller will handle several tasks:</p>
<ol>
<li><p>Provide the size of the presented view depending on its content size;</p>
</li>
<li><p>Decorate the appearance of the presented view controller (by adding the overlay, pull bar, etc.);</p>
</li>
<li><p>Add a swipe gesture recognizer and use it for interactive dismissal.</p>
</li>
</ol>
<p>Next, let’s find out how to implement them.</p>
<h3 id="heading-provide-the-size-of-the-presented-view">Provide the size of the presented view</h3>
<p>To provide the size of the bottom sheet, you need to override the <code>frameOfPresentedViewInContainerView</code> property.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BottomSheetPresentationController</span>: <span class="hljs-title">UIPresentationController</span> </span>{

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">var</span> frameOfPresentedViewInContainerView: <span class="hljs-type">CGRect</span> {
        <span class="hljs-comment">// calculate presented view size here</span>
    }

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>The <code>systemLayoutSizeFitting</code> method of UIView can be used to determine the optimal size that best fits the constraints and closely aligns with the target size.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-comment">/// The size fitting most closely to targetSize in which the receiver's subtree</span>
    <span class="hljs-comment">/// can be laid out while optimally satisfying the constraints. </span>
    <span class="hljs-meta">@available</span>(iOS <span class="hljs-number">8.0</span>, *)
    <span class="hljs-keyword">open</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">systemLayoutSizeFitting</span><span class="hljs-params">(
                targetSize: CGSize, 
                horizontalFittingPriority: UILayoutPriority, 
                verticalFittingPriority: UILayoutPriority
        )</span></span> -&gt; <span class="hljs-type">CGSize</span>
}
</code></pre>
<p>This code we will use inside <code>frameOfPresentedViewInContainerView</code> property computation:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> fittingSize = <span class="hljs-type">CGSize</span>(width: containerView.bounds.width,
                         height: <span class="hljs-type">UIView</span>.layoutFittingCompressedSize.height)

<span class="hljs-keyword">let</span> presentedViewHeight = presentedView.systemLayoutSizeFitting(
     fittingSize,
     withHorizontalFittingPriority: .<span class="hljs-keyword">required</span>,
     verticalFittingPriority: .fittingSizeLevel
 ).height
</code></pre>
<p>We create <code>fittingSize</code> with <code>layoutFittingCompressedSize.height</code> and set <code>verticalFittingPriority</code> parameter to <code>fittingSizeLevel</code> to ensure that we will get the smallest possible size and aligns as closely as possible to satisfy the constraints.</p>
<h3 id="heading-decorate-presented-view-controller">Decorate presented view controller</h3>
<p>To decorate our bottom sheet we will override the presentation controller’s methods:</p>
<p><code>presentationTransitionWillBegin</code> - notifies the presentation controller that the presentation animations are about to start;</p>
<p><code>dismissalTransitionWillBegin</code> - notifies the presentation controller that the dismissal animations are about to start;</p>
<p><code>containerViewDidLayoutSubviews</code> - notifies the presentation controller that layout ended on the views of the container view. Here we will layout added subviews.</p>
<pre><code class="lang-swift">    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentationTransitionWillBegin</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.presentationTransitionWillBegin()

        containerView?.addSubview(overlayView)
        presentedView?.addSubview(pullBarView)

        overlayView.alpha = <span class="hljs-number">0</span>

        presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> <span class="hljs-keyword">self</span> = <span class="hljs-keyword">self</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
            <span class="hljs-keyword">self</span>.presentedView?.layer.cornerRadius = <span class="hljs-keyword">self</span>.cornerRadius
            <span class="hljs-keyword">self</span>.overlayView.alpha = <span class="hljs-number">1</span>
        })
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">dismissalTransitionWillBegin</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.dismissalTransitionWillBegin()

        presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> <span class="hljs-keyword">self</span> = <span class="hljs-keyword">self</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
            <span class="hljs-keyword">self</span>.presentedView?.layer.cornerRadius = .zero
            <span class="hljs-keyword">self</span>.overlayView.alpha = <span class="hljs-number">0</span>
        })
    }
</code></pre>
<p>As previously mentioned, the UIPresentationController acts as a container for the transition and is the ideal candidate to decorate the presented controller with an overlay and pull bar. The <code>containerView</code> and <code>presentedView</code> properties of UIPresentationController were used to access the views involved in the transition that needed to be decorated.</p>
<h3 id="heading-interactive-dismiss-with-a-pan-gesture">Interactive dismiss with a pan gesture</h3>
<p>The final task in the presentation controller is to handle user swipes to enable interactive dismissal. Let's examine the code for this:</p>
<pre><code class="lang-swift">    <span class="hljs-comment">// The pan gesture used to drag and interactively dismiss the sheet.</span>
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> panGesture = <span class="hljs-type">UIPanGestureRecognizer</span>(target: <span class="hljs-keyword">self</span>,
                                                         action: #selector(pannedPresentedView))

    <span class="hljs-meta">@objc</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">pannedPresentedView</span><span class="hljs-params">(<span class="hljs-number">_</span> recognizer: UIPanGestureRecognizer)</span></span> {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> presentedView = presentedView <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span>
        }
        <span class="hljs-keyword">switch</span> recognizer.state {
        <span class="hljs-keyword">case</span> .began:
            <span class="hljs-comment">// Start to dismiss interactively when user begins to swipe</span>
            dismiss(interactively: <span class="hljs-literal">true</span>)

        <span class="hljs-keyword">case</span> .changed:
            <span class="hljs-comment">// Update the dismiss tansition progress</span>
            <span class="hljs-keyword">let</span> translation = recognizer.translation(<span class="hljs-keyword">in</span>: presentedView)
            updateTransitionProgress(<span class="hljs-keyword">for</span>: translation)

        <span class="hljs-keyword">case</span> .ended, .cancelled, .failed:
            <span class="hljs-comment">// Finish or cancel the dimissal depending on current progress of dismiss transition</span>
            handleEndedInteraction()

        <span class="hljs-keyword">case</span> .possible:
            <span class="hljs-keyword">break</span>

        @unknown <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">break</span>
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">handleEndedInteraction</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> transitioningDelegate = transitioningDelegate <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span>
        }
        <span class="hljs-keyword">if</span> transitioningDelegate.transition.dismissFractionComplete &gt; dismissThreshold {
            transitioningDelegate.transition.finish()
        } <span class="hljs-keyword">else</span> {
            transitioningDelegate.transition.cancel()
        }
    }
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In conclusion, I would like to highlight that this is not a perfect solution as it does not support scrolling of content if the presented view controller is larger than the screen height. Additionally, navigation controllers inside the bottom sheet are not supported. However, it is still a simple and clean option if you need to add a bottom sheet to your project.</p>
<p>You can find all code <a target="_blank" href="https://github.com/vitalybatrakov/BottomSheet">here</a>.</p>
]]></content:encoded></item></channel></rss>