<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Yury Lebedev on Medium]]></title>
        <description><![CDATA[Stories by Yury Lebedev on Medium]]></description>
        <link>https://medium.com/@quasaryy?source=rss-2d4b2b0b78c7------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*m3XnLkV2AGyVicKbftCLjg.jpeg</url>
            <title>Stories by Yury Lebedev on Medium</title>
            <link>https://medium.com/@quasaryy?source=rss-2d4b2b0b78c7------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 20 Apr 2026 18:13:33 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@quasaryy/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Threads, Queues, and Deadlocks: A Developer’s Guide to Swift Concurrency]]></title>
            <link>https://quasaryy.medium.com/threads-queues-and-deadlocks-a-developers-guide-to-swift-concurrency-6f021a1bbb72?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/6f021a1bbb72</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[multithreading]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Tue, 26 Nov 2024 17:10:20 GMT</pubDate>
            <atom:updated>2024-11-26T17:10:20.468Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*un6FR30QKC3C9XpOf3eFIA.jpeg" /></figure><h3>1. Anything Can Happen</h3><p>One evening, while catching up with a friend at a café, we touched on the topic of multithreading in iOS. Without going into too much detail, we found ourselves diving deep into the technical weeds. At some point, my brain decided it needed a break, and I began explaining how threads manage queues (or so I thought, quite convincingly 😊). Later, I thought to myself, “Who hasn’t been there?” If this made you smile, it probably means you’re already comfortable with GCD. But if you’re wondering what’s so funny about it, this article will explain it all in simple terms.</p><h4>What This Article Covers</h4><p>We won’t dive into basic definitions like “What are threads?” or “How does GCD work?” Instead, we’ll focus on nuanced topics that often lead to confusion:</p><ul><li>The difference between threads and queues.</li><li>How sync, async, serial, and concurrent work.</li><li>Why deadlocks happen and how to avoid them.</li><li>Best practices for using the main and global queues.</li></ul><h4>Who Should Read This Article</h4><ul><li>Developers who already know what multithreading is but face challenges when implementing it.</li><li>Those who want to better understand how threads and queues work in Swift to write safer and more efficient code.</li></ul><h4>Problems We’ll Address</h4><ol><li>Why understanding the difference between threads and queues is essential.</li><li>How to avoid deadlocks when working with DispatchQueue.</li><li>When to use sync and async and how they differ from serial and concurrent.</li><li>Why UI updates must always occur on the main queue.</li></ol><h3>2. What Came First: The Chicken or the Egg? (Threads and Queues)</h3><p>Multithreading in Swift isn’t just about threads; it’s also about queues that help manage tasks. To truly understand this topic, you need to grasp how threads and queues interact — and which one takes the lead.</p><h4>Threads: What Are They?</h4><ul><li>A thread is a low-level entity provided by the operating system. Threads execute tasks, but managing them directly is complex and resource-intensive.</li><li>In iOS, the operating system assigns threads to processes to ensure tasks are executed as efficiently as possible.</li></ul><h4>Queues: Why Do We Need Them?</h4><ul><li>A queue is a high-level tool provided by the Grand Central Dispatch (GCD) library. Its primary role is to manage tasks that will eventually run on threads.</li><li>The queue’s main responsibility is to organize tasks so they execute in the correct order and on the appropriate thread.</li></ul><h4>How Do They Work Together?</h4><ul><li>Threads are the workforce.</li><li>Queues are the managers that assign tasks to the threads.</li></ul><h4>Analogy: A Restaurant</h4><p>Imagine a restaurant:</p><ul><li><strong>Chefs (threads)</strong> cook the dishes.</li><li><strong>Waitstaff (queues)</strong> take orders and assign them to chefs.</li><li>The waitstaff (queue) doesn’t care which chef (thread) completes a task, as long as the dish (task) gets prepared.</li></ul><h4>Who Manages Whom?</h4><ul><li>Developers add tasks to queues.</li><li>Queues distribute tasks to a pool of threads, and the operating system decides which thread will execute the task.</li><li>In summary:</li><li>— Developers work with queues.</li><li>— Queues manage tasks and threads.</li></ul><h4>GCD: Managing Threads for Us</h4><p>Grand Central Dispatch (GCD) is a tool that:</p><ul><li>Provides preconfigured queues for task execution.</li><li>Relieves developers from manual thread management.</li><li>Optimizes task distribution for maximum performance.</li></ul><h4>Key Takeaway</h4><p>Developers always work with queues, not directly with threads. Queues are the interface for creating multitasking, while threads are the underlying mechanism that implements it behind the scenes.</p><h3>3. Main Queue and Main Thread</h3><p>In Swift, you’ll often hear phrases like “Switch to the main thread” or “Switch to the main queue.” If you’re new to multithreading, such expressions can be confusing. Let’s break them down and explain why the “main queue” and the “main thread” are essentially the same thing.</p><h4>What Is the Main Thread?</h4><ul><li><strong>Main Thread: </strong>- The single thread in an application responsible for:</li><li>— UI rendering: Displaying and updating the user interface.</li><li>— User interactions: Handling gestures, taps, and scrolling.</li><li>It’s a system-managed thread that always exists in iOS applications.</li></ul><h4>What Is the Main Queue?</h4><ul><li><strong>Main Queue (</strong><strong>DispatchQueue.main)</strong>:</li><li>— A serial queue that executes tasks one at a time in sequence.</li><li>— Always runs tasks on the main thread.</li><li>— Used to execute all tasks that interact with the UI.</li></ul><h4>Why Are the Main Queue and the Main Thread Essentially the Same?</h4><ul><li><strong>Queues manage tasks; threads execute them.</strong></li><li>— The main queue is the mechanism that schedules tasks to be run on the main thread.</li><li>— Therefore, “switch to the main thread” means “add a task to the main queue.”</li><li><strong>Simple rule:</strong></li><li>— If a task is executed on the DispatchQueue.main, it’s running on the main thread.</li><li>— All UI-related work must go through the main queue to avoid conflicts and errors.</li></ul><h4>Why Is the Main Queue Always Serial?</h4><ul><li>The main queue processes tasks one at a time to:</li><li>— Maintain execution order.</li><li>— Ensure UI stability.</li><li>— Prevent multiple tasks from attempting to modify the UI simultaneously.</li></ul><h4>Why Is This Important?</h4><ul><li>If the main queue is “blocked” by lengthy tasks, the app will stop responding to user actions.</li><li>For example, performing heavy computations on the main queue will freeze the UI because the queue cannot process new tasks.</li></ul><h4>Analogy: Main Queue and Main Thread</h4><p>Imagine the main thread is the <strong>head chef</strong> who prepares only signature dishes (UI updates), while the main queue is the <strong>order list</strong> handed to the chef. The chef processes one order at a time to maintain quality. Other <strong>chefs</strong> (background threads) may try to handle these signature dishes, but their performance won’t meet the same standard, leaving customers unsatisfied.</p><h4>Example: Using the Main Queue</h4><pre>// Execute a task on the main queue<br>DispatchQueue.main.async {<br>    // Update the UI<br>}</pre><h4>What Happens If You Call sync on the Main Queue?</h4><p>Calling DispatchQueue.main.sync from a task already executing on the main queue will cause a <strong>deadlock</strong>. Let’s explore why this happens and why the compiler doesn’t prevent it.</p><h4>Why Does a Deadlock Occur?</h4><ul><li><strong>The Main Queue Is Serialized (</strong><strong>serial)</strong>:</li><li>— It executes tasks one at a time in the order they are added.</li><li>— A new task cannot start until the current task finishes.</li><li><strong>Synchronous Execution (</strong><strong>sync)</strong>:</li><li>— The sync method adds a task to the queue and blocks the current thread until the task completes.</li><li>— However, on the main queue, the new task cannot start because the queue is already occupied by the current task.</li><li><strong>The Result:</strong></li><li>— The current task waits for the new task to complete.</li><li>— The new task cannot start because the queue is still processing the current task.</li><li>— This results in a mutual wait — a deadlock.</li></ul><p>Example:</p><pre>DispatchQueue.main.sync {<br>    print(&quot;Deadlock&quot;)<br>}</pre><h4>Why Doesn’t the Compiler Prevent This?</h4><ol><li><strong>The Compiler Doesn’t Know Which Queue Is Active:</strong></li></ol><ul><li>When you call DispatchQueue.main.sync, the compiler only sees a method call. It doesn’t know whether the code is running on the main queue or another thread.</li><li>For instance, if you call DispatchQueue.main.sync from a background thread, no deadlock occurs:</li></ul><pre>DispatchQueue.global().async {<br>    DispatchQueue.main.sync {<br>        print(&quot;This works fine&quot;)<br>    }<br>}</pre><p><strong>2. Flexibility of GCD:</strong></p><ul><li>GCD (Grand Central Dispatch) is a versatile tool for managing queues and threads. It doesn’t impose restrictions, allowing developers to use sync and async in various scenarios.</li><li>This flexibility gives developers control but requires an understanding of potential risks.</li></ul><p><strong>3. Developer Responsibility:</strong></p><ul><li>The compiler cannot analyze the program’s logical flow at runtime, where the deadlock actually occurs.</li><li>For example, using global queues with sync is perfectly valid:</li></ul><pre>let globalQueue = DispatchQueue.global()<br>globalQueue.sync {<br>    print(&quot;Executes without issues&quot;)<br>}</pre><h4>Why Can’t DispatchQueue.main.sync Be Automatically Prohibited?</h4><ol><li><strong>Some Scenarios Require </strong><strong>sync:</strong></li></ol><ul><li>For example, executing a task on the main queue and waiting for its completion before continuing:</li></ul><pre>DispatchQueue.global().async {<br>    DispatchQueue.main.sync {<br>        print(&quot;Task executed on the main queue&quot;)<br>    }<br>    print(&quot;Returning to the background thread&quot;)<br>}</pre><ul><li>Here, sync is used safely and serves its purpose.</li></ul><p><strong>2. Warnings Instead of Restrictions:</strong></p><ul><li>While the compiler doesn’t block DispatchQueue.main.sync, tools like SwiftLint can provide warnings to help avoid such mistakes.</li></ul><h4>How to Avoid Deadlocks?</h4><ol><li><strong>Don’t Call </strong><strong>DispatchQueue.main.sync from Code Already Running on the Main Queue.</strong></li></ol><ul><li>If you’re unsure which queue the code is running on, check it:</li></ul><pre>if Thread.isMainThread {<br>    print(&quot;On the main queue. Using async for safety.&quot;)<br>    DispatchQueue.main.async {<br>        print(&quot;Task executed on the main queue.&quot;)<br>    }<br>} else {<br>    DispatchQueue.main.sync {<br>        print(&quot;Switched to the main queue.&quot;)<br>    }<br>}</pre><p><strong>2. Use </strong><strong>async Whenever Possible for Tasks That Can Be Deferred.</strong></p><ul><li>Example:</li></ul><pre>DispatchQueue.main.async {<br>    print(&quot;Task executed safely&quot;)<br>}</pre><p><strong>3. Minimize the Use of </strong><strong>sync, Especially on the Main Queue.</strong></p><ul><li>This not only reduces the risk of deadlocks but also keeps the UI responsive.</li></ul><h4>Key Idea</h4><p>The compiler allows DispatchQueue.main.sync because:</p><ul><li>It’s a powerful and flexible tool that can be used in various scenarios.</li><li>The responsibility for correct usage lies with the developer.</li></ul><p>If you call sync on the main queue while already on it, a deadlock occurs because the main queue is busy with the current task, and sync requires the immediate execution of a new task. To avoid this, always check the current queue and use async if possible.</p><p>Finally, remember: even if the main queue appears idle, it’s never truly “empty” if you’re executing a task on it — this task is already occupying the queue.</p><h3>4. The Global Queue and How It Differs from the Main Queue</h3><p>In Swift multithreading, global queues (DispatchQueue.global) are a key tool for performing background tasks. They differ from the main queue in both structure and purpose. Let’s explore what makes them unique and how to use them correctly.</p><h4>What Is a Global Queue?</h4><ul><li>A global queue is a <strong>concurrent</strong> queue provided by GCD.</li><li>It allows multiple tasks to run simultaneously, distributing them across threads in a shared thread pool.</li><li>Unlike the main queue, the global queue is not tied to any specific thread.</li></ul><h4>Characteristics of Global Queues</h4><ol><li><strong>Concurrent:</strong></li></ol><ul><li>Multiple tasks can start at the same time.</li><li>The order in which tasks complete is not guaranteed.</li><li>This makes global queues ideal for performing background tasks.</li></ul><p><strong>2. Priority Levels (QoS — Quality of Service):</strong></p><p>Global queues are divided into priority levels that determine how tasks are handled by the system:</p><ul><li>.userInteractive: The highest priority for tasks that must finish immediately and involve user interaction (e.g., animations).</li><li>.userInitiated: High priority for user-initiated tasks (e.g., loading content).</li><li>.default: Medium priority.</li><li>.utility: Low priority for background tasks (e.g., data processing).</li><li>.background: The lowest priority for tasks that don’t affect the user experience (e.g., log processing).</li></ul><h4>Differences from the Main Queue</h4><ol><li><strong>Main Queue:</strong></li></ol><ul><li><strong>Serial</strong> (tasks are executed one at a time).</li><li>Executes tasks strictly in order.</li><li>Used for UI-related tasks.</li><li>Bound to the main thread.</li></ul><p><strong>2. Global Queue:</strong></p><ul><li><strong>Concurrent</strong> (multiple tasks can run simultaneously).</li><li>Executes tasks in parallel.</li><li>Used for background tasks.</li><li>Operates on a thread pool managed by the system.</li></ul><h4>When to Use Global Queues?</h4><ul><li><strong>For Background Tasks That Don’t Depend on the UI:</strong></li><li>— Data loading.</li><li>— Image processing.</li><li>— File operations.</li><li><strong>When You Need a Balance Between Performance and Resources:</strong></li><li>— Global queues distribute tasks across threads, avoiding blocking the main thread.</li></ul><h4>Analogy: Main Queue vs. Global Queue</h4><ul><li>Imagine the main queue as a <strong>head chef</strong> preparing signature dishes (UI operations) with a single workstation.</li><li>The global queue is a <strong>team of chefs</strong>, each working on a different task simultaneously, with no limit on workstations.</li></ul><h4>Example of Using Global Queues</h4><ol><li><strong>Asynchronous Task Execution:</strong></li></ol><pre>DispatchQueue.global(qos: .background).async {<br>    // Perform a background task<br>    let result = computeHeavyTask()<br>    DispatchQueue.main.async {<br>        // Update the UI on the main thread<br>        self.label.text = &quot;Result: \(result)&quot;<br>    }<br>}</pre><p><strong>2. Multiple Tasks Running Simultaneously:</strong></p><pre>DispatchQueue.global().async {<br>    print(&quot;Task 1 started&quot;)<br>    sleep(2)<br>    print(&quot;Task 1 finished&quot;)<br>}<br><br>DispatchQueue.global().async {<br>    print(&quot;Task 2 started&quot;)<br>    sleep(1)<br>    print(&quot;Task 2 finished&quot;)<br>}</pre><p><strong>Outcome:</strong> Tasks can run concurrently, and their completion order is not guaranteed.</p><h4>How to Avoid Mistakes with Global Queues</h4><ol><li><strong>Don’t Block a Global Queue:</strong></li></ol><ul><li>Avoid calling sync within a task running on a global queue. This can lead to a deadlock, just as with the main queue (discussed in detail earlier).</li></ul><pre>DispatchQueue.global().sync {<br>    DispatchQueue.global().sync {<br>        print(&quot;Deadlock!&quot;)<br>    }<br>}</pre><p><strong>2. Use the Right Priority (QoS):</strong></p><ul><li>High-priority tasks for the user should use .userInteractive or .userInitiated.</li><li>Long-running background tasks should use .utility or .background.</li></ul><h4>Key Takeaway</h4><p>The main queue and global queues are tools designed for different purposes. The main queue is used for UI tasks and operates serially, while global queues are designed for background tasks, allowing for parallel execution and efficient workload distribution.</p><h3>5. The Differences Between sync/async and serial/concurrent</h3><p>Developers often confuse these concepts, especially when working with multithreading in Swift. Let’s break down how they differ and how they interact with each other.</p><h4>sync and async: How Tasks Are Executed</h4><p>These terms define what happens to the <strong>current thread</strong> after a task is dispatched to a queue.</p><ol><li><strong>sync (Synchronous):</strong></li></ol><ul><li>Blocks the current thread until the task is completed.</li><li>Useful when you need to wait for the result before proceeding.</li></ul><p><strong>2. </strong><strong>async (Asynchronous):</strong></p><ul><li>Does not block the current thread.</li><li>The task is dispatched to the queue and executed in the background, while the current thread continues running.</li></ul><p><strong>Example:</strong></p><pre>let queue = DispatchQueue.global()<br><br>// Synchronous: blocks the current thread<br>queue.sync {<br>    print(&quot;Synchronous task completed&quot;)<br>}<br><br>// Asynchronous: the current thread continues immediately<br>queue.async {<br>    print(&quot;Asynchronous task completed&quot;)<br>}<br>print(&quot;This code runs before async is finished&quot;)</pre><h4>serial and concurrent: Queue Behavior</h4><p>These terms define how <strong>the queue processes tasks</strong>.</p><ol><li><strong>serial (Serial):</strong></li></ol><ul><li>Tasks are executed one at a time, in the order they were added.</li><li>Guarantees sequential execution.</li></ul><p><strong>2. </strong><strong>concurrent (Concurrent):</strong></p><ul><li>Multiple tasks can start running at the same time.</li><li>Task completion order is not guaranteed.</li></ul><p><strong>Example:</strong></p><pre>let serialQueue = DispatchQueue(label: &quot;com.example.serial&quot;)<br>let concurrentQueue = DispatchQueue(label: &quot;com.example.concurrent&quot;, attributes: .concurrent)<br><br>// Serial: tasks are executed one after another<br>serialQueue.async {<br>    print(&quot;Serial: Task 1&quot;)<br>}<br>serialQueue.async {<br>    print(&quot;Serial: Task 2&quot;)<br>}<br><br>// Concurrent: tasks can run simultaneously<br>concurrentQueue.async {<br>    print(&quot;Concurrent: Task 1&quot;)<br>}<br>concurrentQueue.async {<br>    print(&quot;Concurrent: Task 2&quot;)<br>}</pre><h4>How sync and async combine with serial and concurrent</h4><p>These concepts are independent but are often used together. Here’s what happens with different combinations:</p><ol><li><strong>sync + </strong><strong>serial:</strong></li></ol><ul><li>Tasks are executed one at a time, and the current thread waits for each to finish and remains blocked until completion.</li></ul><pre>let serialQueue = DispatchQueue(label: &quot;com.example.serial&quot;)<br>serialQueue.sync {<br>    print(&quot;Task 1&quot;)<br>}<br>serialQueue.sync {<br>    print(&quot;Task 2&quot;)<br>}</pre><p><strong>2. </strong><strong>async + </strong><strong>serial:</strong></p><ul><li>Tasks are executed one at a time, but the current thread is not blocked.</li></ul><pre>let serialQueue = DispatchQueue(label: &quot;com.example.serial&quot;)<br>serialQueue.async {<br>    print(&quot;Task 1&quot;)<br>}<br>serialQueue.async {<br>    print(&quot;Task 2&quot;)<br>}</pre><p><strong>3. </strong><strong>sync + </strong><strong>concurrent:</strong></p><ul><li>Tasks can start simultaneously, but the current thread waits for each task to finish and remains blocked until completion.</li></ul><pre>let concurrentQueue = DispatchQueue(label: &quot;com.example.concurrent&quot;, attributes: .concurrent)<br>concurrentQueue.sync {<br>    print(&quot;Task 1&quot;)<br>}<br>concurrentQueue.sync {<br>    print(&quot;Task 2&quot;)<br>}</pre><p><strong>4. </strong><strong>async + </strong><strong>concurrent:</strong></p><ul><li>Tasks are executed simultaneously, and the current thread continues running.</li></ul><pre>let concurrentQueue = DispatchQueue(label: &quot;com.example.concurrent&quot;, attributes: .concurrent)<br>concurrentQueue.async {<br>    print(&quot;Task 1&quot;)<br>}<br>concurrentQueue.async {<br>    print(&quot;Task 2&quot;)<br>}</pre><h4>Common Mistakes</h4><ol><li><strong>Deadlock When Using </strong><strong>sync on the Same Queue:</strong></li></ol><pre>let serialQueue = DispatchQueue(label: &quot;com.example.serial&quot;)<br>serialQueue.async {<br>    serialQueue.sync {<br>        print(&quot;Deadlock!&quot;)<br>    }<br>}</pre><p><strong>2. Improper Task Waiting:</strong></p><ul><li>Using sync on the main queue:</li></ul><pre>DispatchQueue.main.sync {<br>    print(&quot;Deadlock on the main queue&quot;)<br>}</pre><h4>Analogy: How They’re Connected</h4><ul><li>sync and async are like deciding whether to <strong>wait for your turn</strong> or <strong>place your order and leave</strong>.</li><li>serial and concurrent are like deciding whether to <strong>handle orders one by one</strong> or <strong>process multiple orders at once</strong>.</li></ul><h4>Key Takeaway</h4><ul><li>sync and async define how the current thread interacts with the queue.</li><li>serial and concurrent define how tasks are executed within the queue.</li><li>While these concepts interact, it’s important to understand their independence.</li></ul><h3>Conclusion</h3><p>Understanding concurrency and multithreading in Swift is essential for building efficient, responsive, and stable applications. This guide has aimed to break down the complexities of threads, queues, and task management into digestible concepts supported by practical examples.</p><h4>Key Takeaways</h4><ul><li><strong>Threads vs. Queues:</strong> Queues are the high-level tools developers use to manage tasks, while threads execute them behind the scenes.</li><li><strong>Main vs. Global Queues:</strong> Use the main queue for UI updates and global queues for background tasks, each tailored to specific priorities and workloads.</li><li><strong>sync vs. async / serial vs. concurrent:</strong> Recognize how these concepts control task execution and affect performance, ensuring you use them correctly in your workflows.</li><li><strong>Avoid Deadlocks:</strong> Understanding how to avoid them is critical for smooth operation.</li></ul><h4>Final Thought</h4><p>Concurrency is not just a technical necessity but a skill that unlocks your app’s full potential. With careful application of these concepts and a mindset geared toward problem-solving, you can navigate the challenges of multithreading with confidence.</p><p>If you’re ready to dive deeper into advanced patterns or explore new concurrency tools in Swift, let this guide be your foundation. Start experimenting, stay curious, and embrace the power of efficient multitasking in your apps.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6f021a1bbb72" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Customize Your File Header in Xcode: Step-by-Step Guide]]></title>
            <link>https://quasaryy.medium.com/customize-your-file-header-in-xcode-step-by-step-guide-b6a1f6514f70?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/b6a1f6514f70</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Fri, 16 Aug 2024 13:36:20 GMT</pubDate>
            <atom:updated>2024-08-16T13:36:20.216Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-30FnoLfe6t5EXQhodxv_w.jpeg" /></figure><p>This guide will walk you through the simple process of creating custom headers for your .swift files in Xcode. The process is straightforward, so this will be a quick read!</p><h3>Step 1: Create a IDETemplateMacros.plist File</h3><p>First, you’ll need to create an empty file named IDETemplateMacros.plist in a location that’s convenient for you. You can do this using Terminal or directly in Finder with your favorite text editor — whatever works best for you. If you’re creating the file in a text editor, like the default TextEdit, make sure the document format is set to Plain Text.</p><h3>Step 2: Add the Header Template</h3><p>Next, open the IDETemplateMacros.plist file in your preferred editor and paste in the following content:</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br>&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;<br>&lt;plist version=&quot;1.0&quot;&gt;<br>&lt;dict&gt;<br>    &lt;key&gt;FILEHEADER&lt;/key&gt;<br>    &lt;string&gt; ___FILENAME___<br>// ___PROJECTNAME___<br>//<br>// Created by ___FULLUSERNAME___ on ___DATE___.<br>// Copyright © ___YEAR___ ___FULLUSERNAME___. All rights reserved.<br>//<br>// Any other text&lt;/string&gt;<br>&lt;/dict&gt;<br>&lt;/plist&gt;</pre><h3>3. Pay Attention to Formatting</h3><p>Notice the space after &lt;string&gt; before the start of the template — this is intentional. If this space is omitted, the formatting will be incorrect. The header will still work, but it may look off, such as having //SomeClass.swift instead of // SomeClass.swift. Also, note that the first line after &lt;string&gt; should not include the double slashes (//). If you add them, the final file will start with //// SomeClass.swift.</p><h3>4. Explore Available Macros</h3><p>You can find the full list of available macros here: <a href="https://help.apple.com/xcode/mac/current/#/dev7fe737ce0">Apple’s Xcode Help Documentation</a>.</p><h3>5. Determine Where to Use the Custom Header</h3><p>The next step is to decide where you’d like to use this custom header. Depending on your choice, you’ll need to copy the IDETemplateMacros.plist file to the appropriate directory. Here are the options:</p><ul><li><strong>Globally for Xcode:</strong> ~/Library/Developer/Xcode/UserData/</li><li><strong>Workspace — shared by all users:</strong>&lt;WorkspaceName&gt;.xcworkspace/xcshareddata/</li><li><strong>Workspace — single user:</strong>&lt;WorkspaceName&gt;.xcworkspace/xcuserdata/[username].xcuserdatad/</li><li><strong>Project — shared by all users:</strong> &lt;ProjectName&gt;.xcodeproj/xcshareddata/</li><li><strong>Project — single user:</strong>&lt;ProjectName&gt;.xcodeproj/xcuserdata/[username].xcuserdatad/</li></ul><h3>6. Use Multiple IDETemplateMacros.plist Files</h3><p>It’s important to note that you can use multiple IDETemplateMacros.plist files simultaneously. For example, you can have one global file in:</p><pre>~/Library/Developer/Xcode/UserData/</pre><p>And another one, say, in:</p><pre>&lt;ProjectName&gt;.xcodeproj/xcuserdata/[username].xcuserdatad/</pre><p>This project-specific file will override the global one.</p><h3>This is how it looks in reality 😀</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PG5jIqTIWqKxDV1DENLTlQ.gif" /><figcaption>Part 1</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FffW6WxjDx_TadLqXuuAZg.gif" /><figcaption>Part 2</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b6a1f6514f70" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SPM in Practice: Creating and Publishing Your Libraries]]></title>
            <link>https://quasaryy.medium.com/spm-in-practice-creating-and-publishing-your-libraries-768b2f55a4d0?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/768b2f55a4d0</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[spm]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Tue, 06 Aug 2024 17:15:40 GMT</pubDate>
            <atom:updated>2024-08-06T17:15:40.834Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LO8TzrDenheElPtvi43Stw.jpeg" /><figcaption>Swift Package Manager (SPM)</figcaption></figure><p>Swift Package Manager (SPM) is the official dependency management tool for the Swift ecosystem, developed by Apple. It allows developers to automate the process of adding, configuring, and managing libraries and frameworks, significantly simplifying project development in Swift. SPM is integrated directly into the Swift compiler and Xcode, providing smooth integration and compatibility with existing development tools.</p><h3>Why Create Your Own SPM Dependencies?</h3><p>You’ve likely encountered a situation where, in one of your projects, you wrote your own manager, for example, for networking or logging. It turned out to be so good and complex that rewriting it for each new project becomes a tedious task. Of course, you can copy and paste files and code into the new project, but this is far from the best approach. Instead, by spending a little time creating your own library, you can connect it to any new projects in just a few minutes.</p><ol><li><strong>Improving Modularity:</strong> Creating your own SPM dependencies allows you to structure your project so that its components are separated and can be reused in other projects. This makes the code cleaner, easier to test, and maintain.</li><li><strong>Version Control and Dependencies:</strong> Managing your code versions through SPM simplifies updates and ensures compatibility with projects that use your library. You can specify which versions of other libraries should be used, preventing dependency conflicts.</li><li><strong>Facilitating Collaboration:</strong> When projects are broken down into smaller dependencies, it is easier for development teams to work on different aspects of the project simultaneously without interfering with each other.</li><li><strong>Centralized Management:</strong> SPM allows centralized management of dependencies and their versions, simplifying updates and maintenance of large and complex systems.</li><li><strong>Wide Availability and Support:</strong> As part of Apple’s official technology stack, SPM is well-supported and regularly updated, ensuring compatibility with the latest versions of Swift and Xcode.</li></ol><h3>1. Swift Package Manager Basics</h3><p>To fully understand its capabilities and working principle, it’s important to get acquainted with its main components.</p><h4>Main Components of SPM</h4><p><strong>1. Package Manifest (Package.swift) </strong>The package manifest is the central configuration file in each Swift package. It is written in Swift and describes the package configuration, including its name, dependencies, targets, and products.</p><pre>// swift-tools-version:5.9<br>import PackageDescription<br><br>let package = Package(<br>    name: &quot;ExamplePackage&quot;,<br>    products: [<br>        .library(name: &quot;ExamplePackage&quot;, targets: [&quot;ExamplePackage&quot;])<br>    ],<br>    dependencies: [<br>        .package(url: &quot;https://github.com/apple/example-dependency.git&quot;, from: &quot;1.0.0&quot;)<br>    ],<br>    targets: [<br>        .target(<br>            name: &quot;ExamplePackage&quot;,<br>            dependencies: [&quot;ExampleDependency&quot;]),<br>        .testTarget(<br>            name: &quot;ExamplePackageTests&quot;,<br>            dependencies: [&quot;ExamplePackage&quot;])<br>    ]<br>)</pre><p>In this example:</p><ul><li>products define the types of libraries available to other projects.</li><li>dependencies list other packages required for this package.</li><li>targets indicate the modules and tests that make up the package.</li></ul><p><strong>2. Swift Tools Version</strong></p><p>This directive specifies the minimum required version of Swift tools, ensuring that your package can be built with the corresponding version of the Swift compiler, which is critical for code compatibility.</p><h4><strong>Supported Platforms and Swift Versions</strong></h4><p>Swift Package Manager is designed to work with a variety of platforms where Swift can run, including macOS, iOS, watchOS, tvOS, and Linux. However, there are some limitations and features when working with each of these platforms, especially regarding building and testing on different operating systems.</p><ul><li><strong>macOS and Linux:</strong> On these platforms, SPM works most fully, as they support script and command execution directly from the terminal.</li><li><strong>iOS, watchOS, tvOS:</strong> For these platforms, additional configuration in Xcode is required to manage dependencies and build libraries, as they require the creation of special project files and configurations.</li></ul><blockquote>Here, I considered delving deeply into the differences for each platform, but ended up getting too technical with Linux specifics. Since my publications are directly related to iOS development, let’s assume that the above points exist, and I will VERY briefly describe the differences. Otherwise, we might get “stuck” here, and 99% of readers simply won’t be interested.</blockquote><p><strong>macOS and Linux</strong></p><p>On macOS and Linux platforms, Swift Package Manager works directly through the terminal, providing a full set of commands for dependency management, project building, and running tests. These operating systems allow using SPM as a standalone tool without the need for IDE integration. Examples of commands:</p><p><strong>Initializing a new package:</strong></p><pre>swift package init --type library</pre><p><strong>Building the project:</strong></p><pre>swift build</pre><p><strong>Running tests:</strong></p><pre>swift test</pre><p><strong>iOS, watchOS, tvOS</strong></p><p>For iOS, watchOS, and tvOS platforms, SPM is used together with Xcode, as specific integration with Xcode projects and workspaces is required for building and testing applications. Unlike macOS and Linux, where SPM can function independently, on these platforms, it relies on Xcode to perform many tasks. Despite this, SPM can be used to create packages on iOS, watchOS, and tvOS platforms also through the terminal.</p><p><strong>In short:</strong></p><blockquote>Packages can be created through the terminal, but dependency integration and management are performed in Xcode. The main difference lies not in the way the package is created, but in its integration and use in projects for these platforms.</blockquote><p>Specifically, we can create a package through the terminal using commands identical to those for macOS or Linux. Commands for initialization and working with the package remain the same.</p><p>Regardless of where and how the package was created, to use it in an iOS, watchOS, or tvOS application, you need to integrate it through Xcode:</p><ul><li>Open the project in Xcode.</li><li>Go to “File” -&gt; “Swift Packages” -&gt; “Add Package Dependency…”</li><li>Specify the URL of the package repository.</li></ul><p>Even if the package is created and developed using terminal commands, Xcode provides tools to manage its dependencies within the project, including updates, configuration, and removal.</p><h3>2. Comparing SPM and CocoaPods</h3><p>Key Differences Between SPM and CocoaPods</p><ol><li>Integration and Installation</li><li>Dependency Management</li><li>Platform Support</li><li>Dependency Storage</li></ol><h4>Pros and Cons of Each Tool</h4><p><strong>SPM:</strong></p><p><strong><em>Pros:</em></strong></p><ul><li>Direct integration into Xcode makes project management easier.</li><li>Does not require additional software installation.</li><li>Apple support and regular updates improve integration with the latest versions of Swift and Xcode.</li></ul><p><strong><em>Cons:</em></strong></p><ul><li>Fewer available libraries compared to CocoaPods.</li><li>Limited support for complex dependencies and configurations.</li></ul><p><strong>CocoaPods:</strong></p><p><strong><em>Pros:</em></strong></p><ul><li>Large community and many available libraries.</li><li>Support for complex dependency scenarios and configurations.</li><li>Good integration with existing iOS and macOS projects.</li></ul><p><strong><em>Cons:</em></strong></p><ul><li>Requires Ruby installation and dependency management via Podfile.</li><li>Can slow down project build time, especially in large projects with many dependencies.</li></ul><h4>Why SPM Might Be Preferable</h4><p>SPM may be preferable for new projects and projects that aim to use the latest integration features with Xcode and Swift, due to the following reasons:</p><ul><li><strong>Better Integration with Xcode:</strong> Dependency management directly from the IDE simplifies the development process.</li><li><strong>Simplified Installation and Updates:</strong> Does not require running external commands or setting up additional tools.</li><li><strong>Future Support:</strong> As an Apple product, SPM constantly receives updates and improvements, making it an attractive choice for long-term project support.</li></ul><h3>3. Creating a New SPM Package</h3><p><strong>Step 1: Initializing a New Package</strong></p><ul><li>Open the terminal.</li><li>Navigate to the directory where the package will be created.</li><li>Run the command:</li></ul><pre>swift package init --type library</pre><p>This command will create a new package of type “library,” implying the creation of a library. You can also choose --type executable to create an executable file.</p><ul><li><strong>library:</strong> Creates a package intended for use as a code library that can be imported into other projects.</li><li><strong>executable:</strong> Creates a package that compiles into an executable file. This type is ideal for creating command-line tools or applications that run directly from the command line.</li></ul><p><strong>Step 2: Viewing the Package Structure</strong></p><p>After initialization, the project structure will include:</p><ul><li>Sources/: Directory for library source files.</li><li>Tests/: Directory for tests, where you can place tests for the library.</li><li>Package.swift: The manifest file describing the package configuration.</li></ul><p><strong>Step 3: Directory and File Structure of the Package</strong></p><ul><li>Sources/&lt;LibraryName&gt;/: Each target in the package will have its subdirectory in Sources. For example, if the target is named &quot;MyLibrary,&quot; the source files for this target will be in Sources/MyLibrary/.</li><li>Tests/&lt;LibraryName&gt;Tests/: Similarly, for tests, a separate directory is created for each target. Tests for &quot;MyLibrary&quot; will be in Tests/MyLibraryTests/.</li></ul><p><strong>Step 4: Configuring the Package Manifest (Package.swift)</strong></p><p><strong>Main components of the Package.swift file:</strong></p><ul><li><strong>name:</strong> The name of the package, e.g., “MyLibrary.”</li><li><strong>dependencies:</strong> Here, you specify the package dependencies, in the format .package(url: &quot;&lt;url&gt;&quot;, from: &quot;&lt;version&gt;&quot;).</li><li><strong>targets:</strong> Defines the package’s modules and tests. Each target can depend on other packages or targets within this package.</li></ul><p><strong>Targets and Their Dependencies</strong></p><ul><li>In SPM, a target represents a module or set of code that can be either a library or an executable file.</li><li>Each target can have dependencies on other packages, specified in the dependencies array. This means that for the target to compile, it needs the code and resources from these packages.</li><li>Targets can also depend on other targets within the same package. This is useful for breaking down a package into multiple modules, where one module can use the functionality of another. For example, if there is a target for network access and a target for data processing, one can depend on the other to perform its functions.</li><li><strong>products:</strong> Describes the products offered to package users, usually libraries.</li></ul><p><strong>Products and Their Types</strong></p><ul><li>Products in SPM describe what the package provides for use in other projects.</li><li>Typically, these are libraries that offer reusable code for inclusion in applications or other libraries.</li><li>In addition to libraries, a product can be an executable file that can be run as a program. Executable files are often used to create command-line tools that perform specific tasks within larger projects or for automation.</li></ul><p><strong>Example configuration of the manifest:</strong></p><pre>// swift-tools-version:5.9<br>import PackageDescription<br><br>let package = Package(<br>    name: &quot;MyLibrary&quot;,<br>    products: [<br>        .library(<br>            name: &quot;MyLibrary&quot;,<br>            targets: [&quot;MyLibrary&quot;]),<br>    ],<br>    dependencies: [<br>        .package(url: &quot;https://github.com/someone/UsefulLibrary.git&quot;, from: &quot;1.0.0&quot;)<br>    ],<br>    targets: [<br>        .target(<br>            name: &quot;MyLibrary&quot;,<br>            dependencies: [&quot;UsefulLibrary&quot;]),<br>        .testTarget(<br>            name: &quot;MyLibraryTests&quot;,<br>            dependencies: [&quot;MyLibrary&quot;])<br>    ]<br>)</pre><p>This manifest configures the basic aspects of the package and ensures that all components are correctly linked and ready for use both within and outside the project. I should note: this is a fairly simple manifest, but sufficient to get started. I do not claim in this publication to rewrite the entire manual from Apple on creating manifests.</p><h3>4. Differences Between Local and Remote Dependencies</h3><p><strong>Local Dependencies</strong> are located within the file system. This is useful for modules that you are developing locally or when you do not want to rely on external sources. Local dependencies can be added by specifying the path to the directory:</p><pre>.package(path: &quot;../local-dependency&quot;)</pre><p>Typically, they are created within the project.</p><p><strong>Remote Dependencies</strong> are libraries or frameworks usually hosted on platforms such as GitHub, GitLab, or Bitbucket. They are added via URL.</p><h3>5. Working with Versions</h3><p>Managing dependency versions is critical for maintaining project stability and compatibility.</p><h4>Semantic Versioning</h4><p>Semantic Versioning (SemVer) is an agreement on how to assign and increment version numbers. It consists of three components:</p><ul><li><strong>MAJOR version</strong> indicates incompatible API changes.</li><li><strong>MINOR version</strong> adds functionality in a backward-compatible manner.</li><li><strong>PATCH version</strong> applies backward-compatible bug fixes.</li></ul><p><strong>Example:</strong></p><ul><li>1.0.0 — Initial release.</li><li>1.1.0 — Added new features, but the old API is preserved.</li><li>1.1.1 — Bug fixes.</li></ul><h4>How to Define and Update Dependency Versions</h4><p><strong>Defining Versions:</strong> In Package.swift, you can specify a version range for each dependency, allowing you to control which updates can be automatically applied. Example:</p><pre>.package(url: &quot;https://github.com/apple/example-dependency.git&quot;, from: &quot;1.0.0&quot;)</pre><p><strong>Updating Dependencies:</strong> To update project dependencies to the latest allowed versions, use the terminal command (from the package directory where the manifest Package.swift is located):</p><pre>swift package update</pre><p>This keeps the project current, secure, and stable, minimizing risks associated with outdated dependencies.</p><h3>6. Testing and Debugging the Package</h3><p>Testing and debugging are crucial aspects of developing reliable software products. In the context of Swift Package Manager, this includes writing unit tests to verify package functionality and effective debugging methods, especially when dealing with dependencies.</p><h4>Writing Unit Tests to Verify the Package</h4><p><strong>1. Creating Test Cases:</strong></p><p>Swift Package Manager automatically generates a test directory when creating a new package. This is usually Tests/&lt;PackageName&gt;Tests.</p><p>For writing tests, we will use XCTest, which is part of the standard Xcode distribution.</p><p><strong>Example structure of a test case:</strong></p><pre>import XCTest<br>@testable import MyLibrary<br><br>final class MyLibraryTests: XCTestCase {<br>    func testExample() {<br>        // Here goes the test code<br>        XCTAssertEqual(&quot;Hello, World!&quot;, &quot;Hello, World!&quot;)<br>    }<br><br>    static var allTests = [<br>        (&quot;testExample&quot;, testExample),<br>    ]<br>}</pre><p>@testable allows tests to access the internal elements of the package.</p><p><strong>2. Running Tests:</strong></p><p>Tests can be run from the terminal by navigating to the package directory and executing the command:</p><pre>swift test</pre><p>This command will find all test cases in the package and run them.</p><h4>Debugging Dependency Issues</h4><p><strong>Checking Dependency Configuration:</strong></p><p>Ensure that all URLs and versions in the Package.swift file are correctly specified. Incorrect addresses or version ranges can cause errors when building or updating the package.</p><p><strong>Using Conditional Compilation Directives:</strong></p><p>If the package depends on platform-specific libraries, you can use conditional compilation for their integration. For example:</p><pre>#if canImport(UIKit)<br>import UIKit<br>#else<br>import Foundation<br>#endif</pre><p>This helps avoid issues when building on various platforms.</p><p><strong>Logging and Breakpoints:</strong></p><p>Use logging to track values and states during package execution. When debugging in Xcode, set breakpoints to evaluate the state of important variables and the flow of program execution.</p><p><strong>Debugging with Xcode:</strong></p><p>For deeper debugging, open the package as part of an Xcode project, where all standard debugging tools are available, including the inspector, call visualization, performance profiling, and more.</p><h3>7. Publishing the Package</h3><p>First, consider public package publication, making it available to other developers and allowing the community to contribute to the project’s improvement. Below is a step-by-step guide to preparing a package for public release, placing it on GitHub, and registering it in the Swift Package Index.</p><h4>7.1. Preparing the Package for Public Release</h4><p><strong>Code Review and Cleanup:</strong></p><ul><li>Ensure all code is well-documented and meets common standards. This includes adding comments and removing unused code.</li><li>Test the package to ensure all features work as intended and there are no errors.</li></ul><p><strong>Adding a README File:</strong></p><ul><li>Create a README.md file in the root directory of your package. It should include a description of the package, installation and usage instructions, and code examples.</li><li>Include information about the license and copyrights.</li></ul><p><strong>Setting Up a License:</strong></p><p>Add a license file to your package. Common licenses are MIT or Apache 2.0, popular in open-source projects. This ensures other developers know how they can use and modify your package.</p><p><strong>Reviewing the Package.swift File:</strong></p><p>Ensure your Package.swift file is correctly configured, and all dependencies are precisely specified. This will ensure easy integration of your package into other developers&#39; projects.</p><h4><strong>Publishing the Package on GitHub</strong></h4><p><strong>Creating a Repository on GitHub:</strong></p><p>In your GitHub account, create a new repository and name it according to your package.</p><p><strong>Uploading the Package:</strong></p><p>Upload the package in any convenient way. An example for the terminal:</p><ul><li>Initialize a local git repository in the package directory with the command git init.</li><li>Add all files to the repository using git add . and make the first commit with git commit -m &quot;Initial commit&quot;.</li><li>Link the local repository to the GitHub repository and push the changes with git push -u origin main.</li></ul><p><strong>Registering in the Swift Package Index</strong></p><p>Swift Package Index is a service that helps find Swift packages and assess their compatibility and quality.</p><p><strong>Adding the Package to the Swift Package Index:</strong></p><p>Go to the <a href="https://swiftpackageindex.com/">Swift Package Index website</a> and find the option to add your package.</p><p>You will need to enter the URL of your GitHub repository. The system will automatically analyze your package and add it to the index.</p><h4>7.2. Publishing the Package in a Private Repository</h4><p>If you are developing a package intended only for internal use or should not be publicly available, you can publish it in a private repository. This ensures the protection of your intellectual property while retaining all the benefits of dependency management through Swift Package Manager.</p><p><strong>Preparing the Package for Publishing in a Private Repository</strong></p><p><strong>Code Review and Documentation:</strong></p><p>As with public publication, ensure all code is well-documented and meets quality standards. Good documentation is especially important for internal projects to simplify understanding and usage by other developers.</p><p><strong>Setting Up Repository Access:</strong></p><p>Create a new repository in your chosen version control service. Set privacy settings so that the repository is accessible only to selected users, teams, or just yourself.</p><p><strong>Uploading the Package to the Repository:</strong></p><p>Follow the same steps as with a public repository.</p><h4><strong>Using a Private Package in Other Projects</strong></h4><p><strong>Adding a Dependency to the Project:</strong></p><p>In the Package.swift file of the project, add the dependency by specifying the path to the private repository:</p><pre>dependencies: [<br>    .package(url: &quot;https://&lt;private-repository-url&gt;&quot;, from: &quot;1.0.0&quot;)<br>]</pre><p><strong>Updating and Managing Dependencies:</strong></p><p>Use standard SPM commands, such as swift package update, to update dependencies, including those in private repositories. There are no differences between private and public repositories as long as you have access to them.</p><blockquote>This approach allows for the most efficient use of Swift Package Manager for dependency management and reduces the time to prepare and integrate new projects or modules.</blockquote><h3>8. Examples of Creating and Using Custom SPM Packages</h3><p><strong>Scenario 1: Relatively Complex and Abstract</strong></p><p>Suppose we are developing a package, MediaProcessingKit, designed for image and audio processing. This package will use various external libraries for multimedia work and provide functionalities for graphical rendering and audio processing.</p><pre>// Indicates the minimum version of Swift tools required to work with this package.<br>// swift-tools-version:5.10<br>import PackageDescription<br><br>let package = Package(<br>    name: &quot;MediaProcessingKit&quot;,  // Package name.<br>    products: [<br>        // Defines libraries that the package offers to external users.<br>        .library(<br>            name: &quot;MediaProcessingKit&quot;,<br>            targets: [&quot;ImageProcessor&quot;, &quot;AudioProcessor&quot;]),<br>        // Defines an executable product compiled from the specified targets.<br>        .executable(<br>            name: &quot;MediaTool&quot;,<br>            targets: [&quot;MediaTool&quot;])<br>    ],<br>    targets: [<br>        // Defines a build target for image processing.<br>        .target(<br>            name: &quot;ImageProcessor&quot;,<br>            dependencies: [&quot;SDL&quot;, &quot;CairoGraphics&quot;],  // Dependencies of this target.<br>            path: &quot;Sources/ImageProcessor&quot;,  // Path to the target&#39;s source files.<br>            exclude: [&quot;ExcludeUnusedFiles&quot;],  // Excludes files or directories from the build.<br>            resources: [  // Resources included in the package.<br>                .process(&quot;Resources/DefaultFilters&quot;)<br>            ],<br>            cSettings: [  // Settings for C-compatible code (e.g., C, C++).<br>                .headerSearchPath(&quot;External/SDL/include&quot;),<br>                .headerSearchPath(&quot;External/Cairo/include&quot;)<br>            ],<br>            linkerSettings: [  // Linker settings for connecting external libraries.<br>                .linkedLibrary(&quot;SDL&quot;),<br>                .linkedLibrary(&quot;cairo&quot;)<br>            ]<br>        ),<br>        // Target for audio processing.<br>        .target(<br>            name: &quot;AudioProcessor&quot;,<br>            dependencies: [&quot;OpenAL&quot;],<br>            path: &quot;Sources/AudioProcessor&quot;,<br>            cSettings: [<br>                .headerSearchPath(&quot;External/OpenAL/include&quot;)<br>            ],<br>            linkerSettings: [<br>                .linkedLibrary(&quot;openal&quot;)<br>            ]<br>        ),<br>        // Executable target using both libraries.<br>        .executableTarget(<br>            name: &quot;MediaTool&quot;,<br>            dependencies: [&quot;ImageProcessor&quot;, &quot;AudioProcessor&quot;],<br>            path: &quot;Sources/MediaTool&quot;<br>        ),<br>        // Test target, including tests for both libraries.<br>        .testTarget(<br>            name: &quot;MediaProcessingKitTests&quot;,<br>            dependencies: [&quot;ImageProcessor&quot;, &quot;AudioProcessor&quot;],<br>            path: &quot;Tests&quot;<br>        )<br>    ]<br>)</pre><p>For this example, it is important to note that Swift Package Manager cannot directly integrate libraries that are not packaged as Swift packages without additional configuration. If the dependencies in the targets are presented as system libraries, it may be necessary to create separate “wrapper” packages that include the necessary header files and module settings. However, we will not delve into these intricacies here. For a beginner, this will “blow their mind,” and it will extend an already long article even further. A detailed manual for those who understand the matter is likely unnecessary.</p><p>Therefore, in the next scenario, we will consider a simpler example.</p><p><strong>Scenario 2: Simple and Real</strong></p><p>Let’s take a very simple logger, Logger.swift, and create a simple Swift package from it, which can be used as an external dependency. Then we will publish this package on GitHub and see how it can be connected to other projects.</p><p>The logger:</p><pre>// Logger.swift<br><br>import Foundation<br><br>enum Logger {<br>    <br>    // Properties<br>    static var isLoggingEnabled = true // Flag to enable/disable logging<br>}<br><br>// Methods<br>extension Logger {<br>    <br>    // General logging method<br>    static func log(_ message: String) {<br>        guard isLoggingEnabled else { return }<br>        print(message)<br>    }<br>}</pre><p><strong>Step 1: Creating a Swift Package</strong></p><p><strong>Initializing a new package:</strong> Open the terminal and execute the following commands to create a new directory and initialize the package:</p><pre>mkdir SwiftLogger<br>cd SwiftLogger<br>swift package init --type library</pre><p><strong>Adding the </strong><strong>Logger.swift file to the project:</strong> Simply copy the Logger.swift file into the Sources/SwiftLoggerdirectory.</p><p><strong>Step 2: Configuring the Package Manifest</strong></p><pre>// swift-tools-version:5.10<br>import PackageDescription<br><br>let package = Package(<br>    name: &quot;SwiftLogger&quot;,<br>    products: [<br>        .library(<br>            name: &quot;SwiftLogger&quot;,<br>            targets: [&quot;SwiftLogger&quot;])<br>    ],<br>    targets: [<br>        .target(<br>            name: &quot;SwiftLogger&quot;,<br>            dependencies: []<br>        ),<br>        .testTarget(<br>            name: &quot;SwiftLoggerTests&quot;,<br>            dependencies: [&quot;SwiftLogger&quot;])<br>    ]<br>)</pre><p>In this example, there is no need to specify additional details about the location of source files or their extensions if they conform to SPM’s standard conventions. Here are some key points:</p><p><strong>Directory Structure:</strong> By default, SPM expects the source code for each target to be in a subdirectory of the Sources directory named after the target. If the package is named SwiftLogger, SPM will look for source code in Sources/SwiftLogger/. Since in our case the code should be in this directory, there is no need to specify the path additionally.</p><p><strong>File Extensions:</strong> Swift Package Manager automatically processes files with the .swift extension in the specified directories. There is no need to specify file extensions in Package.swift — SPM will find and compile all .swiftfiles in the specified directories.</p><p><strong>Tests:</strong> For tests, the standard location is the Tests/&lt;TargetName&gt;Tests/ directory, where &lt;TargetName&gt; is the name of the target for which the tests are written. In our case, this is Tests/SwiftLoggerTests/. Files in this directory should also have the .swift extension.</p><p>Here is what the directory structure for this package will look like:</p><pre>- SwiftLogger/<br>  |- Sources/<br>    |- SwiftLogger/<br>      |- Logger.swift  // Main logging file<br>  |- Tests/<br>    |- SwiftLoggerTests/<br>      |- SwiftLoggerTests.swift  // Tests for the package</pre><p><strong>Step 3: Publishing on GitHub</strong></p><p>I won’t dwell on this in detail; just upload the package to the required repository, for example, https://github.com/your_username/SwiftLogger.git.</p><p><strong>Step 4: Using the Package in Other Projects</strong></p><p><strong>Adding the dependency to another package:</strong></p><p>In the Package.swift file of another Swift project, add the following dependency to the dependencies array:</p><pre>dependencies: [<br>    .package(url: &quot;https://github.com/your_username/SwiftLogger.git&quot;, from: &quot;1.0.0&quot;)<br>]</pre><p>Note that in Swift Package Manager, the package version is determined through git tags, not directly in the Package.swift file.</p><p><strong>Importing and Using </strong><strong>SwiftLogger:</strong></p><p>In the necessary project file, add:</p><pre>import SwiftLogger<br><br>Logger.log(&quot;Some important log&quot;)</pre><p>First, install the dependency through Xcode for the current project.</p><p>This scenario demonstrates how to turn a simple logging class into a reusable Swift package and share it with the community or use it in other projects.</p><h3>Conclusion</h3><p>Congratulations to those who read to the end! You have reviewed many aspects of working with Swift Package Manager (SPM), from basic dependency management to creating and publishing your own package.</p><p>Now you know (or maybe already knew) how to:</p><ul><li>Initialize a new package.</li><li>Configure the package manifest for managing dependencies and build targets.</li><li>Use various settings for compiling and linking external libraries.</li><li>Test and debug developed packages.</li><li>Publish packages on GitHub and register them in the Swift Package Index for availability to all developers.</li></ul><p>Swift Package Manager simplifies many aspects of project management and can significantly increase the efficiency and organization of your workflow. Understanding and correctly using this tool opens up broad possibilities for creating reliable and scalable applications.</p><p>Now that you have mastered the basics of working with Swift Package Manager, why not try creating your own package? It can be a library you often use in your projects or a tool that can be useful to other developers in the community.</p><ul><li><strong>Start simple:</strong> Choose a small task that your package could solve.</li><li><strong>Think through the architecture:</strong> Develop clean and modular code that is easy to test and maintain.</li><li><strong>Document your package:</strong> Detailed documentation and usage examples significantly increase the chances of your package being accepted by the community.</li><li><strong>Publish and get feedback:</strong> Post your package on GitHub, register it in the Swift Package Index, and invite other developers to try it.</li></ul><p>Creating and publishing your own package not only improves your skills in working with Swift and understanding dependency management but can also significantly contribute to the Swift ecosystem, helping other developers find solutions to their tasks.</p><p><strong>Experiment, explore, and share your developments — the Swift community will thank you!</strong></p><p>P.S. For those who want to deepen their knowledge and understanding of Swift Package Manager or expand their skills in Swift development, the following resources will be extremely useful:</p><ul><li>Official Swift Package Manager documentation: <a href="https://swift.org/package-manager/">https://swift.org/package-manager/</a></li><li>PackageDescription documentation: <a href="https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html">https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html</a></li><li>GitHub repository with example packages that can be used for study and testing: <a href="https://github.com/apple/swift-package-manager">https://github.com/apple/swift-package-manager</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=768b2f55a4d0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Should You Learn UIKit in 2024? (Compared to SwiftUI)]]></title>
            <link>https://quasaryy.medium.com/should-you-learn-uikit-in-2024-compared-to-swiftui-52616bb22513?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/52616bb22513</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Mon, 24 Jun 2024 12:04:38 GMT</pubDate>
            <atom:updated>2024-06-24T12:04:38.801Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PmbJk8iFG39MlNAPgvdkig.jpeg" /></figure><h3>Introduction</h3><p>Unlike my usual articles, this one will have a more philosophical tone rather than a deep dive with numerous code examples. I’d even describe it as relaxing. If you’re an experienced developer, there won’t be anything new here for you. However, if you’re a beginner, this article will clarify all your queries about the topic at hand. Let’s get started :)</p><p>Since the advent of SwiftUI in 2019, the world of iOS development has undergone a true revolution. This framework introduced a new, declarative approach to creating user interfaces, promising to make the development process faster, simpler, and more engaging. Despite its advantages, however, SwiftUI hasn’t completely displaced UIKit, which remains foundational for many complex and high-performance applications. This article will examine the factors to consider when choosing between UIKit and SwiftUI in 2024.</p><h3><strong>What are UIKit and SwiftUI?</strong></h3><p>UIKit is a framework that has been used to create user interfaces across all Apple devices since the launch of the first iPhone. It offers a broad range of controls and tools for intricate interface customization, including components like buttons, tables, collections, and many others.</p><p><strong>Here’s a very simplified example of creating a table in UIKit:</strong></p><pre>func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {<br>    return dataSource.count<br>}<br><br>func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {<br>    let cell = tableView.dequeueReusableCell(withIdentifier: &quot;cell&quot;, for: indexPath)<br>    cell.textLabel?.text = dataSource[indexPath.row]<br>    return cell<br>}</pre><p>This is indeed a very simplified example, lacking custom cell usage, table initialization, or the myriad delegates and their methods required in real development. In practice, the code would span hundreds of lines.</p><p><strong>SwiftUI</strong>, on the other hand, represents a newer framework that employs a declarative coding style. This means developers describe what they want to see rather than how it should be implemented. SwiftUI facilitates the creation of cross-platform applications for all Apple devices, including iPhones, iPads, Macs, Apple Watches, and even Apple TVs.</p><p><strong>An example of creating a list (table) in SwiftUI:</strong></p><pre>List(dataSource) { item in<br>    Text(item)<br>}</pre><p>This is also a simplified example, but it’s clear how much less code is involved, yet how much less control developers have with SwiftUI.</p><h3><strong>Features of Developing with SwiftUI</strong></h3><p>SwiftUI is ideal for quickly developing simple to moderately complex interfaces thanks to its declarative nature (we’ll discuss why only simple and moderate shortly). Here are some key aspects:</p><ul><li><strong>Declarative Approach:</strong> In SwiftUI, you describe what should appear on the screen, not how it should be displayed. This reduces the amount of code and simplifies understanding. For example, creating a dynamic list with text and images (a UITableView in UIKit) in SwiftUI takes just a few lines of code, while in UIKit, it requires much more effort to set up and manage a UITableView or UICollectionView. However, as mentioned earlier, this comes at the cost of control, and I’d add that there’s a noticeable performance advantage in favor of UIKit without needing any measurements or tests.</li><li><strong>Bindings and State:</strong> SwiftUI simplifies UI component state management with its system of bindings and state properties. This allows for the easy creation of reactive interfaces that automatically update when data changes. In UIKit, reactive frameworks like Apple’s native Combine are required for similar functionality, which has a steep learning curve but also offers a different level of control. To be fair, it’s worth noting that nothing prevents the use of Combine with SwiftUI.</li></ul><h4><strong>The Importance of UIKit in Modern Development</strong></h4><p>UIKit continues to play a critical role in the development of complex and high-performance iOS apps:</p><ul><li><strong>Fine-Tuning and Control:</strong> UIKit provides developers with deep control over interface elements and their behavior. This is especially important for apps requiring high performance or specialized behavior, which are difficult or impossible to achieve with SwiftUI.</li><li><strong>Advanced Features:</strong> Some capabilities, such as complex animations, multitasking, or integration with legacy systems, are better implemented using UIKit.</li></ul><h3><strong>Synergy Between UIKit and SwiftUI</strong></h3><p>Integrating UIKit and SwiftUI can bring significant benefits to projects that require the simplicity of SwiftUI and the power of UIKit:</p><ul><li><strong>Using UIView in SwiftUI:</strong> You can integrate UIView from UIKit into SwiftUI projects using UIViewRepresentable. This allows for the use of advanced UIKit features within a SwiftUI interface.</li><li><strong>Encapsulating SwiftUI in UIKit:</strong> Conversely, SwiftUI elements can be embedded within UIKit applications using UIHostingController, enabling a gradual transition to the new technology without completely rewriting the existing app.</li></ul><p><strong>Example of integrating UIView into SwiftUI:</strong></p><pre>struct UIKitView: UIViewRepresentable {<br>    func makeUIView(context: Context) -&gt; UILabel {<br>        let label = UILabel()<br>        label.text = &quot;Example of UIKit Integration in SwiftUI&quot;<br>        return label<br>    }<br><br>    func updateUIView(_ uiView: UILabel, context: Context) {<br>        // Update UIView properties when data changes<br>    }<br>}</pre><h3><strong>Job Market Prospects and Learning</strong></h3><p>Mastering skills in both UIKit and SwiftUI significantly increases a developer’s chances of securing a high-paying job. The market is constantly evolving, and the ability to quickly adapt to new technologies is a key competitive advantage. Developers aiming for maximum demand should study both frameworks to be prepared for any project challenge.</p><ul><li><strong>Versatility:</strong> Knowledge of both frameworks allows developers to flexibly choose the right tools for each specific project.</li><li><strong>Education and Resources:</strong> There are numerous courses, online platforms, and communities that help developers learn both UIKit and SwiftUI, ensuring ongoing professional development.</li></ul><p><strong>Summary of this section:</strong> If you’re just starting your journey in iOS development, SwiftUI may suffice for intern and junior positions, but even these roles often require at least a basic understanding of UIKit. In the conclusion, I will explain why.</p><h3><strong>Conclusion</strong></h3><p>Choosing between UIKit and SwiftUI isn’t just a technical decision — it’s a strategic consideration that affects the architecture of the app, development speed, and future support. While SwiftUI continues to evolve and offer increasingly powerful tools for simplifying interface creation, UIKit remains indispensable in a developer’s arsenal due to its <strong>performance and control</strong> over interface components.</p><h4><strong>Performance and Control</strong></h4><p>Thanks to its low-level access to control elements and deep integration with the operating system, UIKit ensures high performance even in the most demanding applications. It allows precise control over the visual presentation and behavior of applications, which is critically important in complex projects with dynamic interfaces, intricate animations, and intensive user input handling. All things being equal, UIKit will always perform faster than SwiftUI, as it interacts directly with low-level system components.</p><h4><strong>Understanding the Fundamentals</strong></h4><p>Although SwiftUI provides convenient abstractions for working with interfaces, it largely relies on concepts and structures that were first implemented in UIKit. Knowledge of UIKit not only <strong>expands a developer’s ability</strong> to create customized solutions but also <strong>provides a deep understanding of how iOS control elements work</strong>. For example, understanding gestures and their handling in UIKit allows for a deeper comprehension of how SwiftUI processes user interactions. UIKit offers more detailed and flexible settings for event handling of gestures, which is critically important for developing interactive applications where each gesture can have multiple different states and consequences.</p><p><strong>Example of Gesture Handling</strong></p><p>In UIKit, adding a custom gesture to an interface element can be done using the following approach:</p><pre>let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))<br>view.addGestureRecognizer(tapGesture)<br><br>@objc func handle(gesture: UITapGestureRecognizer) {<br>    // Handle the gesture<br>}</pre><p>In SwiftUI, adding gestures is significantly simplified, but some aspects of control are hidden:</p><pre>Text(&quot;Tap me&quot;)<br>    .onTapGesture {<br>        print(&quot;Tapped!&quot;)<br>    }</pre><p>This example shows how gesture addition in SwiftUI is simplified, but it does not allow full control over event handling as is possible in UIKit. This simplification makes SwiftUI accessible to beginners, however, it limits the capabilities of experienced developers seeking maximum control over interactivity.</p><h3>Summary</h3><p>Mastery of UIKit and understanding its interaction with the operating system and hardware are key to creating professional and high-performance applications. For those aspiring to become true masters of iOS development, learning UIKit is essential. It not only expands technical capabilities and deepens understanding of the interaction processes within the app but also provides a deeper insight into the architectural and design decisions that make the iOS ecosystem unique. Therefore, despite all the advantages and conveniences of SwiftUI, UIKit remains an indispensable tool in the arsenal of a developer aiming for the heights of mastery in creating applications for Apple devices.</p><p>As promised at the start of the post, it’s straightforward, with few code examples. If you want to delve deeper into the differences between these frameworks and see more code examples, I also recommend reading this publication: <a href="https://medium.com/@quasaryy/reusable-cells-in-uikit-and-their-differences-in-swiftui-132f6d17afcc"><strong>Reusable Cells in UIKit and Their Differences in SwiftUI</strong></a></p><p><strong>Brief Summary: </strong>Yes, UIKit is worth learning in 2024 and beyond. The real question is how to learn it. If you’ve read this far, you’re likely just starting out in iOS development. In 2024, I’d recommend starting with UIKit but not fanatically. Definitely delve deeply into topics like UITableView and UICollectionView, gestures, navigation, the lifecycle of apps and controllers. The rest can be covered more superficially. When you move to SwiftUI, you’ll already understand why things work the way they do and how to achieve the desired behavior.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=52616bb22513" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Deep Dive into Dependency Containers in Swift]]></title>
            <link>https://quasaryy.medium.com/a-deep-dive-into-dependency-containers-in-swift-c8ce86dcdcaf?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/c8ce86dcdcaf</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Sun, 26 May 2024 19:22:54 GMT</pubDate>
            <atom:updated>2024-05-26T19:22:54.860Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gpXbX0PfTSPDXxRJ82YRUw.jpeg" /></figure><h3>Introduction</h3><p>In the world of Swift development, especially when building large and modular systems, dependency management plays a crucial role. A dependency container is a tool that allows you to manage, register, and resolve dependencies in an application. This not only simplifies the development and testing process but also enhances the modularity and reusability of the code.</p><h3>Basics of Dependency Containers</h3><h4>What is a Dependency?</h4><p>A dependency in the context of programming is an object or class that another object requires to function. For example, if you have a DatabaseHandler class that requires a ConnectionPool to work with a database, then ConnectionPool is a dependency for DatabaseHandler.</p><h4>Why Do We Need Dependency Containers?</h4><p>Dependency containers help automate the process of creating and managing these dependencies. They provide a centralized way to register all dependencies and subsequently retrieve them, which helps to:</p><ul><li>Reduce code coupling.</li><li>Simplify testing through inversion of control.</li><li>Enhance the scalability and maintainability of the application.</li></ul><h3>Creating a Dependency Container</h3><h4>Defining the Container</h4><p>Let’s start by creating a basic container that allows us to register and retrieve dependencies:</p><h4>Implementing the Registration Mechanism</h4><pre>class DependencyContainer {<br>    private var registry = [String: () -&gt; Any]()<br><br>    func register&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>        let key = String(describing: type)<br>        registry[key] = factory<br>    }<br>}</pre><p>The register method uses a dictionary to store factories that create instances of objects. The key is a string representation of the type, which simplifies the lookup and avoids the need to use complex data structures.</p><h4>Implementing Dependency Resolution</h4><pre>func resolve&lt;T&gt;(_ type: T.Type) -&gt; T {<br>    let key = String(describing: type)<br>    guard let factory = registry[key]?() as? T else {<br>        fatalError(&quot;No registered entry for \(T.self)&quot;)<br>    }<br>    return factory<br>}</pre><p>The resolve method accesses the registry for an instance of the required type. If the dependency is not found, an error is generated, indicating improper dependency management.</p><h4>Combining the Above Code into One</h4><pre>class DependencyContainer {<br>    private var registry = [String: () -&gt; Any]()<br><br>    func register&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>        let key = String(describing: type)<br>        registry[key] = factory<br>    }<br><br>    func resolve&lt;T&gt;(_ type: T.Type) -&gt; T {<br>        let key = String(describing: type)<br>        guard let factory = registry[key] as? () -&gt; T else {<br>            fatalError(&quot;No registered entry for \(T.self)&quot;)<br>        }<br>        return factory()<br>    }<br>}</pre><h4>Exception Handling When Dependency Is Missing</h4><p>The resolve method in the dependency container plays a critical role as it is responsible for retrieving the registered dependency. If a dependency is not registered, it is important to have a clear strategy for handling such a situation:</p><pre>func resolve&lt;T&gt;(_ type: T.Type) -&gt; T {<br>    let key = String(describing: type)<br>    guard let factory = registry[key]?() as? T else {<br>        fatalError(&quot;No registered entry for \(T.self). Ensure that the dependency is registered before resolving it.&quot;)<br>    }<br>    return factory()<br>}</pre><p>Using fatalError() helps identify configuration errors during development, but for production code, it might be useful to use a softer approach, such as returning nil or throwing an exception, which can be handled to avoid application crashes.</p><p>In Swift, error handling can be implemented using the throw mechanism, which allows you to throw and catch exceptions during code execution. This provides more flexible error management compared to the rigid program termination with fatalError(). Implementing exceptions in the resolve method might look like this:</p><pre>enum DependencyError: Error {<br>    case dependencyNotRegistered(String)<br>}<br><br>class DependencyContainer {<br>    private var registry = [String: () -&gt; Any]()<br><br>    func register&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>        let key = String(describing: type)<br>        registry[key] = factory<br>    }<br><br>    func resolve&lt;T&gt;(_ type: T.Type) throws -&gt; T {<br>        let key = String(describing: type)<br>        guard let factory = registry[key]?() as? T else {<br>            throw DependencyError.dependencyNotRegistered(&quot;No registered entry for \(T.self)&quot;)<br>        }<br>        return factory()<br>    }<br>}</pre><p>In this example, if the dependency is not found, instead of calling fatalError(), the resolve method now throws a DependencyError.dependencyNotRegistered exception. This allows the calling code to catch and handle errors more flexibly, such as providing an alternative solution or informing the user of the issue.</p><h4>Registering Dependencies</h4><p>Let’s see how to register some typical dependencies in our container:</p><pre>class ApiService {}<br>class DatabaseService {}<br>class UserManager {<br>    init(apiService: ApiService, databaseService: DatabaseService) {}<br>}<br><br>let container = DependencyContainer()<br>container.register(ApiService.self) { ApiService() }<br>container.register(DatabaseService.self) { DatabaseService() }<br>container.register(UserManager.self) {<br>    UserManager(apiService: container.resolve(ApiService.self),<br>                databaseService: container.resolve(DatabaseService.self))<br>}</pre><h4>Using Dependencies</h4><p>Getting a dependency from the container is quite simple:</p><pre>let userManager: UserManager = container.resolve(UserManager.self)</pre><h3>Delving Deeper into the Topic</h3><h4>Managing Object Lifecycles</h4><p>Managing the lifecycle of objects in dependency containers can be implemented using various strategies. The object lifecycle management strategy determines how and when object instances are created, reused, and destroyed. Here are some common strategies that can be implemented in your custom dependency container:</p><h4>Singleton</h4><p>This strategy creates the same instance of an object for all requests, which is useful for managing shared resources such as configuration, database connections, etc.</p><p>When to use: When you need a single instance of an object accessible throughout the entire application, such as for logging, network request management, or data caching.</p><pre>func registerSingleton&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>    let key = String(describing: type)<br>    var instance: T?<br>    registry[key] = {<br>        if instance == nil {<br>            instance = factory()<br>        }<br>        return instance!<br>    }<br>}</pre><h4>Transient</h4><p>This is the simplest strategy where a new instance of an object is created each time a dependency is requested. This is useful when no state preservation is required between different object requests.</p><p>When to use: When you need to ensure that each object has its own state and does not depend on the state of other objects, such as in multithreaded or parallel processing.</p><pre>func registerTransient&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>    let key = String(describing: type)<br>    registry[key] = factory<br>}</pre><p><strong>Example usage:</strong></p><pre>let service: MyService = container.resolve(MyService.self)<br>// Always returns a new instance of MyService</pre><h4>Scoped</h4><p>This strategy allows you to create an object that will live for a specific “scope” or context, such as during a session, request, or transaction. The object is created once within the given scope and destroyed after its completion.</p><p>When to use: When objects should be shared between certain parts of the application within one logical scope but not between different scopes or sessions.</p><pre>func registerScoped&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T, scope: String) {<br>    let key = String(describing: type) + scope<br>    registry[key] = factory<br>}<br><br>func resolveScoped&lt;T&gt;(_ type: T.Type, scope: String) -&gt; T {<br>    let key = String(describing: type) + scope<br>    if let factory = registry[key] as? () -&gt; T {<br>        return factory()<br>    } else {<br>        fatalError(&quot;No registered entry for \(T.self) with scope \(scope)&quot;)<br>    }<br>}</pre><h4>Lazy Initialization</h4><p>With lazy initialization, the object is created upon its first access and then saved for subsequent requests. This is similar to a singleton but with deferred initialization.</p><p>In a dependency container, both singleton and lazy initialization may seem similar since both patterns involve creating the object upon the first request. However, the key differences lie in the intentions behind their use and object lifecycle management.</p><h4>Singleton in a Dependency Container</h4><p>A singleton in a dependency container is typically used to ensure a single global point of access to a service throughout the application’s lifecycle. This means the object is created once and stored in the container for reuse on every request.</p><p><strong>Singleton registration example:</strong></p><pre>class Logger {<br>    func log(message: String) {<br>        print(&quot;Log: \(message)&quot;)<br>    }<br>}<br><br>let container = DependencyContainer()<br>container.registerSingleton(Logger.self) {<br>    Logger()<br>}<br><br>let logger1: Logger = container.resolve(Logger.self)<br>let logger2: Logger = container.resolve(Logger.self)<br>// logger1 and logger2 will point to the same object</pre><h4>Lazy Initialization in a Dependency Container</h4><p>Lazy initialization, although similar to singleton, is more focused on deferred object creation, not necessarily on its global single use. In the container, this means the object can be created on the first request and reused within a certain context, such as during a request execution.</p><p><strong>Lazy initialization example:</strong></p><pre>class ConfigurationLoader {<br>    func loadConfig() -&gt; [String: Any] {<br>        print(&quot;Loading configuration...&quot;)<br>        return [&quot;key&quot;: &quot;value&quot;]<br>    }<br>}<br><br>let container = DependencyContainer()<br>container.registerLazy(ConfigurationLoader.self) {<br>    ConfigurationLoader()<br>}<br><br>let configLoader: ConfigurationLoader = container.resolve(ConfigurationLoader.self)<br>// ConfigurationLoader will be created on the first resolve call</pre><h4>Summary on Singleton and Lazy Initialization</h4><p>The key difference between these approaches is that while both can use lazy initialization, a singleton ensures that only one instance of the object is used throughout the application’s runtime, whereas lazy initialization in a dependency container can be configured to create the object on demand and reuse it under certain conditions, which does not always imply a single instance.</p><p>Thus, the right choice between singleton and lazy initialization depends on the application’s object lifecycle management requirements.</p><h3>Examples of Object Lifecycle Management from Real Practice</h3><p><strong>1. Singleton for Application Settings Management</strong></p><p>In applications, centralized settings management accessible from any part of the program is often required. Singleton is ideal for this task.</p><p>In this example, I will show how to integrate the singleton pattern into a dependency container that provides global access to an AppConfig instance through the container:</p><pre>class AppConfig {<br>    var settings: [String: Any] = [&quot;Theme&quot;: &quot;Dark&quot;, &quot;Language&quot;: &quot;English&quot;]<br><br>    func updateSettings(key: String, value: Any) {<br>        settings[key] = value<br>    }<br>}<br><br>class DependencyContainer {<br>    private var registry = [String: () -&gt; Any]()<br>    private var singletons = [String: Any]()<br><br>    func registerSingleton&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>        let key = String(describing: type)<br>        registry[key] = factory<br>    }<br><br>    func resolve&lt;T&gt;(_ type: T.Type) -&gt; T {<br>        let key = String(describing: type)<br>        if let instance = singletons[key] as? T {<br>            return instance<br>        } else if let factory = registry[key]?() as? T {<br>            singletons[key] = factory<br>            return factory<br>        } else {<br>            fatalError(&quot;No registered entry for \(T.self)&quot;)<br>        }<br>    }<br>}<br><br>// Registration and usage<br>let container = DependencyContainer()<br>container.registerSingleton(AppConfig.self) { AppConfig() }<br><br>let config: AppConfig = container.resolve(AppConfig.self)<br>print(config.settings[&quot;Theme&quot;]!)  // Outputs: Dark<br><br>config.updateSettings(key: &quot;Language&quot;, value: &quot;French&quot;)<br>let updatedConfig: AppConfig = container.resolve(AppConfig.self)<br>print(updatedConfig.settings[&quot;Language&quot;]!)  // Outputs: French</pre><p>In this example, AppConfig is managed as a singleton within the dependency container. This demonstrates how the container can manage the lifecycle of an object, ensuring that all parts of the application access the same instance of AppConfig, which is useful for shared application settings. This approach ensures that changes in settings are propagated throughout the application instantly.</p><p><strong>2. Transient Objects for REST API Clients</strong></p><p>REST API clients often do not contain state and are created to perform network requests, making them ideal candidates for the transient strategy:</p><pre>class APIClient {<br>    func fetchData(completion: @escaping (Data?, Error?) -&gt; Void) {<br>        // Request logic<br>    }<br>}<br><br>class DependencyContainer {<br>    func registerTransient&lt;T&gt;(_ type: T.Type, factory: @escaping () -&gt; T) {<br>        let key = String(describing: type)<br>        registry[key] = factory<br>    }<br><br>    func resolve&lt;T&gt;(_ type: T.Type) -&gt; T {<br>        let key = String(describing: type)<br>        guard let factory = registry[key]?() as? () -&gt; T else {<br>            fatalError(&quot;No registered entry for \(T.self)&quot;)<br>        }<br>        return factory()<br>    }<br>}<br><br>// Registration and usage<br>container.registerTransient(APIClient.self) { APIClient() }<br>let apiClient: APIClient = container.resolve(APIClient.self)<br>apiClient.fetchData { data, error in<br>    // Handle response<br>}</pre><h4>Summary on Object Lifecycle Management</h4><p>Why it is important to choose the right lifecycle management strategy:</p><ul><li><strong>Singleton</strong>: Used to provide global and single access to an object throughout the application’s lifecycle. Ideal for managing resources that should be available throughout the application’s runtime, such as an API client or configuration manager.</li><li><strong>Transient</strong>: Each request to the container results in the creation of a new instance. This is suitable for objects that do not contain state and provide functionality independent of previous uses, such as web service request handlers.</li><li><strong>Scoped</strong>: Objects are created once for each unique “scope”. This is useful in situations where objects need to maintain state within one operation or transaction but should not be shared or reused across different contexts.</li></ul><h3>Extending the Container</h3><p>The container can be extended by adding support for property injection or automatic dependency resolution through reflection, which can significantly simplify the registration of components in large applications.</p><h4>1. Property Injection</h4><p>Property injection allows you to set dependencies after an object is created. This can be useful when the constructor is not available for modification (e.g., when classes are created through third-party libraries or frameworks).</p><p><strong>Property injection example:</strong></p><pre>class AnalyticsService {<br>    var logger: Logger?<br>}<br><br>let container = DependencyContainer()<br>container.register(AnalyticsService.self) {<br>    let analytics = AnalyticsService()<br>    analytics.logger = container.resolve(Logger.self)<br>    return analytics<br>}</pre><p>In this example, AnalyticsService depends on Logger, which is injected via a property after the AnalyticsService instance is created.</p><h4>2. Reflection-Based Resolution</h4><p>Automatic dependency resolution through reflection allows the container to independently determine which dependencies an object requires and automatically inject them by inspecting property types or constructor parameters.</p><p>Reflection-based resolution example:</p><pre>import Foundation<br><br>class ViewController {<br>    var analytics: AnalyticsService?<br>    var logger: Logger?<br>}<br><br>let container = DependencyContainer()<br><br>// Registering types<br>container.register(AnalyticsService.self) { AnalyticsService() }<br>container.register(Logger.self) { Logger() }<br><br>// Function for automatic dependency resolution<br>func autoInject(_ object: AnyObject) {<br>    let mirror = Mirror(reflecting: object)<br>    for child in mirror.children {<br>        if let propertyName = child.label, let propertyType = child.value as? AnyClass {<br>            if let resolved = container.resolve(propertyType) {<br>                object.setValue(resolved, forKey: propertyName)<br>            }<br>        }<br>    }<br>}<br><br>// Creating ViewController and automatically injecting dependencies<br>let viewController = ViewController()<br>autoInject(viewController)</pre><p>In this example, reflection is used to inspect ViewController properties and automatically inject AnalyticsService and Logger dependencies. The autoInject function checks each property of the object against the registered types in the container and injects them if a match is found.</p><p>Extending the dependency container to support property injection and automatic resolution can significantly simplify working with dependencies in large applications, reducing the amount of code required for manual registration and resolution of each dependency. These methods make the container a more powerful and flexible tool.</p><h4>Performance and Security Concerns with Reflection</h4><p>While reflection allows flexible dependency management, it can impact performance due to the need to analyze metadata at runtime. Additionally, using reflection can introduce security issues if the data used for reflection is controlled by external users.</p><p><strong>Minimizing risks and improving performance:</strong></p><ul><li><strong>Limiting usage:</strong> Reflection can decrease performance, so its usage should be limited to critical cases. For instance, use reflection during initialization or system configuration but avoid it in frequently called code.</li><li><strong>Security:</strong> Reflection increases the risk of executing unauthorized code, especially if the input data for reflection comes from untrusted sources. Always validate and sanitize such data before using it.</li><li><strong>Caching results:</strong> To improve performance when using reflection, cache the results of the analysis for reuse, thereby reducing the number of costly reflection operations.</li></ul><h3>What is a Factory in a Dependency Container?</h3><p>A factory is a term used to refer to a function or method that creates instances of objects. In the context of a dependency container, a factory is a function that returns a new instance of a specific type. Factory functions provide flexibility in object creation, allowing the container to manage not only the lifecycle of objects but also the way they are created.</p><h4>Why are factory functions needed?</h4><p>Factory functions are important for several reasons:</p><ul><li><strong>Encapsulation of object creation:</strong> Factory functions hide the details of object creation, simplifying dependency management. Users of the object do not need to know how the object was created.</li><li><strong>Flexibility:</strong> Factory functions allow you to easily change the way objects are created without modifying the code that uses these objects.</li><li><strong>Lifecycle management:</strong> The container can use factories to implement various object lifecycle management strategies, such as creating a new object on each request or using a single instance (singleton), as described above.</li></ul><p><strong>Factory Function Example</strong></p><p>Let’s consider a simple example of registering a dependency and resolving it through a factory function:</p><pre>class DatabaseService {<br>    init(configuration: Configuration) {}<br>}<br><br>let container = DependencyContainer()<br>container.register(DatabaseService.self) {<br>    let config = Configuration(databaseURL: &quot;https://example.com&quot;)<br>    return DatabaseService(configuration: config)<br>}<br><br>let databaseService: DatabaseService = container.resolve(DatabaseService.self)</pre><p>In this example, the factory function creates an instance of DatabaseService, first creating the necessary configuration. This shows how a factory can be used to set up dependencies before their creation, which is especially useful when objects require complex initialization or configuration.</p><h4>Factories and Their Flexibility</h4><ul><li>Factory methods can adapt to the current state of the application or configuration. For example, if the application is running in test mode, the factory can return mock objects instead of real services. This is especially useful in unit testing.</li><li>Factories can be configured to inject different dependencies depending on the launch conditions or the current system configuration, allowing you to easily change object behavior without modifying their code.</li></ul><h4>Impact of Changing the Factory on Object Creation</h4><p>Factory methods can be configured to perform various tasks before returning an object instance, such as configuration, logging, or applying decorators. Changing the factory can significantly alter the behavior or performance of the created object, making factories a powerful tool for managing object creation.</p><h3>Conclusion</h3><p>A dependency container is a powerful tool for any Swift developer striving to create clean, modular, and easily testable code. Developing your own dependency container provides a deep understanding of your applications and offers high control over the dependency management process.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c8ce86dcdcaf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reusable Cells in UIKit and Their Differences in SwiftUI]]></title>
            <link>https://quasaryy.medium.com/reusable-cells-in-uikit-and-their-differences-in-swiftui-132f6d17afcc?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/132f6d17afcc</guid>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Sat, 13 Apr 2024 18:15:45 GMT</pubDate>
            <atom:updated>2024-04-13T18:15:45.853Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CUHOmaYNlddBWoy4-JG-tA.jpeg" /></figure><h3>Introduction</h3><p>Effective resource management when displaying large lists of data is a key task in iOS app development. UIKit, having served as the foundation for building user interfaces for many years, offers the concept of reusable cells through UITableView and UICollectionView. However, with the advent of SwiftUI, approaches to interface management have significantly changed. Let’s examine how reuse works in UIKit and what innovations SwiftUI has brought. We will also learn how to fix the issue when cells in UIKit often display incorrect data.</p><h3>Analysis of Reusable Cells in UIKit</h3><h4>Basics</h4><p>In UIKit, UITableView is used to create lists, where cells are managed through a queue of reusable cells. This optimizes memory usage and speeds up the interface loading. Developers define a cell class that can be filled with various data and reused.</p><p>In UIKit, the key to effective data list management is the UITableView or UICollectionView class, which uses cells that are reusable thanks to the queuing mechanism. We set a cell template (or more often, we create a custom cell for greater control), which can be filled with various data and reused. This saves resources since UIKit does not create a new instance of the cell for each item but reuses existing ones.</p><pre>class SomeCustomTableViewCell: UITableViewCell {<br>    @IBOutlet weak var titleLabel: UILabel! // This can be anything, for simplicity, let it be a simple label<br>    <br>    // Here we configure the custom cell, for example<br>}</pre><h4>Problems and Solutions</h4><p>However, this approach has its pitfalls, such as the accidental display of incorrect data due to improper management of the state of the reusable cell. This is particularly frequent with images, but I won’t delve into why — this article is not about that, but about how to fix it.</p><p>To solve this problem, simply use the prepareForReuse() method to clean or reset the state of the cell before it is reused.</p><p>The prepareForReuse() method is called before reusing the custom cell SomeCustomTableViewCell to reset its state and prevent the display of incorrect data:</p><pre>class SomeCustomTableViewCell: UITableViewCell {<br>    @IBOutlet weak var titleLabel: UILabel! // This can be anything, for simplicity, let it be a simple label<br>    <br>    // Our magic method<br>    override func prepareForReuse() {<br>        super.prepareForReuse()<br>        titleLabel.text = nil // reset the state of the label<br>    }<br>    <br>    // Here we configure the custom cell, for example<br>}</pre><p>It is precisely in the prepareForReuse() method of the custom cell that I strongly recommend resetting the state for those elements that are clearly not displayed where they should be.</p><p>Additionally, this is a simplified example; in reality, you can reset virtually anything. For instance, if you have a Boolean variable that defaults to true but is set to false for some cells, you can also reset it in this method by simply reassigning the default state (not nil)</p><h3>SwiftUI and the New Declarative Approach</h3><p>In SwiftUI, there is no direct analog to the prepareForReuse() method used in UIKit to prepare reusable cells for their redisplay. This is due to fundamental differences in the approaches to managing the user interface between SwiftUI and UIKit.</p><p>In SwiftUI, the creation and updating of the interface are entirely declarative and depend on the current state of the data that controls the views. When data changes, SwiftUI automatically updates the relevant views. This means that we do not need to manually manage the reuse and cleaning of view states, as is done in UIKit.</p><p>To solve tasks similar to those addressed by prepareForReuse() in UIKit, in SwiftUI we typically use the following approaches:</p><ol><li><strong>Data Binding:</strong> Using data binding (@Binding) ensures that our view always reflects the current state of the data. This guarantees that each data update is immediately displayed in the interface.</li></ol><p>2. <strong>View State Management:</strong> Using @State or @ObservedObject to manage the local state within the view. These properties help manage temporary states inside the view.</p><p>3. <strong>Conditional Views:</strong> Views can be conditionally modified depending on the state of the data. This allows dynamic modification of interface components based on current data or state.</p><h4>Operating Principles</h4><p>As SwiftUI uses a declarative approach where the UI is defined by its state, not the process of its construction. This simplifies the creation and updating of the interface (but does not provide the low-level flexibility of UIKit):</p><pre>struct ContentView: View {<br>    var items = [&quot;Item 1&quot;, &quot;Item 2&quot;, &quot;Item 3&quot;]<br><br>    var body: some View {<br>        List(items, id: \.self) { item in<br>            Text(item)<br>        }<br>    }<br>}</pre><p><strong>Automatic Memory Management</strong></p><p>SwiftUI automatically manages memory, achieved through lazy data loading and idiomatic state management (e.g., @State and @ObservedObject), loading and unloading UI elements. Elements are removed from memory when they are out of the visible area and recreated as necessary, without explicit reuse:</p><pre>ScrollView {<br>    LazyVStack {<br>        ForEach(items, id: \.self) { item in<br>            Text(item)<br>        }<br>    }<br>}</pre><p>In this example, LazyVStack is used to create a vertical stack of elements that load only when they appear in the visible area of the ScrollView. This demonstrates how SwiftUI optimizes the use of memory and processing time by loading and unloading elements as needed.</p><h3>Comparison and Analysis</h3><p>SwiftUI manages resources and memory differently than UIKit. While in UIKit cells are reused to save resources, in SwiftUI rendering and memory management are based on the current visibility context of the components.</p><p><strong>Resource Optimization in SwiftUI:</strong> SwiftUI optimizes rendering and memory usage by dynamically loading and unloading interface elements as they appear and disappear from the visible area. This means that:</p><ol><li><strong>Lazy Loading:</strong> Like UITableView in UIKit, SwiftUI uses “lazy loading” for collections, such as List and ScrollView. This means that views are loaded and removed from memory as they scroll.</li><li><strong>Automatic Memory Management:</strong> In SwiftUI, there is no need to explicitly manage memory for views. The framework itself ensures that views that are not visible do not occupy resources. Components that have moved out of the user’s field of view can be removed from memory and recreated as necessary. However, if something goes wrong, debugging can be much more difficult than in UIKit.</li><li><strong>Efficient Updates:</strong> SwiftUI updates only those parts of the interface that have actually changed, thereby saving both computational and memory resources.</li></ol><h4>Comparison with UIKit:</h4><ul><li>In UIKit, we need to actively manage the reuse of cells, which can lead to errors (but provides more flexibility and is easier to debug) if the state of the cell is not properly cleared. This often happens, I must say!</li><li>In SwiftUI, the framework itself takes care of reuse and memory management, reducing the likelihood of errors related to incorrect data display or excessive memory consumption.</li></ul><p>Thus, SwiftUI can indeed clear views from memory, similar to UIKit, but does it more automatically, allowing developers to focus on application logic rather than low-level resource management. However, it should be noted that debugging errors in SwiftUI can be more challenging due to its high-level abstraction.</p><h3>For those not closely familiar with SwiftUI, a brief aside:</h3><h4>State Management in SwiftUI</h4><ul><li><strong>@State: </strong>This property wrapper is used to store and manage mutable state within a single view. SwiftUI automatically monitors changes in properties marked with @State, and redraws those parts of the interface that depend on this data.</li></ul><pre>struct ContentView: View {<br>    @State private var counter = 0<br><br>    var body: some View {<br>        Button(&quot;Click Me&quot;) {<br>            counter += 1<br>        }<br>        Text(&quot;Number of clicks: \(counter)&quot;)<br>    }<br>}</pre><p>In this example, each time the button is pressed, the value of counter increases, and the interface is automatically updated.</p><ul><li><strong>@Binding:</strong> Allows for a two-way binding between the state stored in one view and the view that needs to display this state or allow it to be modified. This maintains data synchronization across different parts of the interface.</li></ul><pre>struct ContentView: View {<br>    @State private var isOn = false<br><br>    var body: some View {<br>        ToggleView(isOn: $isOn)<br>    }<br>}<br><br>struct ToggleView: View {<br>    @Binding var isOn: Bool<br><br>    var body: some View {<br>        Toggle(&quot;Enable&quot;, isOn: $isOn)<br>    }<br>}</pre><p>Here, ToggleView receives a @Binding to the isOn state from ContentView, allowing it to change this state.</p><ul><li><strong>@ObservedObject:</strong> Used to create a reactive link between the view and an external reference type of data (usually a class) that conforms to the ObservableObject protocol. This is useful for more complex or shared states that need to be used by multiple views.</li></ul><pre>class UserData: ObservableObject {<br>    @Published var username = &quot;Guest&quot;<br>}<br><br>struct ContentView: View {<br>    @ObservedObject var userData = UserData()<br><br>    var body: some View {<br>        Text(&quot;User: \(userData.username)&quot;)<br>    }<br>}</pre><p>@Published inside ObservableObject automatically notifies views when data changes, necessitating an update.</p><h4>Memory Management and Data Loading in SwiftUI</h4><p>SwiftUI uses lazy loading and smart memory management to optimize performance. Interface elements are loaded only when they need to be displayed and are unloaded when they are no longer visible, saving resources.</p><pre>ScrollView {<br>    LazyVStack {<br>        ForEach(0..&lt;1000) { index in<br>            Text(&quot;Element \(index)&quot;)<br>                .onAppear {<br>                    print(&quot;Loading element \(index)&quot;)<br>                }<br>                .onDisappear {<br>                    print(&quot;Unloading element \(index)&quot;)<br>                }<br>        }<br>    }<br>}</pre><p>In this example, LazyVStack is used to create a vertical stack of elements that are loaded only when they appear in the visible area of the ScrollView. This demonstrates how SwiftUI optimizes the use of memory and processing time by loading and unloading elements as needed.</p><h3>Advantages and Disadvantages</h3><p>UIKit requires explicit management of cell reuse and state. SwiftUI, on the other hand, simplifies state and presentation management, minimizing the possibility of errors and speeding up development through automatic handling of states and memory.</p><p>This approach not only simplifies the work of developers but also minimizes risks associated with “sticking” of old data that could appear when using reusable cells in UIKit. SwiftUI essentially guarantees that the user interface is always up-to-date and corresponds to the current state of the data.</p><p>Unlike UIKit, SwiftUI uses a fully declarative approach, where the user interface is described by its current state, not by a sequence of commands to change it. This simplifies development and makes the code cleaner and more understandable.</p><h3>When Each Approach is Preferable</h3><p>UIKit remains the preferred choice <strong>for complex interactive and dynamically changing interfaces</strong> with a high level of customization.</p><p>SwiftUI is better suited for new projects with <strong>simple or moderately complex UIs</strong>, where the speed of development and support is important.</p><h3>Conclusion</h3><p>Understanding the differences in managing dynamic data and interfaces between UIKit and SwiftUI will help choose the most effective approach for each project. SwiftUI offers a progressive approach to building interfaces that can significantly simplify the development process and reduce the risks associated with managing the state of components.</p><p>The choice between UIKit and SwiftUI largely depends on the specifics of the project and the preferences of the development team. However, understanding the fundamental differences in approaches to managing dynamic data and interfaces can help determine the most effective methods of work for each specific application.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=132f6d17afcc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Higher-Order Functions in Swift and Advanced Collection Manipulation]]></title>
            <link>https://quasaryy.medium.com/higher-order-functions-in-swift-and-advanced-collection-manipulation-7f0fb345aa9f?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/7f0fb345aa9f</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[functional-programming]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Sun, 10 Mar 2024 17:16:24 GMT</pubDate>
            <atom:updated>2024-03-10T17:16:24.313Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lN4qoXPH3gVpCGh3jGc_mA.jpeg" /></figure><h3>Introduction</h3><p>Deciding on a title for this article was quite a task; it could well be dubbed “Functional Programming in Swift” or “Swift Collection Manipulation Tricks”. Each title fits, so feel free to pick your favorite :) Let’s dive in!</p><p>Higher-order functions are those that can take other functions as arguments or return them as results. Functions like flatMap, compactMap, and forEach certainly fit this bill as they accept a function as an argument.</p><ul><li>flatMap and compactMap take a function that&#39;s applied to each collection element, then &quot;flatten&quot; the result or eliminate any nil elements, respectively. On the other hand,</li><li>forEach executes a given function for each collection element.</li></ul><p>Other methods, while immensely useful for working with collections, don’t directly take a function as an argument, so they can’t be strictly classified as higher-order functions. Let’s categorize these under advanced manipulation techniques. For instance, dropFirst(), dropLast(), prefix(), and suffix() are used to obtain subsets of collections, enumerated() returns a sequence of (index, element) pairs, and zip() merges two sequences into a sequence of element pairs.</p><p>Now that we’ve covered the introductions, let’s look at some examples to see how these features significantly simplify our coding lives.</p><h4><strong>1. Using </strong><strong>map</strong></h4><p>map applies a given transformation to each collection element and returns an array of the results. It&#39;s the perfect tool for data transformation without the need for a for loop.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let squaredNumbers = numbers.map { $0 * $0 }<br>// Result: [1, 4, 9, 16, 25]</pre><h4>2. Applying filter</h4><p>filter lets you sift through a collection, returning only those elements that match a given condition.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let evenNumbers = numbers.filter { $0 % 2 == 0 }<br>// Result: [2, 4]</pre><h4>3. Utilizing reduce</h4><p>reduce merges all collection elements into a single value using a specified operation, making it a powerful data aggregation tool.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let sum = numbers.reduce(0, { $0 + $1 })<br>// Result: 15</pre><h4>4. Employing flatMap and compactMap</h4><p>flatMap applies a function to each collection element and returns a flattened array of results, automatically removing any nesting.</p><pre>let arrayOfArrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]<br>let flattened = arrayOfArrays.flatMap { $0 }<br>// Result: [1, 2, 3, 4, 5, 6, 7, 8, 9]</pre><p>compactMap is similar to map but additionally removes all nil values from the result, especially useful when working with optional values.swiftCopy code</p><pre>let possibleNumbers = [&quot;1&quot;, &quot;two&quot;, &quot;3&quot;, &quot;four&quot;, &quot;5&quot;]<br>let numbers = possibleNumbers.compactMap { Int($0) }<br>// Result: [1, 3, 5]</pre><h4>5. Iterating with forEach</h4><p>Instead of a traditional for loop, forEach provides a more concise way to iterate over collection elements.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>numbers.forEach { print($0) }</pre><h4>6. Checking Existence with contains()</h4><p>contains() quickly checks if a collection includes a specific element, returning true or false accordingly.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let hasThree = numbers.contains(3)<br>// Result: true</pre><h4>7. Accessing Elements with first and last</h4><p>first and last easily retrieve the first or last collection element, respectively.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>if let firstNumber = numbers.first {<br>    print(&quot;First element: \(firstNumber)&quot;)<br>}</pre><h4>8. Sorting with sorted()</h4><p>sorted() returns a new array with its elements sorted according to a provided condition.</p><pre>let numbers = [5, 3, 1, 2, 4]<br>let sortedNumbers = numbers.sorted()<br>// Result: [1, 2, 3, 4, 5]</pre><h4>9. Using dropFirst() and dropLast()</h4><pre>let numbers = [1, 2, 3, 4, 5]<br>let withoutFirstTwo = numbers.dropFirst(2)<br>// Result: [3, 4, 5]<br><br>let withoutLastTwo = numbers.dropLast(2)<br>// Result: [1, 2, 3]</pre><p>These functions “drop” the first or last N elements of a collection, useful when you need to ignore the beginning or end elements in data.</p><h4>10. Employing prefix() and suffix()</h4><p>prefix and suffix fetch a specified number of elements from the start or end of a collection, respectively.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let firstThree = numbers.prefix(3)<br>// Result: [1, 2, 3]<br><br>let lastThree = numbers.suffix(3)<br>// Result: [3, 4, 5]</pre><h4>11. Using enumerated()</h4><p>enumerated() returns a sequence of (index, element) pairs, useful when you need to access both the element and its index during iteration.</p><pre>let names = [&quot;Anna&quot;, &quot;Alex&quot;, &quot;Brian&quot;, &quot;Jack&quot;]<br>for (index, name) in names.enumerated() {<br>    print(&quot;Name \(index + 1): \(name)&quot;)<br>}</pre><h4>12. Combining Sequences with zip()</h4><p>zip() combines two sequences into a sequence of pairs, each consisting of corresponding elements from each sequence.</p><pre>let names = [&quot;Anna&quot;, &quot;Alex&quot;, &quot;Brian&quot;, &quot;Jack&quot;]<br>let scores = [75, 88, 90, 99]<br><br>for (name, score) in zip(names, scores) {<br>    print(&quot;\(name) scored \(score)&quot;)<br>}</pre><h4>13. Dividing with partition(by:)</h4><p>partition divides a collection into two parts, depending on whether the elements satisfy the condition provided to the function.</p><pre>var numbers = [30, 40, 20, 30, 30, 60, 10]<br>let pivot = numbers.partition(by: { $0 &gt; 30 })<br>// pivot is the index of the first element greater than 30<br><br>let lessThanOrEqualToThirty = numbers[..&lt;pivot]<br>let greaterThanThirty = numbers[pivot...]<br><br>print(lessThanOrEqualToThirty) // [30, 20, 30, 30, 10]<br>print(greaterThanThirty) // [40, 60]</pre><h4>14. Validating with allSatisfy()</h4><p>This function checks if all collection elements meet a given condition, returning true if so.</p><pre>let numbers = [2, 4, 6, 8, 10]<br>let allEven = numbers.allSatisfy { $0 % 2 == 0 }<br>// Result: true</pre><h4>15. Finding Extremes with min(by:) and max(by:)</h4><p>These functions find the minimum or maximum collection element based on a provided condition.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>if let min = numbers.min(by: { $0 &lt; $1 }) {<br>    print(&quot;Minimum value: \(min)&quot;)<br>}<br><br>if let max = numbers.max(by: { $0 &lt; $1 }) {<br>    print(&quot;Maximum value: \(max)&quot;)<br>}</pre><h4>16. Aggregating with reduce(into:)</h4><p>reduce(into:) is an alternative to reduce that can be more efficient in certain cases as it allows you to directly modify the accumulating result, potentially reducing the number of intermediate objects created.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let sum = numbers.reduce(into: 0) { $0 += $1 }<br>// Result: 15</pre><h4>17. Leveraging Lazy Collections</h4><p>Swift offers mechanisms for “lazy” collection computations, which postpone potentially expensive operations until they’re actually needed.</p><pre>let numbers = [1, 2, 3, 4, 5]<br>let squares = numbers.lazy.map { $0 * $0 }<br>// Squares will be computed only when accessed</pre><h4>18. Chunking</h4><pre>let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]<br>let chunkSize = 3<br>let chunks = stride(from: 0, to: numbers.count, by: chunkSize).map {<br>    Array(numbers[$0..&lt;min($0 + chunkSize, numbers.count)])<br>}<br>// Result: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]</pre><h4>19. Using uniqued()</h4><pre>let repeatedNumbers = [1, 1, 2, 2, 3, 3, 3]<br>let uniqueNumbers = repeatedNumbers.uniqued()<br>// Result: [1, 2, 3]</pre><h4>20. Selecting Random Elements</h4><p>Swift offers the ability to select random elements from a collection, useful for creating diverse user interfaces or in testing.</p><pre>let colors = [&quot;red&quot;, &quot;green&quot;, &quot;blue&quot;, &quot;yellow&quot;]<br>if let randomColor = colors.randomElement() {<br>    print(&quot;Random color: \(randomColor)&quot;)<br>}</pre><h4>21. Combining Elements</h4><p>Combining collection elements allows for creating all possible unique pairs or groups of elements, useful in statistical analysis, gaming, or optimization tasks.</p><pre>let fruits = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;]<br>let fruitCombinations = fruits.combinations(ofCount: 2).map { $0.joined(separator: &quot; and &quot;) }<br>for combo in fruitCombinations {<br>    print(combo)<br>}<br>// Results: &quot;apple and banana&quot;, &quot;apple and cherry&quot;, &quot;banana and cherry&quot;</pre><h4>22. New Possibilities with enum (A Bit Off-Topic but Useful)</h4><p><strong>Before:</strong></p><pre>let someString: String<br>switch enumValue {<br>case .firstValue:<br>    someString = &quot;First value&quot;<br>case .secondValue:<br>    someString = &quot;Second value&quot;<br>}</pre><p><strong>After:</strong></p><pre>let someString = switch enumValue {<br>case .firstValue: &quot;First value&quot;<br>case .secondValue: &quot;Second value&quot;<br>}</pre><p>The list could go on, covering all the methods for working with collections in Swift, but I’ve chosen the most commonly used and useful ones.</p><h3>Conclusion</h3><p>Swift’s functional capabilities greatly simplify working with collections, making the code more readable and concise. Imagine how much more code would be needed without these methods. This is why employing higher-order functions like map, filter, reduce, and others allows for efficient data processing and transformation, reducing boilerplate code and speeding up the development process.</p><p>Leveraging Swift’s functional features for collection manipulation can greatly streamline data handling, making your application more efficient.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7f0fb345aa9f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SOLID Principles in the Context of iOS Development]]></title>
            <link>https://quasaryy.medium.com/solid-principles-in-the-context-of-ios-development-e1bf8bf40e16?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1bf8bf40e16</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[solid]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Wed, 28 Feb 2024 21:27:33 GMT</pubDate>
            <atom:updated>2024-02-28T21:27:33.814Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/730/1*NWk4qGjuBcszMaCrez1iQA.jpeg" /></figure><h3>Introduction</h3><p>SOLID stands for five key principles of object-oriented programming and design aimed at making software more understandable, flexible, and maintainable. These principles are particularly relevant in the context of iOS development, where the quality of architecture directly impacts the performance, scalability, and maintainability of applications.</p><h4>1. Single Responsibility Principle</h4><p>The principle states that a class should have only one task or area of responsibility. In the context of iOS, this could mean separating UI management logic, business logic, and data handling into different classes.</p><p><strong>Example:</strong></p><p>Consider a UIViewController initially responsible for updating UI, navigation, and network requests. By applying the Single Responsibility Principle, you can delegate network requests to a separate service class and navigation to a coordinator or router.</p><pre>class UserProfileViewController: UIViewController {<br>    var userProfileService: UserProfileService!<br><br>    func updateProfile() {<br>        userProfileService.fetchProfile { [weak self] profile in<br>            self?.updateUI(with: profile)<br>        }<br>    }<br><br>    private func updateUI(with profile: UserProfile) {<br>        // UI update logic<br>    }<br>}<br><br>class UserProfileService {<br>    func fetchProfile(completion: @escaping (UserProfile) -&gt; Void) {<br>        // Network request to fetch user profile<br>    }<br>}</pre><h4><strong>2. Open/Closed Principle</strong></h4><p>This principle asserts that software entities should be open for extension but closed for modification. This means you should design your classes in a way that adding new functionality doesn’t require changing existing code.</p><p><strong>Example:</strong></p><p>Use protocols and extensions to define common behavior that can be extended without altering the original protocol code.</p><pre>protocol Drawable {<br>    func draw()<br>}<br><br>class Circle: Drawable {<br>    func draw() {<br>        // Circle drawing implementation<br>    }<br>}<br><br>class Square: Drawable {<br>    func draw() {<br>        // Square drawing implementation<br>    }<br>}<br><br>func drawShapes(shapes: [Drawable]) {<br>    shapes.forEach { $0.draw() }<br>}</pre><p>An experienced eye might notice that the `drawShapes` function is declared outside of any class.</p><p>The example below illustrates how you can use the `drawShapes` function, defined outside of a class, to draw an array of shapes. This approach can be convenient when you have a collection of shapes and need to perform an operation on them without adding a method to the base class or creating a separate object for drawing.</p><pre>func drawShapes(shapes: [Drawable]) {<br>    shapes.forEach { $0.draw() }<br>}<br><br>let circle = Circle()<br>let square = Square()<br>let shapes: [Drawable] = [circle, square]<br><br>drawShapes(shapes: shapes)</pre><h4>3. Liskov Substitution Principle</h4><p>This principle states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. In the context of iOS, this means inheritance should be used properly, and subclasses should not change the behavior of the base class in a way that could undermine expected functionality.</p><p><strong>Example:</strong></p><p>When creating a subclass of UIViewController, ensure it adheres to the base class’s contract and does not alter its intended behavior, such as by overriding lifecycle methods without calling super.</p><pre>class BaseViewController: UIViewController {<br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        setupViews()<br>        bindViewModel()<br>    }<br><br>    func setupViews() {<br>        // Basic view setup<br>    }<br><br>    func bindViewModel() {<br>        // Binding ViewModel to the view<br>    }<br>}<br><br>class CustomViewController: BaseViewController {<br>    override func setupViews() {<br>        super.setupViews()<br>        // Additional view setup<br>    }<br><br>    override func bindViewModel() {<br>        super.bindViewModel()<br>        // Additional ViewModel binding<br>    }<br>}</pre><h4>4. Interface Segregation Principle</h4><p>The principle asserts that clients should not be forced to depend on interfaces they do not use. In iOS development, this often means breaking down large protocols into smaller, more specialized ones.</p><p><strong>Example:</strong></p><p>Instead of a single large DataSource protocol with methods for handling different types of data, create several smaller protocols such as UserDataSource, ProductDataSource, etc., each responsible for a specific data type.</p><pre>protocol UserDataSource {<br>    func fetchUser(id: String, completion: @escaping (User) -&gt; Void)<br>}<br><br>protocol ProductDataSource {<br>    func fetchProduct(id: String, completion: @escaping (Product) -&gt; Void)<br>}<br><br>class APIService: UserDataSource, ProductDataSource {<br>    func fetchUser(id: String, completion: @escaping (User) -&gt; Void) {<br>        // Implementation<br>    }<br><br>    func fetchProduct(id: String, completion: @escaping (Product) -&gt; Void) {<br>        // Implementation<br>    }<br>}</pre><h4>5. Dependency Inversion Principle</h4><p>This principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Furthermore, abstractions should not depend on details; details should depend on abstractions.</p><p><strong>Example:</strong></p><p>Instead of having a high-level component like UserProfileViewModel directly depend on a specific data service, use a UserProfileServiceProtocol. This allows for easy replacement of the service implementation without changing the ViewModel.</p><pre>protocol UserProfileServiceProtocol {<br>    func fetchProfile(completion: @escaping (UserProfile) -&gt; Void)<br>}<br><br>class UserProfileService: UserProfileServiceProtocol {<br>    func fetchProfile(completion: @escaping (UserProfile) -&gt; Void) {<br>        // Network request to fetch user profile<br>    }<br>}<br><br>class UserProfileViewModel {<br>    var userProfileService: UserProfileServiceProtocol!<br><br>    init(service: UserProfileServiceProtocol) {<br>        self.userProfileService = service<br>    }<br><br>    func loadUserProfile() {<br>        userProfileService.fetchProfile { profile in<br>            // UI update<br>        }<br>    }<br>}</pre><h4>LSP and ISP (items 3 and 4) deserve special attention.</h4><p>Let’s delve deeper into these two principles with more detailed examples, focusing particularly on potential issues and specific iOS development tasks.</p><p><strong>Clarification of the Liskov Substitution Principle (LSP)</strong></p><p>The Liskov Substitution Principle asserts that objects of a base class should be replaceable with objects of derived classes without altering the program’s correctness. Violating this principle in iOS development can lead to unpredictable behavior, especially when using UIKit components or other Apple frameworks.</p><p><strong>Example of LSP violation:</strong></p><p>Imagine we have a base class Vehicle with a method startEngine(). The class Car inherits from Vehicle and overrides the startEngine() method.</p><pre>class Vehicle {<br>    func startEngine() {<br>        print(&quot;Starting the engine of the vehicle&quot;)<br>    }<br>}<br><br>class Car: Vehicle {<br>    override func startEngine() {<br>        super.startEngine()<br>        print(&quot;Starting the car engine with system checks&quot;)<br>        // Additional actions specific to the car<br>    }<br>}<br><br>class ElectricCar: Vehicle {<br>    override func startEngine() {<br>        // Electric cars do not have a conventional engine<br>        fatalError(&quot;Electric cars do not have a conventional engine to start&quot;)<br>    }<br>}</pre><p>Using ElectricCar in a context where an engine start is expected violates LSP, as ElectricCar changes the expected behavior of the base class, potentially leading to program crashes.</p><p><strong>Clarification of the Interface Segregation Principle (ISP)</strong></p><p>The Interface Segregation Principle asserts that clients should not be forced to depend on interfaces they do not use. This is particularly relevant in iOS development, where modularity and code reusability are critical for creating scalable and maintainable applications.</p><p><strong>Example of applying ISP in iOS:</strong></p><p>In an iOS social networking app, there might be various functionalities for handling media: uploading images, videos, and audio. Instead of one large MediaHandling protocol encompassing all these operations, it’s better to define several specialized protocols.</p><pre>protocol ImageHandling {<br>    func uploadImage(_ image: UIImage)<br>}<br><br>protocol VideoHandling {<br>    func uploadVideo(_ video: URL)<br>}<br><br>protocol AudioHandling {<br>    func uploadAudio(_ audio: URL)<br>}<br><br>class SocialMediaService: ImageHandling, VideoHandling {<br>    func uploadImage(_ image: UIImage) {<br>        // Image upload implementation<br>    }<br><br>    func uploadVideo(_ video: URL) {<br>        // Video upload implementation<br>    }<br><br>    // AudioHandling is not implemented as this service does not handle audio<br>}</pre><p>Thus, SocialMediaService implements only those protocols whose functionality it requires, adhering to ISP. This simplifies testing and maintenance, as well as enhancing flexibility for future expansions.</p><h4>Let’s also examine classic SOLID anti-patterns</h4><p>These can arise from incorrect interpretation or application of SOLID principles, along with brief examples for better understanding.</p><p><strong>Anti-pattern 1: “Monolithic Monster”</strong></p><p>This anti-pattern occurs when developers rigidly adhere to all SOLID principles, creating overly complex architectures even for small projects.</p><p><strong>Example:</strong></p><pre>protocol Shape {<br>    func draw()<br>    func calculateArea()<br>    func resize()<br>    func rotate()<br>    // and so on...<br>}</pre><p>This violates the Single Responsibility Principle, as the Shape protocol carries too many responsibilities.</p><p><strong>Anti-pattern 2: “Interface Pollution”</strong></p><p>This anti-pattern involves creating superfluous methods in an interface that are not used in implementations, leading to unnecessary dependencies and complexity.</p><p><strong>Example:</strong></p><pre>protocol Worker {<br>    func work()<br>    func eat()<br>    func sleep()<br>}<br><br>class Engineer: Worker {<br>    func work() {<br>        // Engineer&#39;s work implementation<br>    }<br>    <br>    // eat() and sleep() methods are never used for an engineer<br>}</pre><p>In this case, the Worker protocol is polluted with methods that make no sense for all its implementations.</p><p><strong>Anti-pattern 3: “God Object”</strong></p><p>This anti-pattern involves creating classes that violate the Single Responsibility Principle, becoming overly powerful objects that perform too many tasks.</p><p><strong>Example:</strong></p><pre>class Application {<br>    func run() {<br>        // Application startup<br>    }<br>    <br>    func configure() {<br>        // Application configuration<br>    }<br>    <br>    func handleUserInput() {<br>        // User input handling<br>    }<br>    <br>    // and so on...<br>}</pre><p>The Application class performs too many different tasks, making it difficult to maintain and test.</p><p><strong>Anti-pattern 4: “Tunnel Vision”</strong></p><p>This anti-pattern occurs when developers follow SOLID principles so strictly that they ignore the specific requirements and context of the project.</p><p><strong>Example:</strong></p><pre>protocol ReportGenerator {<br>    func generateReport(data: [Any]) -&gt; String<br>}<br><br>class PDFReportGenerator: ReportGenerator {<br>    func generateReport(data: [Any]) -&gt; String {<br>        // PDF report generation<br>    }<br>}</pre><p>If the requirement is to generate not PDF but, for example, HTML reports, the application of this code might be unnecessary and inefficient.</p><p>Avoid SOLID anti-patterns by adhering to the principles with consideration for the project context and requirements, aiming for a balance between flexibility and architectural cleanliness.</p><h4>Transitioning smoothly to the next part of the article:</h4><p>Let’s discuss when strict adherence to these principles is desirable and when it might be impractical or even disadvantageous.</p><h4>When Adhering to SOLID Principles Is Appropriate</h4><ul><li><strong>Large and long-term projects: </strong>In large projects expecting long-term development and support, adhering to SOLID helps ensure codebase flexibility, scalability, and maintainability.</li><li><strong>Teamwork:</strong> In a team development setting, especially with many developers, SOLID provides a common design language and helps avoid code conflicts.</li><li><strong>Complex domain areas:</strong> In applications with particularly complex business logic that is prone to frequent changes, SOLID principles facilitate a flexible architecture that can adapt to new requirements.</li></ul><h4>When Adhering to SOLID Principles Might Be Impractical</h4><ul><li><strong>Prototyping and MVP:</strong> In the early stages of development, when the goal is rapid validation of an idea or concept, strict adherence to SOLID can slow down the process. Here, development speed is prioritized over architectural purity.</li><li><strong>Small or one-off projects:</strong> For small projects or scripts not intended for scaling or long-term use, strict architectural principles may be superfluous.</li><li><strong>Excessive abstraction and complexity:</strong> Sometimes, too strict adherence to SOLID leads to unnecessary abstraction and increased system complexity. This can make the code harder to understand and maintain, especially if the development team is small or changing.</li></ul><h4>Special Considerations</h4><ul><li><strong>Pragmatism vs. Purism:</strong> It’s important to find a balance between strict adherence to principles and a pragmatic approach to development. In some cases, deviating from one of the principles to simplify a solution or improve performance may be justified.</li><li><strong>Iterative Improvement:</strong> Instead of trying to adhere to all SOLID principles from the start, developers can apply them gradually as the project grows and evolves.</li><li><strong>Context and Common Sense:</strong> Always consider the specific context of the project and the technologies used. In some situations, certain principles may be less relevant, or their application may lead to unjustified time and resource expenditures.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1bf8bf40e16" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Detailed Analysis of JSON Errors in iOS: From Problem to Solution]]></title>
            <link>https://quasaryy.medium.com/a-detailed-analysis-of-json-errors-in-ios-from-problem-to-solution-0ea981e7f023?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/0ea981e7f023</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[json]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Sat, 24 Feb 2024 19:47:14 GMT</pubDate>
            <atom:updated>2024-02-24T20:09:27.543Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kuyKvLQs_MQ8VcEGadK_ww.jpeg" /></figure><h3>Introduction</h3><p>In the era of mobile applications and cloud technologies, JSON has become the de facto standard for data exchange between client and server. For iOS development, it&#39;s particularly crucial to be proficient in handling JSON, as most network requests return data in this format. In this article, I will delve into JSON decoding in Swift, explore potential errors that may arise during this process, and discuss effective strategies for diagnosing and resolving these issues.</p><h3>A Brief Digression</h3><p>Many of you are probably familiar with the scenario where, when working with URLSession, for instance, we rely on the generic error description error.localizedDescription. Consequently, if some data is missing, we encounter vague error messages like &quot;The data couldn’t be read because it is missing&quot; or &quot;The operation couldn’t be completed,&quot; leaving us guessing where and at which step the JSON decoding went awry. One approach is to inspect the server response and compare all properties with the data model, which can become unbearable, especially with large or numerous models and frequent errors. This is where DecodingError comes to our rescue, and we&#39;ll discuss it in more detail.</p><h3>Understanding JSONDecoder in Swift</h3><p>Swift offers a powerful and convenient tool for working with JSON - JSONDecoder. This class allows for decoding data from the JSON format into custom data types that conform to the Decodable protocol. The decoding process is typically straightforward and doesn&#39;t require writing a lot of boilerplate code:</p><pre>let decoder = JSONDecoder()<br>let decodedData = try decoder.decode(MyModel.self, from: jsonData)</pre><p>Despite its apparent simplicity, errors can occur during the decoding process that are crucial to diagnose correctly.</p><h3>Common Decoding Errors and Their Diagnosis</h3><p><strong>1 KeyNotFound</strong></p><p>A frequent error encountered is DecodingError.keyNotFound, which occurs when a key expected in the corresponding data model is missing in the JSON. Here&#39;s an example illustrating this error:</p><pre>struct User: Decodable {<br>    var name: String<br>    var age: Int<br>}<br><br>// JSON missing the &#39;age&#39; key<br>let jsonData = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;John&quot;<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>do {<br>    let user = try JSONDecoder().decode(User.self, from: jsonData)<br>} catch DecodingError.keyNotFound(let key, _) {<br>    print(&quot;Missing key: \(key.stringValue)&quot;)<br>} catch {<br>    print(&quot;Another error occurred: \(error)&quot;)<br>}</pre><p>DecodingError.keyNotFound provides immediate feedback on the specific missing key in the JSON, simplifying the debugging process.</p><p>For those unfamiliar with the notation used here:</p><pre>let jsonData = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;John&quot;<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!</pre><p>Quick clarification - this is an example where we create JSON within the app (important! as the examples provided henceforth simulate JSON not fetched from a server, but as if it were embedded within the app). In this example:</p><p>- <strong>&quot;&quot;&quot;</strong> - a multi-line string literal in Swift, allowing for convenient representation of string data, including JSON formatting.<br>- <strong>{ &quot;name&quot;: &quot;John&quot; }</strong> - a string representation of a JSON object.<br>- <strong>.data(using: .utf8)!</strong> - a method called on a string (here, a multi-line literal containing JSON). This method attempts to encode the string into a Data format using the specified encoding (UTF-8). Note that this method returns an optional Data? since the encoding process may fail if the string contains characters that cannot be represented in the chosen encoding. The `!` operator (forced unwrapping) is used to forcefully extract the value from the optional, which should be used with caution as attempting to unwrap a nil value will result in a runtime error. However, it is suitable for educational purposes.</p><p>Thus, <strong>.data(using: .utf8)! </strong>converts the string representation of JSON into binary Data format, which can then be used for decoding into Swift structures or classes using JSONDecoder.</p><p><strong>2 TypeMismatch</strong></p><p>The DecodingError.typeMismatch error occurs when the type of a value in the JSON does not match the property type in the data model. For instance, if the model expects an Int, but the JSON provides a string:</p><pre>// JSON with incorrect data type for &#39;age&#39;<br>let jsonData = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;John&quot;,<br>    &quot;age&quot;: &quot;30&quot; // &#39;age&#39; should be Int, not String<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>do {<br>    let user = try JSONDecoder().decode(User.self, from: jsonData)<br>} catch DecodingError.typeMismatch(_, let context) {<br>    print(&quot;Type mismatch: \(context.debugDescription)&quot;)<br>} catch {<br>    print(&quot;Another error occurred: \(error)&quot;)<br>}</pre><p>DecodingError.typeMismatch provides a specific indication of the type error, unlike the vague &quot;The operation couldn’t be completed&quot; message.</p><p><strong>3 ValueNotFound</strong></p><p>DecodingError.valueNotFound occurs when an expected value is not found. For example, if an optional value is missing in the JSON and the decoder attempts to decode it as non-optional:</p><pre>struct User: Decodable {<br>    var name: String<br>    var age: Int? // &#39;age&#39; is now optional<br>}<br><br>// JSON missing the &#39;age&#39; key<br>let jsonData = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;John&quot;<br>}<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>do {<br>    let user = try JSONDecoder().decode(User.self, from: jsonData)<br>} catch DecodingError.valueNotFound(let type, _) {<br>    print(&quot;Value of type \(type) not found&quot;)<br>} catch {<br>    print(&quot;Another error occurred: \(error)&quot;)<br>}</pre><p>In this case, an error would not occur because &#39;age&#39; is now optional. However, if &#39;age&#39; were mandatory, the decoder would generate DecodingError.valueNotFound.</p><p><strong>4 DataCorrupted</strong></p><p>DecodingError.dataCorrupted indicates that the data is corrupted and cannot be decoded. This can happen, for instance, when the JSON is malformed:</p><pre>// Incorrect JSON<br>let jsonData = &quot;&quot;&quot;<br>{<br>    &quot;name&quot;: &quot;John,<br>    &quot;age&quot;: 30<br>&quot;&quot;&quot;.data(using: .utf8)!<br><br>do {<br>    let user = try JSONDecoder().decode(User.self, from: jsonData)<br>} catch DecodingError.dataCorrupted(let context) {<br>    print(&quot;Data corrupted: \(context.debugDescription)&quot;)<br>} catch {<br>    print(&quot;Another error occurred: \(error)&quot;)<br>}</pre><h3>In-Depth Analysis of Decoding Errors</h3><p>For a more thorough analysis of decoding errors, one can utilize the error context (DecodingError.Context). This context provides information about the data path (codingPath) where the error occurred and a description of the error (debugDescription).</p><pre>do {<br>    let user = try JSONDecoder().decode(User.self, from: jsonData)<br>} catch let error as DecodingError {<br>    switch error {<br>    case .keyNotFound(let key, let context):<br>        let path = context.codingPath.map { $0.stringValue }.joined(separator: &quot; -&gt; &quot;)<br>        print(&quot;Missing key &#39;\(key.stringValue)&#39; at path \(path), reason: \(context.debugDescription)&quot;)<br>    case .typeMismatch(let type, let context):<br>        let path = context.codingPath.map { $0.stringValue }.joined(separator: &quot; -&gt; &quot;)<br>        print(&quot;Type &#39;\(type)&#39; mismatch at path \(path), reason: \(context.debugDescription)&quot;)<br>    case .valueNotFound(let type, let context):<br>        let path = context.codingPath.map { $0.stringValue }.joined(separator: &quot; -&gt; &quot;)<br>        print(&quot;Value of type &#39;\(type)&#39; not found at path \(path), reason: \(context.debugDescription)&quot;)<br>    case .dataCorrupted(let context):<br>        let path = context.codingPath.map { $0.stringValue }.joined(separator: &quot; -&gt; &quot;)<br>        print(&quot;Data corrupted at path \(path), reason: \(context.debugDescription)&quot;)<br>    @unknown default:<br>        print(&quot;Unknown decoding error: \(error.localizedDescription)&quot;)<br>    }<br>} catch {<br>    print(&quot;Another error occurred: \(error)&quot;)<br>}</pre><p>This approach not only precisely identifies the location of the error in the data but also clarifies its nature, significantly simplifying the debugging and error correction process!</p><h3>Practical Tips for Error Handling</h3><p><strong>1. Use optional types for non-mandatory data</strong>: If some data may be absent in the JSON, corresponding properties in your data model should be optional.</p><p><strong>2. Carefully review data models:</strong> Ensure that the data types in your model match those in the JSON, including basic types and nested models.</p><p><strong>3. Log detailed error information:</strong> When handling decoding errors, log as much information as possible, including the data path and error description, to facilitate problem diagnosis.</p><h3>Create a Separate Network Manager</h3><p>To avoid rewriting the extensive error handling code block from the &quot;In-Depth Analysis of Decoding Errors&quot; section every time JSONDecoder() is called, consider creating a universal network manager through a singleton with an escaping closure:</p><pre>completion: @escaping (Result&lt;T, NetworkError&gt;) -&gt; Void</pre><p>As you can see, I propose using a custom NetworkError instead of a standard error. You can create a simple enum in the same file before the class (which can be extended in the future for server response errors like 401, etc.):</p><pre>enum NetworkError: Error {<br>    case decodingError(String) // Case for decoding errors<br>    case other(Error) // Case for other errors<br>}</pre><p>Integrate error handling into the network manager, possibly through a private method for convenience. When calling the main method from the network manager, use a switch to check .success and .failure. For .failure, use another switch to check these two cases and, for example, print the error through print or a <a href="https://medium.com/@quasaryy/lifehack-making-logging-more-convenient-in-swift-5bb5fc5e3a97">basic Logger</a> to the console.</p><p>A very simple example for .failure:</p><pre>case .failure(let error):<br>    switch error {<br>    case .decodingError(let errorMessage):<br>        Logger.log(&quot;Decoding error: \(errorMessage)&quot;)<br>    case .other(let error):<br>        Logger.logErrorDescription(error)<br>    }</pre><p>Ideally, this block should also be abstracted into a separate manager to avoid rewriting even these few lines for every call. Moreover, as your enum of errors expands, it will contain a lot of code...</p><p>The topic of a network manager is quite deep, and one could create a versatile monster capable of handling errors, building queues, or sequencing specific calls, and integrate a JWT manager core (if working with authentication) and much more. If you&#39;re interested in this topic, let me know in the comments, so I understand whether to write about it or not. Writing a good network manager isn&#39;t as simple as it seems, and writing a good article about it is even harder.</p><h3>Conclusion</h3><p>Proper handling of JSON decoding errors in iOS development is crucial for creating reliable and robust applications. Understanding how JSONDecoder works and being able to thoroughly analyze and correct errors will help you avoid many future problems, improving not only your code quality but also saving a lot of time and nerves. I hope this article will make you more confident in working with JSON in Swift and make your development process more efficient and enjoyable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0ea981e7f023" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Protecting iOS Apps from Reverse Engineering]]></title>
            <link>https://quasaryy.medium.com/protecting-ios-apps-from-reverse-engineering-d8caf21aaf68?source=rss-2d4b2b0b78c7------2</link>
            <guid isPermaLink="false">https://medium.com/p/d8caf21aaf68</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[Yury Lebedev]]></dc:creator>
            <pubDate>Sat, 17 Feb 2024 17:13:01 GMT</pubDate>
            <atom:updated>2024-02-17T17:13:01.942Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q7WXfUeyYrIAg7-__RfuRg.png" /></figure><h3>Introduction</h3><p>Reverse engineering of apps is a big scare for iOS developers. It can lead to leaks of smart ideas, fake apps, bad software, and other troubles. In this article, we’ll chat about ways and doings that can keep your app safe from reverse engineering.</p><h4>1 Using Final Classes</h4><p>Classes marked as final in Swift can’t be inherited or changed, making it hard for bad folks to mess with the class. They also make things run faster because the compiler can make method calls more efficient.</p><p><strong>Code example:</strong></p><pre>final class SecureClass {<br>    func secureMethod() {<br>        // Safe code here<br>    }<br>}</pre><h4>2 Finding Jailbreak</h4><p>Apps need to figure out if jailbreak tools are on the device. You can do this by checking for common jailbreak paths or unusual libraries. If jailbreak is found, the app can stop working or take other safety steps.</p><p><strong>Check example:</strong></p><pre>func isJailbroken() -&gt; Bool {<br>    if FileManager.default.fileExists(atPath: &quot;/Applications/Cydia.app&quot;) ||<br>       FileManager.default.fileExists(atPath: &quot;/Library/MobileSubstrate/MobileSubstrate.dylib&quot;) {<br>        return true<br>    } else {<br>        return false<br>    }<br>}</pre><h4>3 Hiding Code and Obfuscation</h4><p>Use tools to make code confusing, which makes it hard to analyze. Ways include changing names of variables and functions, removing metadata, using indirect calling methods, and so on.</p><p><strong>Code obfuscation example:</strong></p><pre>// Original string<br>let apiKey = &quot;mySecretAPIKey123&quot;<br><br>// Scrambled string<br>let obfuscatedApiKey = String(apiKey.reversed())</pre><h4>4 Control Flow Obfuscation:</h4><p><strong>Example of changing program execution logic:</strong></p><pre>func complexLogic() {<br>    #if DEBUG<br>    print(&quot;Debug mode&quot;)<br>    #else<br>    let a = 3<br>    let b = a * 2<br>    print(&quot;Release with altered control flow \(b)&quot;)<br>    #endif<br>}</pre><p>Note that using #if DEBUG will not allow the wrapped code into production.</p><h4>5 Anti-Debugging Techniques:</h4><p><strong>Example of using ptrace to prevent debugging:</strong></p><pre>import Darwin<br><br>func disableDebugging() {<br>    let PT_DENY_ATTACH = 31<br>    ptrace(PT_DENY_ATTACH, 0, 0, 0)<br>}</pre><p>Darwin is the OS core for macOS and iOS. It gives low-level functions like file, network, and system operations. Using Darwin in Swift lets you access these system calls and functions, like using standard C libraries on Apple platforms.</p><p>ptrace is a system call for debugging and altering other programs. It allows one process to “attach” to another, control its execution, change its memory and registers, etc. It’s a powerful tool used in debuggers like GDB or LLDB.</p><p>PT_DENY_ATTACH is a specific request sent through ptrace to stop debuggers from attaching to a process. When ptrace(PT_DENY_ATTACH, 0, 0, 0) is called, the app tells the system not to let debuggers attach. This makes it harder for bad folks to use debuggers to analyze or change how an app works while it’s running.</p><h4>6 Data Encryption:</h4><p><strong>Example of encrypting data with CryptoKit:</strong></p><pre>import CryptoKit<br><br>func encryptString(string: String, key: String) -&gt; String? {<br>    // Encrypting a string<br>}</pre><h4>7 Runtime Integrity Checks:</h4><p><strong>Code integrity check example:</strong></p><pre>func verifyIntegrity() -&gt; Bool {<br>    // Check if the binary file is unchanged<br>    return true // Return check result<br>}</pre><h4>8 Code Certification and Authentication</h4><p>Make sure your app uses mechanisms like SSL pinning and ATS (App Transport Security) for a safe connection and to prevent MITM (man-in-the-middle) attacks.</p><h4>9 Regular Updates and Patching</h4><p>Stay updated on the latest vulnerabilities and security updates. Regularly update your app to fix known weaknesses and improve defense mechanisms.</p><h4>10 Deeper Understanding</h4><p>For a deeper grasp of protecting iOS apps from reverse engineering, it’s key to explore more methods and techniques. Here are some detailed strategies and practices:</p><h4><strong>10.1 Code Hardening:</strong></h4><p>Techniques that make an app’s executable file tougher against changes and analysis. Examples include:</p><ul><li><strong>Checksums</strong>: Use checksums to detect modifications in your app.</li><li><strong>Runtime Integrity Checks</strong>: Implement runtime checks to find out if the code or app behavior has been changed.</li></ul><h4><strong>10.2 Encryption Strategies:</strong></h4><p>Encrypt data and resources in the app to make extracting and analyzing information difficult.</p><ul><li><strong>File-level Encryption</strong>: Encrypt important files in your app.</li><li><strong>String Encryption</strong>: Encrypt strings and API keys in your code so they’re not easily accessible (example above). Or use Keychain for storing important data.</li></ul><h4><strong>10.3 Advanced Obfuscation Techniques:</strong></h4><p>Beyond basic obfuscation, use complex methods to beef up code security.</p><ul><li><strong>Control Flow Obfuscation</strong>: Changes instruction order, making it hard to understand the program’s logic (example above).</li><li><strong>Symbol Stripping</strong>: Remove debug symbols and names to make understanding the app’s structure and functions difficult.</li></ul><h4><strong>10.4 Anti-Tampering Mechanisms:</strong></h4><p>Implement mechanisms that detect and respond to attempts to change the app’s code.</p><ul><li><strong>Self-Healing Code</strong>: Design code that can detect changes and restore its original state.</li><li><strong>Anti-Debugging</strong>: Use techniques that make debugging the app difficult for potential bad actors (example above).</li></ul><h4><strong>10.5 Running Environment and Surroundings:</strong></h4><p>Assessing and reacting to the environment where the app runs is also crucial.</p><ul><li><strong>Runtime Environment Checks</strong>: Check if the runtime environment has been modified, like detecting emulators or jailbreaks (example above).</li><li><strong>Geofencing and Time checks</strong>: Determine if the app is being used in a suspicious or unusual geographic area or time.</li></ul><h4><strong>10.6 Using Third-Party Tools and Services:</strong></h4><p>There are specialized tools and services that can help protect apps.</p><p>Integrate app and device management solutions. Use trusted libraries and frameworks specialized in app security (there are plenty, won’t list them here, there’s Google, but the downside is you’ll depend on them, and they’re not free if we’re talking about a serious solution).</p><h4>Conclusion:</h4><p>Using final classes, detecting Jailbreak, code obfuscation, code certification, and regular updates are just some of the measures that can keep your app safe from bad actors. It’s important to always be on the lookout for new attack and defense methods to keep your app secure and reliable.</p><p>These examples show the variety of methods and approaches that can be used to protect apps from reverse engineering. It’s important to mix and match them to your specific app and security needs to ensure maximum protection. And remember, security isn’t a one-time action but a continuous process of updating and improving defense measures.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d8caf21aaf68" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>