<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>kean.blog</title>
    <description>Articles by Alexander Grebenyuk (@kean) on programming for Apple platforms (iOS, Swift, Objective-C, SwiftUI)</description>
    <link>https://kean.blog/</link>
    <atom:link href="https://kean.blog/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 23 Jun 2025 14:08:21 +0000</pubDate>
    <lastBuildDate>Mon, 23 Jun 2025 14:08:21 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Claude Code Experience</title>
        <description>&lt;p&gt;There’s a lot of anxiety around AI coding, but what people often fear is uncertainty. The best way to address it is through action. These tools aren’t going away, so I decided to give one of the latest ones a fair shot this week.&lt;/p&gt;

&lt;p&gt;I’ve been using Claude chats for a while, but tools like Cursor never clicked for me. I just didn’t want to introduce a separate IDE into my workflow, and I didn’t feel they offered enough value for an experienced engineer. That changed with &lt;a href=&quot;https://www.anthropic.com/claude-code&quot;&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;Claude Code is an agentic coding assistant that runs in a terminal and can research your codebase without manual context selection and make coordinated changes across files automatically. It can use command line tools (like git) and MCP servers (like GitHub) to extend its capabilities.&lt;/p&gt;

&lt;p&gt;I tried Claude Code when it launched a few months ago and was immediately drawn to its terminal-based interface. I’ve never been a fan of similar products that required you to learn a separate IDE. Claude Code works alongside Xcode and feels like a natural extension of my current workflow.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;Screenshot&quot; alt=&quot;Claude Code&quot; src=&quot;/images/posts/claude/claude-code-screen-01.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the initial version only had a pay-per-token model where costs ramp up quickly. I tried it a few times, but that was it. But now it’s included in the Claude subscription. I upgraded to a Max monthly plan and started exploring without worrying about costs.&lt;/p&gt;

&lt;p&gt;I began by watching the video about &lt;a href=&quot;https://www.youtube.com/watch?v=6eBSHbLKuN0&quot;&gt;mastering Claude Code&lt;/a&gt; from Boris Cherny, its creator. It was extremely helpful and showed me that I had a lot to learn. I’ve read the bare minimum of the official documentation and started exploring what it can do.&lt;/p&gt;

&lt;h2 id=&quot;asking-questions&quot;&gt;Asking Questions&lt;/h2&gt;

&lt;p&gt;Following Boris’s recommendation, I started by asking questions about the codebase. Claude can explore your project and answer questions, but don’t take its responses at face value. I currently work on the &lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS&quot;&gt;WordPress iOS apps&lt;/a&gt;, which are open-source, and when I asked about post syncing conflicts, it mentioned “collaborative editing” which our app doesn’t support. See the full interaction &lt;a href=&quot;https://gist.github.com/kean/e88f6de0bd4f234b55fa76cfb345b137&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s OK though. I didn’t install it for Q&amp;amp;A.&lt;/p&gt;

&lt;h2 id=&quot;writing-code&quot;&gt;Writing Code&lt;/h2&gt;

&lt;h3 id=&quot;starting-small-refactoring&quot;&gt;Starting Small: Refactoring&lt;/h3&gt;

&lt;p&gt;I had this unused method sitting in one of my files, so I asked: “Remove unused code from this file @file_path”. It worked correctly, though not particularly fast. I opened &lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24590&quot;&gt;PR #24590&lt;/a&gt;. Definitely not the most efficient use of AI, but hey, it made the right changes.&lt;/p&gt;

&lt;p&gt;My next attempt was more ambitious. I asked Claude to rewrite the entire Objective-C “Support Logs” screen using SwiftUI. Here’s the &lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24591&quot;&gt;PR #24591&lt;/a&gt;. It’s a simple screen with low stakes, and my brief prompt worked. While it didn’t match my coding style perfectly, I manually corrected it.&lt;/p&gt;

&lt;h3 id=&quot;feature-activity-logs-and-backups&quot;&gt;Feature: Activity Logs and Backups&lt;/h3&gt;

&lt;p&gt;Now confident, I tackled a large planned project: implementing new “Activity Logs” and “Backups” screens in SwiftUI using the refreshed WordPress design system. This is where things got interesting.&lt;/p&gt;

&lt;p&gt;I created a small standalone app target (&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24598&quot;&gt;PR #24598&lt;/a&gt;) to iterate quickly while still being able to access all the app dependencies. I asked Claude to generate code based on the previous implementation but with a new design based on the screenshot of a different screen I dropped in the terminal.&lt;/p&gt;

&lt;p&gt;It generated code, but it ignored our coding standards. That’s when it hit me. While Claude &lt;em&gt;can&lt;/em&gt; explore your project automatically by reading files and running bash commands, it starts from zero every time unless you pre-configure it using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;. Think of it as Claude’s onboarding document. The better you write it, the better code Claude writes for you.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/claude/give-claude-context.png&quot; /&gt;&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;You can generate a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; file using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/init&lt;/code&gt;. The file should include coding standards, project structure, common patterns, and any domain-specific knowledge. The goal is to update it regularly as you discover what Claude needs to know. This file is part of CLAUDE.md &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/memory&quot;&gt;memory management&lt;/a&gt; with a few more tools at your disposal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With an improved &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt;, I regenerated the code with much better results. If Claude isn’t generating what you want, your prompt is likely underspecified.&lt;/p&gt;

&lt;p&gt;To review changes, I asked Claude to generate previews. Claude generated tons of mocks and code with perfect accuracy, nearly instantly, using realistic domain data. It even knew to use actual WordPress terminology. This is where LLMs truly shine.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;Screenshot&quot; alt=&quot;Xcode Preview&quot; src=&quot;/images/posts/claude/xcode-preview.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I continued iterating, opening multiple PRs resulting in this feature branch: &lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24597&quot;&gt;PR #24597&lt;/a&gt;. It has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+3,672 −4,967&lt;/code&gt; changes with around 90% generated by prompts. I even used Claude Code to create commits by simply asking “commit”.&lt;/p&gt;

&lt;h3 id=&quot;prototyping&quot;&gt;Prototyping&lt;/h3&gt;

&lt;p&gt;The next day, a colleague had a couple of questions about how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WKWebView&lt;/code&gt; interacts with the keyboard and whether the app has any control over it. I asked Claude to generate a sample project with an HTML form and add keyboard observers in Swift code. It did so instantly. This is another area where it excels. Normally, I wouldn’t bother – too time-consuming. Now I can verify anything using prototypes instantly, a massive productivity unlock.&lt;/p&gt;

&lt;h2 id=&quot;my-workflow&quot;&gt;My Workflow&lt;/h2&gt;

&lt;p&gt;I started with short one-liner prompts, with predictably poor results. After a week of trial and error, my workflow evolved to:&lt;/p&gt;

&lt;div class=&quot;blog-new-li&quot;&gt;
  &lt;ol&gt;
    &lt;li&gt;Start a new Claude session (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/clear&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;Ask it to read files I’ll work on and any related files. Pass related screenshots or documents&lt;sup id=&quot;fnref:5&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
    &lt;li&gt;(Optional) Switch to the “Plan” mode (⇧Tab)&lt;/li&gt;
    &lt;li&gt;Request the changes (e.g., “rewrite this” or “generate that”) and maybe throw an “ultrathink” at it&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
    &lt;li&gt;Iterate on the plan&lt;/li&gt;
    &lt;li&gt;Switch to “Auto-Accept Edits” mode (⇧Tab) and execute&lt;/li&gt;
    &lt;li&gt;Review changes and iterate until you are satisfied with the results&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

&lt;p&gt;I typically make one change at a time for easier review. Claude is more accurate with focused tasks. Asking it to do too much usually fails as it makes too many assumptions that can be hard to correct later. Even with detailed prompts, expect multiple iterations. It rarely one-shots changes unless the changes themselves aren’t very specific like generating mock data.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/claude/common-workflows.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I often skip the planning, especially for small changes. As you get more experienced with Claude, you are more likely to generate what you want on the first plan. There doesn’t seem to be that much science around prompts. For me, as long as you include some relevant details, it works fine. Don’t overthink it.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;One limitation: Claude 4 models have a 200K token context window. It’s decent for daily use but can’t process entire projects, so keep your context focused. If it overflows, Claude automatically compacts it, which works but takes time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;where-claude-code-excels&quot;&gt;Where Claude Code Excels&lt;/h2&gt;

&lt;p&gt;LLMs excel at generating text (including code):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Generating unit tests&lt;/li&gt;
  &lt;li&gt;Creating mock data and previews&lt;/li&gt;
  &lt;li&gt;Anonymizing production data&lt;/li&gt;
  &lt;li&gt;Refactoring existing code&lt;/li&gt;
  &lt;li&gt;Prototyping with sample projects&lt;/li&gt;
  &lt;li&gt;Generating documentation&lt;/li&gt;
  &lt;li&gt;Summarizing changes for commits, PRs, and release notes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;where-it-struggles&quot;&gt;Where It Struggles&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Making a large number of small changes across the entire project&lt;/li&gt;
  &lt;li&gt;Working with newest APIs that the model doesn’t know about yet&lt;/li&gt;
  &lt;li&gt;Tasks requiring genuine reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Apple &lt;a href=&quot;https://arxiv.org/abs/2410.05229&quot;&gt;has shown&lt;/a&gt;, AI can’t actually reason – it’s all statistics under the hood. This shows in practice as it occasionally makes nonsensical changes or continuously walks down paths that are obvious dead-ends.&lt;/p&gt;

&lt;p&gt;For an example of small changes, if you ask to rename a class, it spawns an agent to search for usage and read individual files, taking minutes for what should be instant. In one case, it took 25 minutes to rename &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EmptyStateView&lt;/code&gt; across 137 occurrences in 41 files, while also making unnecessary changes like renaming unrelated methods. Here’s &lt;a href=&quot;https://gist.github.com/kean/89a3031c17f5d9925f6801c786bc46a6&quot;&gt;the full session&lt;/a&gt;. The workaround? Ask Claude to generate and run a script instead, which is surprisingly effective&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The amount of time certain operations take is a limiting factor, but it depends on what you ask it to do. It’s the art of writing optimal prompts that you know it can execute well. The other option is parallelization. I currently work interactively with Claude Code, making changes one at a time, as it makes it easier to review the code and it just requires too much steering. But I can see myself parallelizing the work using git worktrees, a &lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices#c-use-git-worktrees&quot;&gt;recommended approach&lt;/a&gt;, and spawning additional Claude sessions to work on other tickets in parallel, particularly bug fixes or other small tasks that can be tested with unit tests and don’t require a long review or a lot of steering.&lt;/p&gt;

&lt;p&gt;One thing worth mentioning is the security and privacy considerations. If you are working on sensitive codebases or in regulated industries, you should think twice before deciding whether to use these tools and how.&lt;/p&gt;

&lt;h2 id=&quot;productivity-gains&quot;&gt;Productivity Gains&lt;/h2&gt;

&lt;p&gt;Let’s talk numbers. Last week, I opened these PRs:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24597&quot;&gt;Feature: Jetpack Activity Logs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24612&quot;&gt;Improve TimeZonePickerViewController&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24613&quot;&gt;Remove TimeZoneObserver from SiteSettingsViewController&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24590&quot;&gt;Remove unused code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wordpress-mobile/WordPress-iOS/pull/24591&quot;&gt;Rewrite Support Activity Logs using SwiftUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s +4,263 lines and −6,018 lines of real code changes in my typical coding style and with a high level of quality. In a normal week, I’d manually write maybe 1-2K lines of meaningful changes. And this was my first week seriously using Claude Code, so I wasn’t effective and I spent half the time learning best practices.&lt;/p&gt;

&lt;p&gt;Your mileage may vary, but for common tasks, I’m easily &lt;strong&gt;2x more productive&lt;/strong&gt;. It unlocks changes I’d never make manually: comprehensive unit tests, documentation, elaborate SwiftUI previews. For these specific tasks, it provides potentially &lt;em&gt;infinite&lt;/em&gt; productivity gains – generating in seconds what you would never otherwise even attempt to write. Of course, measuring overall productivity is impossible since software engineering involves much more than writing code, but the impact is there.&lt;/p&gt;

&lt;p&gt;I’m especially looking forward to sharing the workflows I learned with a team so we could all take advantage of the Claude instances configured to have the same idea of how to write code.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/claude/share-with-team.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;Disclosure: I wrote the entire article myself and then used Claude Code as my editor. It underwent multiple iterations, with Claude reviewing the article according to my stated target audience and goals. The thoughts are my own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After a week with Claude Code, I’m convinced. It’s not about replacing programmers – it’s about amplifying them. The better you are at coding and communicating, the bigger the multiplier&lt;sup id=&quot;fnref:7&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:7&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. It’s still a tool that requires time and skill to master, with plenty of room for learning and min-maxing.&lt;/p&gt;

&lt;p&gt;Claude Code works best when you’re already an expert who knows what to build and how. It turns the boring parts like writing boilerplate, unit tests, documentation into quick prompts. It also surprises you with novel approaches. I learned more this week than my typical one&lt;sup id=&quot;fnref:6&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;For junior developers, these tools present both opportunity and risk – they can accelerate learning by showing patterns and approaches, but there’s danger in accepting generated code without understanding it. The fundamentals still matter.&lt;/p&gt;

&lt;p&gt;Some compare AI to the transition from assembly to high-level languages, but I’m not sure that’s accurate. You still write and read the same code. The abstractions haven’t changed. What’s changed is velocity. I can now spend more time on design and architecture instead of mechanical code writing. But that’s only part of the story. The other parts are LLMs used as part of the existing products, with English as a sort of programming language for prompts.&lt;/p&gt;

&lt;p&gt;Do I have any concerns about code quality or long-term maintenance implications? In my experience – the opposite. I love the craft of engineering software. Our material is code. With AI, you have the power to effortlessly mold it in any shape or form.&lt;/p&gt;

&lt;div class=&quot;blog-post-references&quot;&gt;
  &lt;h2 class=&quot;no_toc&quot;&gt;References&lt;/h2&gt;

  &lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6eBSHbLKuN0&quot;&gt;Anthropic: Mastering Claude Code in 30 minutes&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Anthropic: Claude Code Best Practices&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/memory&quot;&gt;Anthropic: Claude Code Memory Management&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview&quot;&gt;Anthropic: Prompt Engineering Overview&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

&lt;/div&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:5&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Claude can search for context automatically, but doing it yourself is faster and more accurate. When Claude searches on its own, it spawns background agents that can take significant time and often include unrelated content. By explicitly using “Read @file” commands, you give Claude exactly the context it needs. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Special phrases map to thinking levels: “think” &amp;lt; “think hard” &amp;lt; “think harder” &amp;lt; “ultrathink”. These thinking levels actually correspond to different amounts of processing time Claude uses to consider your request. Use higher levels for complex architectural decisions or difficult debugging scenarios. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Even Claude Code itself reveals these limitations. It’s not unlike &lt;a href=&quot;https://youtu.be/mJMvFyBvZEk?si=3UU1IGSGOMyAmmhz&amp;amp;t=407&quot;&gt;Guided Generation&lt;/a&gt; in new Apple’s Foundation Models where agents combine language models with deterministic tool execution. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:7&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;It could be famous last words, lol. We don’t know what the future holds. &lt;a href=&quot;#fnref:7&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;For example, I learned a pretty cool async/await pattern where you execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task.sleep&lt;/code&gt; inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while !Task.isCancelled&lt;/code&gt; to implement polling. &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Mon, 23 Jun 2025 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/experiencing-claude-code</link>
        <guid isPermaLink="true">https://kean.blog/post/experiencing-claude-code</guid>
        
        <category>programming,</category>
        
        <category>ai,</category>
        
        <category>ios</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Swift 6</title>
        <description>&lt;p&gt;I love using Swift and have no thoughts about switching back to Objective-C, but that’s no longer a conversation because Swift is a de facto language on Apple platforms. During the first few years of Swift, I had complete confidence in its direction, and perhaps it was easier to make every change a slam dunk in the beginning. In recent years, there’ve been some questionable changes, the latest one being &lt;a href=&quot;https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety/&quot;&gt;Data Race Safety&lt;/a&gt; in its current form in Xcode 16 beta.&lt;/p&gt;

&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;/h2&gt;

&lt;p&gt;If you want to migrate a large codebase to support Swift 6 mode, you need to fix thousands of compiler warnings that become errors once you enable this mode. For example, if you have any global variables, they are now errors.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/swift-6/variable-error.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think we might need to go back to the drawing board starting from right around here and re-establish what a compiler error is or should be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A syntax, linker or other error that prevents a compiler from generating the binary&lt;/li&gt;
  &lt;li&gt;An obvious logic error where the code makes no sense under any circumstances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A compiler can have additional helpful diagnostics and show them as warnings if there is a high chance that the code is incorrect and/or unsafe. That has been my general framework for thinking about what a compiler is, and under that framework, a global variable by itself can’t be an error or a warning.&lt;/p&gt;

&lt;p&gt;Data Race Safety is touted as one of the great Swift 6 features. You would expect the compiler to be smart enough to help you automatically find and eliminate the hard-to-debug concurrency issues and code smells, but the reality is quite different with too much of the work shifted to the developer.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/swift-6/expectation-vs-reality.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OK, this is just for laughs. Maybe it’s the only reasonable way to implement it, but I think these design decisions combined with the lack of progressive disclosure is why this feature is often criticized and perceived the way it is. You want the compiler to help find the data races, and the data by itself can’t cause a data race – it’s how it’s being used down to individual properties, which is why Sendable also appears to be too blunt of an instrument.&lt;/p&gt;

&lt;p&gt;There’s also been a notion that the new warnings are easy to fix. I’m sure some of them are, but that wasn’t the case in my experience. In my frameworks and apps, I had to carefully think about almost every individual instance and then often update tests, etc, which was extremely time-consuming even in smaller well-maintained codebases. Migration seems easier once you’ve done it, but at this point, it’s too late. I wouldn’t underplay how complex it is, especially anything related to closures and delegates.&lt;/p&gt;

&lt;p&gt;It also depends on what kind of migration we are talking about. Unless you simply want to get your existing code to compile under Swift 6 by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@unchecked Sendable&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe(nonisolated)&lt;/code&gt; to essentially ignore most of the Data Race Safety system, you are looking at a rewrite of your concurrency code and the code that depends on it.&lt;/p&gt;

&lt;h2 id=&quot;suggested-solution&quot;&gt;Suggested Solution&lt;/h2&gt;

&lt;p&gt;The language, of course, should have first-class concurrency support, as outlined in the original &lt;a href=&quot;https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782&quot;&gt;Swift Concurrency Manifesto&lt;/a&gt;. Adding Async/Await and Actors has already been a huge win, but everyone is still in the process of adopting these new APIs. Eliminating data race issues at compile time is also a compelling idea, but I think neither the community nor the features are ready. Fortunately, there seems to be an easy fix to ensure that the migration goes smoothly and everyone is happy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Turn concurrency errors into warnings&lt;/li&gt;
  &lt;li&gt;Make the warnings optional and unbundle them from the Swift 6 mode&lt;/li&gt;
  &lt;li&gt;Continue iterating without locking the design down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the ideal world, I would love to have more granular control over the types of warnings and errors the compiler produces, depending on what you can tolerate in your project. If data race safety is a compelling enough feature, people will enable it. There are also questions about whether it &lt;em&gt;should&lt;/em&gt; be enabled by default and whether its current design can allow it to be enabled by default considering the lack of progressive disclosure.&lt;/p&gt;

&lt;p&gt;Every project is different. Some apps, and I have an iOS app like that, run &lt;em&gt;entirely&lt;/em&gt; on the main thread. For them, this change does nothing, but perhaps a better option would’ve been to implicitly treat every declaration as confined to the main actor. In some apps, the extent to which they use concurrency is limited only to making network calls – another scenario where opting &lt;em&gt;out&lt;/em&gt; of the main actor seems like a better option. And there is code written for prototypes, debugging, testing, validation of ideas, or any other code where high standards don’t apply in which you don’t want any SwiftLint warnings, and likely no concurrency warnings and definitely no errors.&lt;/p&gt;

&lt;h2 id=&quot;aside&quot;&gt;Aside&lt;/h2&gt;

&lt;p&gt;This is pure speculation, but knowing how corporate politics work, I think one of the issues with Swift is that some group within Apple thinks of Swift as a product in itself and not as a means to an end like how Objective-C has always been treated. It leads to time constraints and impossible expectations being set for the teams.&lt;/p&gt;

&lt;p&gt;With ten years in, there are some legitimate questions whether the recent changes have resulted in the productivity gains, which is ultimately the only thing that matters. Many new features have been syntax sugar, including property wrappers, result builders, multiple trailing closures, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; expressions, and more. Swift now has levels of syntax sugar often surpassing scripting languages. I don’t necessarily say that it’s a bad thing, and as a counterexample, Async/Await, the new ownership model that follows the progressive disclosure principles, improvements to generics and incremental compilation, have all been welcome.&lt;/p&gt;

&lt;p&gt;Speaking about compile time, one of Swift’s original premises was that it was “fast,” and you would expect it to apply to the compile time. However, with the current slow compilation, developers have to go to extreme lengths to work this around, including reinventing header files by creating protocol-only modules, which Swift was designed to eliminate. If there was a way to disable some of the language features to improve compile time, I would do it in an instant. I’m bringing this up because I wonder what the impact of data race safety is going to be, especially once it gets upgraded with more advanced techniques for eliminating false positives.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;I hope Apple returns to Swift’s original tenets and makes it fast and simple, focusing on productivity gains over everything else, and &lt;em&gt;reduce&lt;/em&gt; the amount of work the developers need to do, not increase it.&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Jul 2024 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/swift-6</link>
        <guid isPermaLink="true">https://kean.blog/post/swift-6</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>SwiftPM and Xcode</title>
        <description>&lt;p&gt;I recently had the pleasure of migrating a relatively large project from CocoaPods to SwiftPM, and I would like to share my experience and some thoughts about how it could be improved.&lt;/p&gt;

&lt;h2 id=&quot;adding-dependencies&quot;&gt;Adding Dependencies&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app&quot;&gt;existing process&lt;/a&gt; for adding dependencies to Xcode target involves multiple steps and is especially convoluted when you have multiple targets, like app extensions. I wanted to make sure that the experience of managing the dependencies is at least as good as in CocoaPods, so I borrowed a page from their playbook. I’m also working with a situation where most of the code is in Xcode targets and can’t be easily extracted to Swift packages.&lt;/p&gt;

&lt;p&gt;I created a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules/Package.swift&lt;/code&gt; file and defined a list of products that match my Xcode targets:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;products&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XcodeSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MyInternalLibrary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;MyInternalLibrary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XcodeSupport&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;products&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_AppTests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_AppTests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_ShareExtensions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_ShareExtensions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These products are neatly placed at the bottom of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; and have no source files except for the dummy files stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules/Sources/XcodeSupport/&lt;/code&gt;. The libraries produced by these products is what’s get added to “Frameworks, Libraries, and Embedded Content” in Xcode.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/swiftpm-xcode/xcode-targets.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The “actual” dependencies are specified in the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules/Package.swift&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XcodeSupport&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;XcodeTarget_App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Pulse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Pulse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;PulseUI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Pulse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;NukeUI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now if you need to add a new dependency, you simply add it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules/Package.swift&lt;/code&gt;, and you are done. You never need to mess with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pbpxroj&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The dependencies, including the transitive ones, are pinned in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./MyApp.xcworkspace/xcshareddata/swiftpm/Package.resolved&lt;/code&gt; file as usual. I added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modules/Packages.resolved&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; to make sure if someone opens &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Packages.swift&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file never gets committed into the repo.&lt;/p&gt;

&lt;h2 id=&quot;better-developer-experience&quot;&gt;Better Developer Experience&lt;/h2&gt;

&lt;p&gt;With a few extra steps, I achieved my goals, and I think it was an acceptable solution for SwiftPM’s early days. But it leaves a lot to be desired. The ideal developer experience for me would’ve looked the following way:&lt;/p&gt;

&lt;p&gt;You invoke the “Add Package Dependency” action and it shows the existing dialog.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;/images/posts/swiftpm-xcode/add-package.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you select a package, Xcode either creates a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file in the root project directory or adds a dependency to the existing file:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://github.com/kean/Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;12.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The pinned dependencies should be saved in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.resolved&lt;/code&gt; file stored side by side with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file so that if you open a package, it uses the same pins as the Xcode project, and there is no confusion where the dependencies are pinned.&lt;/p&gt;

&lt;p&gt;To add a dependency to an Xcode target you have two options:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add it to “Frameworks, Libraries, and Embedded Content”  manually&lt;/li&gt;
  &lt;li&gt;Add it in code using targets automatically generated by Xcode and/or SwiftPM:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;MyInternalLibrary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nuke&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;Yes, borrowing a bit from &lt;a href=&quot;https://tuist.io&quot;&gt;Tuist&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To delete a dependency, you would Option-Click on the dependency in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file and select “Delete Dependency”, which would remove it from the list of dependencies and also from all the targets.&lt;/p&gt;

&lt;h3 id=&quot;technical-limitations&quot;&gt;Technical Limitations&lt;/h3&gt;

&lt;p&gt;Of course, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file is a Swift file, so if it is complicated enough, some of these things won’t work. But I think it’s an acceptable and understandable trade-off because it’s just a bit of a convenience on top of a human-readable source file. If you are unhappy with a diff it creates, you’ll change it. Currently, these features don’t exist at all, even for simple package files.&lt;/p&gt;

&lt;h2 id=&quot;other-issues&quot;&gt;Other Issues&lt;/h2&gt;

&lt;p&gt;In addition to the issues with the developer experience, which I can live with, there are a couple of major issues with no straightforward solutions that really sour the experience of using SwiftPM, especially coming from other package managers and platforms.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Bundle Duplication&lt;/strong&gt;. Xcode is smart enough to link the dependencies used by more than one target dynamically, but, unfortunately, their bundles still &lt;a href=&quot;https://forums.developer.apple.com/forums/thread/749265&quot;&gt;end up being duplicated&lt;/a&gt; in all of the targets that depend on them.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Invalid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bundle.module&lt;/code&gt;&lt;/strong&gt;. If you include a SwiftPM package that has resources in one of your Xcode test targets, the library is &lt;a href=&quot;https://forums.swift.org/t/swift-5-3-swiftpm-resources-in-tests-uses-wrong-bundle-path/37051&quot;&gt;not be able to locate&lt;/a&gt; its resources. I had to stop using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bundle.module&lt;/code&gt; and switched to an updated version that first looks for bundles in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessInfo.processInfo.environment[&quot;XCTestBundlePath&quot;]&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resolve Packages&lt;/strong&gt;. Xcode constantly restarts &lt;a href=&quot;https://forums.swift.org/t/swiftpm-how-to-prevent-resolve-packages-from-stymying-developer-productivity-local-packages/63363&quot;&gt;package resolution&lt;/a&gt;, even as you type in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package.swift&lt;/code&gt; file, which is especially problematic during rebasing or any other Git operations. I’m usually reserved with my comments, but I can’t overstate how horrible the experience is. It’s a good trade-off only if you are one person working on a small project in a single branch. There has to be a way to turn it off and a simple command like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swiftpm install&lt;/code&gt; to resolve the dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Despite the existing issues, I feel more confident using SwiftPM for medium- to large-size projects, but I hope to see the improvements that will make it a no-brainer decision to switch to it for projects of any size.&lt;/p&gt;
</description>
        <pubDate>Tue, 09 Jul 2024 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/swiftpm-and-xcode</link>
        <guid isPermaLink="true">https://kean.blog/post/swiftpm-and-xcode</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Search</title>
        <description>&lt;p&gt;Starting with iOS 15, SwiftUI supports search thanks to the new &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/adding-search-to-your-app&quot;&gt;.searchable&lt;/a&gt; modifier. It was also extended with the support for tokens in iOS 16 which was exactly what I was waiting for. For me, it was a perfect opportunity to rethink search in &lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pulse is now &lt;em&gt;infinitely&lt;/em&gt; more useful thanks to two new major features in &lt;a href=&quot;https://github.com/kean/Pulse/releases/tag/3.2.0&quot;&gt;version 3.2&lt;/a&gt;: a powerful search with filters and scopes, and a redesigned console that now supports sorting and grouping of messages.&lt;/p&gt;

&lt;p&gt;To start using the new search, simply press “Tab” to focus on a search field, and start typing. It will automatically suggest relevant &lt;em&gt;filters&lt;/em&gt;, auto-complete certain entries like domains, and search in multiple &lt;em&gt;scopes&lt;/em&gt; like response headers and bodies.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-search/search-demo-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Search is just one of the features introduced in version 3.2. So let me show you the rest. This is super exciting because this release is &lt;em&gt;packed&lt;/em&gt; with features.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;I will cover some of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.searchable&lt;/code&gt; basics in the article, but if you just want to learn the API and are not interested in Pulse, go through the Apple’s &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/adding-search-to-your-app&quot;&gt;“Adding search to your app”&lt;/a&gt; article instead. It’s one of the best new articles – love it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;searchable&quot;&gt;Searchable&lt;/h2&gt;

&lt;p&gt;When you open search for the first time, it shows you a list of available filters and search scopes. And if you’ve used one before, it’ll be on the top of the suggestions list with a prefilled value. Hit “Tab”, and it’ll add the suggestion filter. Or hit “Return”, and it’ll use the input as a plain search term (or wildcard, or regex).&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse search&quot; src=&quot;https://kean.blog/images/posts/pulse-search/search-01-welcome.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To add a search bar, I use the new API introduced in iOS 16 that supports tokens and fallback to the previous API on the earlier versions:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ConsoleView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;ConsoleListView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;searchable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;Label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;systemImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onSubmit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onSubmit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ConsoleListView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;@Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSearching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;isSearching&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSearching&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;ConsoleSearchableListContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;ConsoleRegularListContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;listStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;grouped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;warning&quot;&gt;
  &lt;p&gt;There is a way to track when the search is enabled with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isSearching&lt;/code&gt; environment variable, but there is one gotcha: it has to be added in a subview of the view where you add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.searchable&lt;/code&gt;. It’s readonly which is a shame.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you select the “tokens”, they are displayed directly in the search bar, and you can easily remove them. It’s a nice feature because it saves a lot of vertical space and is consistent with many other apps on iOS, e.g. Mail.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse search&quot; src=&quot;https://kean.blog/images/posts/pulse-search/search-05-tokens.png&quot; /&gt;&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;SwiftUI also provides a default UI for displaying suggested search tokens (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggestedTokens&lt;/code&gt;), but it’s a bit limited and covers the entire screen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;suggestions&quot;&gt;Suggestions&lt;/h2&gt;

&lt;p&gt;Pulse is smart about the suggested filters. Just to give you a few examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Start typing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; and it recognizes it’s likely the beginning of a status code and suggests: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Status Code: 2XX&lt;/code&gt;. You can also specify status code ranges: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200-300&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200..&amp;lt;300&lt;/code&gt; – it recognized multiple formats. You can also specify a list instead: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 203 204&lt;/code&gt;. Or make it comma-separated – whatever works for you.&lt;/li&gt;
  &lt;li&gt;Start typing a host from one of the requests, and it autocompletes it.&lt;/li&gt;
  &lt;li&gt;Start with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/path&lt;/code&gt;, and it recognizes you want to search by it.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-search/search-autocompletion.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;How does this stuff work? I used parser combinators from my other project – &lt;a href=&quot;https://github.com/kean/Regex&quot;&gt;Regex&lt;/a&gt;. You can learn more about them in one of the &lt;a href=&quot;https://kean.blog/post/regex-parser&quot;&gt;older articles&lt;/a&gt;. It is by far my favorite programming technique. I slightly modified the parser for Pulse by adding fuzzy matching and non-strict grammar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;scopes&quot;&gt;Scopes&lt;/h2&gt;

&lt;p&gt;Pulse searches in multiple scopes in parallel: request and response headers, bodies, and URLs. You can also select what scopes to use if you don’t need all. You can see all the matches in the results list, and when you select one, it opens the response viewer, highlights the matches, and scrolls to the selected one.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse search&quot; src=&quot;https://kean.blog/images/posts/pulse-search/search-06-scopes.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This new search streamlines many user scenarios. For example, let’s say you need to find a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userId&lt;/code&gt; value from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/login&lt;/code&gt; response. Previously, you had to know the name of the path and search for it: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/login&lt;/code&gt;, then open the response body, search again for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userId&lt;/code&gt;, and only then do you see it. Now you just search for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userId&lt;/code&gt;, click it, and you are done.&lt;/p&gt;

&lt;h2 id=&quot;list&quot;&gt;List&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; is known for its &lt;a href=&quot;https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui&quot;&gt;performance issues&lt;/a&gt;. Unfortunately, it was a deal-breaker for Pulse where I need to display hundreds if not thousands of messages and do it in the reverse order with the newest ones constantly getting inserted at the top. So I &lt;a href=&quot;https://kean.blog/post/not-list&quot;&gt;switched to&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UITableView&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTableView&lt;/code&gt;, but kept using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; on watchOS and tvOS.&lt;/p&gt;

&lt;p&gt;Previously, I only needed to display two types of cells, but now, I needed to introduce filters, scopes, new section headers, and, of course, cells for the search results. I didn’t want to pile onto the technical debt. So I had an idea. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; is slow only when you display tons of cells. So let’s just not. In version 3.2, I limit the number of displayed items to 100&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; and extend the list when the user scrolls close to the bottom – simple.&lt;/p&gt;

&lt;p&gt;With these changes, I was finally able to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; on iOS and macOS, remove a lot of duplicated code, and while at it, I also introduced a slightly refined design for cells that is now consistent across platforms. There is also a new toolbar because I knew switching between regular messages and network requests needed a prime spot. You now also switch to “Logs” and see only plain messages without requests, which is new.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse search&quot; src=&quot;https://kean.blog/images/posts/pulse-search/search-04-console.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’m happy with this approach, but I wish there was a native solution. I’m suspecting, there is no need to diff &lt;em&gt;all&lt;/em&gt; the elements to animate the changes on the screen. Or, at least, please, please, give us the option to disable the diffing.&lt;/p&gt;

&lt;h2 id=&quot;sort-by-and-group-by&quot;&gt;Sort By and Group By&lt;/h2&gt;

&lt;p&gt;When reviewing logs, you often want to find answers to certain questions. For example, what are the requests that took the longest? What transferred the most data? Which ones returned HTML instead of the expected JSON? With version 3.2, you can now easily find answers to these and many other questions thanks to the new “Sort By” and “Group By” features available directly from the toolbar.&lt;/p&gt;

&lt;p&gt;The grouping I’m particularly excited about is “Group by Session”. It’ll show you a list of all your app’s sessions, so now it’s super easy to find logs from one of the earlier sessions. The groups are also infinitely nestable: you can create a group, open it and create a group from the selected logs, and so on.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-search/console-grouping.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;pins&quot;&gt;Pins&lt;/h2&gt;

&lt;p&gt;And last but not least, Pulse now has a proper replacement for the Pins tab that was removed in version 3.0. It took me a while to finally realize the obvious: it’s in the name – just literally &lt;em&gt;pin&lt;/em&gt; them to the top.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-search/console-pins.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;I love building things, especially when I have full control over all aspects of the process. With Pulse, with a limited time, I try to work in quick bursts where I try to make a few good decisions to move it forwards and focus on them. I usually follow the make it work → make it right → make it fast mantra, and with SwiftUI, prototypes are often indistinguishable from the implementation, which is a massive time-saver.&lt;/p&gt;

&lt;p&gt;The new search and other list features make Pulse infinitely more useful. They are available today on iOS and are coming soon to other platforms. I hope you give it a try!&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This new approach with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; is actually kind of perfect for one more reason. The diffing introduces a problem that is a bit more subtle. While &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; doesn’t display all cells upfront, it needs to know the identifiers of all the displayed entities upfront. That’s a problem if you are using &lt;a href=&quot;https://developer.apple.com/documentation/coredata/nsfetchrequest/1506558-fetchbatchsize&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchBatchSize&lt;/code&gt;&lt;/a&gt; to reduce the number of database calls. But if you only display a slice of entities, and if the size of the slice matches your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetchBatchSize&lt;/code&gt;, it works as expected: there is only one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT * FROM&lt;/code&gt; when you open the page: see my Core Data logs &lt;a href=&quot;https://kean.blog/images/posts/pulse-search/core-data-logs-01.txt&quot;&gt;before&lt;/a&gt; and &lt;a href=&quot;https://kean.blog/images/posts/pulse-search/core-data-logs-02.txt&quot;&gt;after&lt;/a&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 24 Jan 2023 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/pulse-search</link>
        <guid isPermaLink="true">https://kean.blog/post/pulse-search</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Pulse 3</title>
        <description>&lt;p&gt;Today is my 10th anniversary of programming for Apple platforms, and what better way to celebrate it than with a new major release – &lt;a href=&quot;https://github.com/kean/Pulse/releases/tag/3.0.0&quot;&gt;Pulse 3.0&lt;/a&gt; is out!&lt;/p&gt;

&lt;p&gt;I loved every year I worked on iOS. I started in 2012 with Objective-C and now, thanks to Swift and SwiftUI, I’m able to target &lt;em&gt;all&lt;/em&gt; Apple platforms, which is incredible. Pulse 3 is a complete overhaul. It enhances the experience and achieves nearly complete feature parity between iOS, macOS, watchOS, and tvOS with the same codebase.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; is a network logger built with SwiftUI. It integrates on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; level allowing it to see unencrypted traffic and record task metrics available only on this level.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;ios&quot;&gt;iOS&lt;/h1&gt;

&lt;p&gt;The original version of Pulse was designed quickly and targeted only iOS, so many of the decisions were incompatible with other platforms. Fortunately, for nearly every issue, there was a better solution that also enhanced the experience on iOS while simplifying the code.&lt;/p&gt;

&lt;h2 id=&quot;inspector&quot;&gt;Inspector&lt;/h2&gt;

&lt;p&gt;The most dramatic changes are probably in the network inspector. The &lt;a href=&quot;https://kean.blog/videos/pulse-2/metrics.mp4&quot;&gt;previous version&lt;/a&gt; had a convoluted navigation with a segmented control in the navigation bar that was hard to reach and didn’t scale for adding more features. More importantly, it was incompatible with other platforms. This design was there from 0.1 and it was time to improve it.&lt;/p&gt;

&lt;p&gt;The simplest solution is often also the best. The inspector now uses a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; with navigation links. The new version is clearer and surfaces important information. The primary navigation areas such as response and headers viewer are now much easier to reach because they are closer to the bottom of the screen.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-inspector.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The new navigation freed a lot of vertical space. The difference between the &lt;a href=&quot;https://kean.blog/images/posts/pulse-3/ios-old-inspector.png&quot;&gt;old inspector&lt;/a&gt; and the new one is stark. Before, the response viewer had an ad-hoc “show fullscreen” button. Not needed anymore. When you click “Response”, you are effectively already fullscreen now.&lt;/p&gt;

&lt;p&gt;The response viewer itself is pretty cool because, unlike regular HTTP proxies, it can display &lt;a href=&quot;https://kean.blog/post/pulse-2#decoding-errors&quot;&gt;decoding errors&lt;/a&gt;. And it’s even better now because the theme of “more vertical space” extends to it as well. I adopted &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/searchable(text:placement:prompt:)-18a8f&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;searchable&lt;/code&gt;&lt;/a&gt; API so that the search bar now disappears when it’s not needed. And I also redesigned the toolbar for jumping between search results. It is now displayed in the bottom-right corner, freeing even more vertical space.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-response-body.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The metrics screen is also redesigned. It now clearly shows all individual transactions along with their &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics/resourcefetchtype&quot;&gt;fetch types&lt;/a&gt; and other details. It’s now much easier to understand the lifetime of a request.&lt;/p&gt;

&lt;p&gt;In the following example, you immediately see that the task had three transactions: the first request was redirected with 301 (Moved Permanently), followed by a cache lookup with another request to validate it that returned 304 (Not Modified).&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-metrics.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;text&quot;&gt;Text&lt;/h2&gt;

&lt;p&gt;Text is often the best interface, and it’s everywhere in Pulse. Where previously you had &lt;a href=&quot;https://kean.blog/images/posts/pulse/ios-headers.png&quot;&gt;non-selectable labels&lt;/a&gt;, it now uses fully featured text views with text selection, search, and sharing. There are also two new sharing options: HTML and PDF. I’m sure a lot of people will be particularly excited about PDF, especially if you print out code at work. To power these features, I had to rework how I create strings because the previous approach of writings different “renderers” for different outputs didn’t scale well.&lt;/p&gt;

&lt;p&gt;In the new system I render things – messages, tasks, response bodies – into attributed strings. The strings are then converted into the requested output format: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSAttributedString -&amp;gt; HTML&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSAttributedString -&amp;gt; PDF&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSAttributedString -&amp;gt; Plain Text&lt;/code&gt;. It’s much simpler and produces consistent results. SwiftUI previews were a massive help to iterate on it.&lt;/p&gt;

&lt;div class=&quot;full-width centering-container&quot;&gt;
&lt;img class=&quot;centered-child large-screenshot JustVertMargins&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-text.png&quot; /&gt;
&lt;/div&gt;

&lt;h3 id=&quot;navigation&quot;&gt;Navigation&lt;/h3&gt;

&lt;p&gt;The main navigation also changed in Pusle 3. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainView&lt;/code&gt; from the previous version had a tab bar, which is a good choice for most iOS apps, but not for a tool that you are integrate into an existing app. Most large apps already have their own debug menus and they want to integrate Pulse there.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-console-tabbar.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In Pulse 3, the navigation is now centered around a single screen: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConsoleView&lt;/code&gt; that you can either push or present. The &lt;strong&gt;Network&lt;/strong&gt; and the &lt;strong&gt;Pins&lt;/strong&gt; screens became simple filters. And &lt;strong&gt;Insights&lt;/strong&gt; and &lt;strong&gt;Settings&lt;/strong&gt; are now accessible from the new context menu. It also provides quick access for common actions, such as removing logs.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-console-context-menu.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View as Text&lt;/strong&gt; is yet another new text-based feature. It renders the entire console output as text. But it’s not static text: you can still apply filters, search, expand and collapse network requests, and more. And just like the response viewer, it displays decoding errors inline (see “id” highlighted in red).&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/ios-text-view.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;storage&quot;&gt;Storage&lt;/h2&gt;

&lt;p&gt;The storage &lt;a href=&quot;https://kean.blog/post/pulse-2#performance&quot;&gt;improvements&lt;/a&gt; were the primary focus of Pulse 2: massive space savings, improved document format for sharing, and fully SQL-compatible storage – these are just some of the changes it introduced. With this solid foundation, I was able to focus on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PulseUI&lt;/code&gt; in this release, but there are some improvements to the core framework as well. For example, &lt;a href=&quot;https://kean-docs.github.io/pulse/documentation/pulse/networklogger&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkLogger&lt;/code&gt;&lt;/a&gt; now has convenience APIs for filtering out sensitive data or just something you don’t want to be logged for other reasons.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;NetworkLogger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Includes only requests with the given domain.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;includedHosts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*.example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Exclude some subdomains.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;excludedHosts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logging.example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Exclude specific URLs.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;excludedURLs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*/log/event&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Replaces values for the given HTTP headers with &quot;&amp;lt;private&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;sensitiveHeaders&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Access-Token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Redacts sensitive query items.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;sensitiveQueryItems&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Replaces values for the given response and request JSON fields with &quot;&amp;lt;private&amp;gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;sensitiveDataFields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;Both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude&lt;/code&gt; patterns support basic wildcards (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;), but you can also turns them into regex patterns using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isRegexEnabled&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;tvos&quot;&gt;tvOS&lt;/h1&gt;

&lt;p&gt;Now let’s talk about the big screen – Apple TV. The original iOS-centered design was largely incompatible with other platforms. But with the new simplified navigation, I was able to use the network inspector and console with almost no changes on tvOS (and other platforms, but more on that later). I was stunned when I ran the new SwiftUI codebase on these other targets – it just worked. I needed to make only small adjustments. Well, of course, I’m also using native design, so many things come for free.&lt;/p&gt;

&lt;div class=&quot;full-width centering-container&quot;&gt;
&lt;img class=&quot;centered-child large-screenshot JustVertMargins&quot; src=&quot;https://kean.blog/images/posts/pulse-3/tvos-console-full.png&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;The main thing I did end up tweaking on tvOS is navigation to take advantage of the big screen. I combined two navigation stacks on a single screen, both in the console and in the network inspector. This way you can access common features, such as filters without ever leaving the console. It also enforces acceptable width for lists so that the existing cells from iOS work well in this context.&lt;/p&gt;

&lt;div class=&quot;full-width centering-container&quot;&gt;
&lt;img class=&quot;centered-child large-screenshot JustVertMargins&quot; src=&quot;https://kean.blog/images/posts/pulse-3/tvos-inspector-full.png&quot; /&gt;
&lt;/div&gt;

&lt;h1 id=&quot;watchos&quot;&gt;watchOS&lt;/h1&gt;

&lt;p&gt;The watchOS version got a complete overhaul as well and now has nearly complete feature parity with iOS. If you enjoy hiking with your Apple Watch Ultra and have a watchOS app to test – great. You can record and preview logs directly on the device. And when you are ready, you can easily share and view them on a bigger screen.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;You can also connect to any device running Pulse remotely with &lt;a href=&quot;https://kean.blog/pulse/pro&quot;&gt;Pulse Pro&lt;/a&gt; and view logs in real-time on your Mac.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse Console on watchOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/watchos-console.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I got a lot of features “for free” after reworking the iOS version. For example, console and network filters that were initially developed for &lt;a href=&quot;https://kean.blog/pulse/pro&quot;&gt;Pulse Pro&lt;/a&gt; now work on all platforms (and with nearly zero conditional code). But there are also some new SwiftUI APIs I integrated on watchOS: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AttributedString&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;searchable&lt;/code&gt;, charts, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ShareLink&lt;/code&gt;, monospace digits for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt;, destructive buttons, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buttonBorderShape&lt;/code&gt;, and more.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse Console on watchOS&quot; src=&quot;https://kean.blog/images/posts/pulse-3/watchos-inspector.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;macos&quot;&gt;macOS&lt;/h1&gt;

&lt;p&gt;The macOS version is back to its OG triple-column design from &lt;a href=&quot;https://kean.blog/post/appkit-is-done&quot;&gt;App Kit is Done&lt;/a&gt;, but now with the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigationSplitView&lt;/code&gt; APIs and 90% of the code shared with others platforms.&lt;/p&gt;

&lt;div class=&quot;full-width centering-container&quot;&gt;
&lt;img class=&quot;centered-child large-screenshot JustVertMargins&quot; src=&quot;https://kean.blog/images/posts/pulse-3/macos-inspector.png&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;The macOS version has probably changed the most over the last couple of years.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://kean.blog/images/posts/pulse-3/history/macos-01.png&quot;&gt;first version&lt;/a&gt; from Pulse 0.x was almost embarrassingly bad, but I’ll add it as a reference point – it’s nice to see the progress. For &lt;a href=&quot;https://kean.blog/images/posts/pulse-3/history/macos-02.png&quot;&gt;1.0&lt;/a&gt;, I added proper macOS support with windows, toolbars, and &lt;a href=&quot;https://kean.blog/post/triple-trouble&quot;&gt;triple-column&lt;/a&gt; navigation. The next major advancement was a mostly AppKit standalone &lt;a href=&quot;https://kean.blog/images/posts/pulse-3/history/macos-03.png&quot;&gt;Pulse Pro app&lt;/a&gt; for viewing logs remotely shared from other devices. With the release of Pulse Pro, I cut the macOS version of the PulseUI console entirely. But with the recent SwiftUI refinements, I started to bring PulseUI back to the Mac. In Pulse 2, I reworked some of the Pulse Pro screens in SwiftUI and put it all together in a &lt;a href=&quot;https://kean.blog/images/posts/pulse-3/history/macos-04.png&quot;&gt;simple view&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pulse 3 is a natural step in the SwiftUIfication of the macOS codebase. I’m sure this isn’t its final form yet, so I don’t want to focus on it as much right now. My next step is to fully rewrite Pulse Pro in SwiftUI – stay tuned for that!&lt;/p&gt;

&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;/h2&gt;

&lt;p&gt;I think I finally figured out SwiftUI – writing it stared to feel as natural as writing UIKit. I use it for everything: design, prototyping, and development. There are fewer and fewer places where I have to fallback to UIKit or AppKit.&lt;/p&gt;

&lt;p&gt;SwiftUI works well when you target just a single platform, but you are making a multiplatform app, it’s a complete game changer, and I hope Pulse is proof of that. There is one condition though – it all comes down to the design. You have to come up with the design solutions that are simple and compatible with other platforms, which is often a good thing because simpler things are easier accessible.&lt;/p&gt;

&lt;p&gt;Pulse 3 development was completely insane with 215 changed files with 8,597 additions and 8,044 deletions in just under 2 weeks. For me, it’s now time to chill. I hope you would check out &lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse 3&lt;/a&gt; and you’d love it.&lt;/p&gt;
</description>
        <pubDate>Tue, 10 Jan 2023 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/pulse-3</link>
        <guid isPermaLink="true">https://kean.blog/post/pulse-3</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Pulse 2.0</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; is a logging system for Apple platforms. Pulse 2.0 is almost a complete rewrite that focuses on adding plenty of new major features and optimizing performance.&lt;/p&gt;

&lt;h2 id=&quot;pulseui&quot;&gt;PulseUI&lt;/h2&gt;

&lt;p&gt;The first feature developed for 2.0 was &lt;em&gt;pending requests&lt;/em&gt; – the console will now show pending tasks with their progress. It uses the new &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate/3929682-urlsession&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;urlSession(_:didCreateTask:)&lt;/code&gt;&lt;/a&gt; method (iOS 16) to track the created tasks&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The remote logger also supports this feature. And in addition to basic data tasks, Pulse now supports &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiondownloadtask&quot;&gt;downloads&lt;/a&gt; and uploads.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot; alt=&quot;Custom context menu preview&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/pending.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h3 id=&quot;improved-metrics&quot;&gt;Improved Metrics&lt;/h3&gt;

&lt;p&gt;The network inspector now also does a much better job of accurately displaying the information about the requests and their &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics&quot;&gt;metrics&lt;/a&gt;. You can now see both the &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontask/1411572-originalrequest&quot;&gt;original&lt;/a&gt; and the &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontask/1411649-currentrequest&quot;&gt;current&lt;/a&gt; request. And it also displays individual transactions.&lt;/p&gt;

&lt;p&gt;In the example below, the response is stored in the disk cache but is expired and needs to be validated with the server. You can now clearly see what happens from the transaction list. The first one loads the response from the cache. And the second transaction validates it with the server that returns 304 (Not Modified). That’s why you see “Source: Cache” – a new field on the summary page.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot; alt=&quot;Custom context menu preview&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/metrics.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;And there are more new fields and sections on the summary page. For example, it now shows a type of recorded task: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSessionDataTask&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSessionDownloadTask&lt;/code&gt;, etc. There are separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt; fields in the request section making it easier to see and copy them. And if the URL contains query items, there are also displayed in pairs.&lt;/p&gt;

&lt;h3 id=&quot;insights&quot;&gt;Insights&lt;/h3&gt;

&lt;p&gt;In addition to tracking individual metrics, Pulse now also analyzes your traffic and presents some key insights in a visual way.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Insights screen&quot; src=&quot;https://kean.blog/images/posts/pulse-2/insights.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’ll show you information like the duration of requests, total transfer size, domains, and recent errors and warnings. For example, it’ll surface the requests with redirects and show how much time the app lost on them.&lt;/p&gt;

&lt;p&gt;The graphs on this screen are powered by &lt;a href=&quot;https://developer.apple.com/documentation/Charts&quot;&gt;Swift Charts&lt;/a&gt; (iOS 16). This API is not yet released and there will be more charts added to this screen when Xcode 14 goes into production this fall.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Warning about redirects&quot; src=&quot;https://kean.blog/images/posts/pulse-2/redirects.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;decoding-errors&quot;&gt;Decoding Errors&lt;/h3&gt;

&lt;p&gt;Unlike regular network proxies, Pulse works directly with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt;. It makes it easier to integrate and see encrypted traffic. It also gives you more information about the requests that any proxy could get.&lt;/p&gt;

&lt;p&gt;When you think about network requests from the perspective of an app, you can’t consider a request with failed decoding success. Yet a regular proxy will report 200 OK. There is a new configuration option in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkLogger&lt;/code&gt; to address that called &lt;a href=&quot;https://kean-docs.github.io/pulse/documentation/pulse/networklogger/configuration/iswaitingfordecoding&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isWaitingForDecoding&lt;/code&gt;&lt;/a&gt;. When enabled, it gives the app control over when to complete the request. If the app reports a decoding error, Pulse will now display it directly in the response body, highlighting the field that produced the error.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;File viewer showing a decoding error&quot; src=&quot;https://kean.blog/images/posts/pulse-2/file-view-decoding-errors.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;File viewer now supports more content types: PDF, Query Items, and HTML. There is now basic highlighting for HTML and an option to open it in a browser for preview. The file viewer is now also the default screen you see when you tap on a request, and you can also open it fullscreen.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;File viewer showing HTML&quot; src=&quot;https://kean.blog/images/posts/pulse-2/file-view-html.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are a ton of other minor fixes, tweaks and features across the app and there is no space to cover them. Many of them are thanks to the new APIs added to SwiftUI this year. For example, &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/form/contextmenu(menuitems:preview:)&quot;&gt;custom previews&lt;/a&gt; for context menus.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot; alt=&quot;Custom context menu preview&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/context-preview.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;back-to-the-mac&quot;&gt;Back to the Mac&lt;/h2&gt;

&lt;p&gt;Pulse has a history on a Mac. The &lt;a href=&quot;https://kean.blog/images/posts/macos/cover-macos.png&quot;&gt;initial version&lt;/a&gt; looked and worked more like it was designed for an iPad despite being a native macOS app. Then came &lt;a href=&quot;https://github.com/kean/PulsePro&quot;&gt;Pulse Pro&lt;/a&gt; designed as a professional macOS app with a primary goal of working as a target for remote logging. But something was lost in that transition.&lt;/p&gt;

&lt;p&gt;Pulse Pro is a standalone app and not part of the PulseUI framework, and there was no longer any way to bundle PulseUI into a macOS app that &lt;a href=&quot;https://github.com/kean/Pulse/issues/82&quot;&gt;people asked for&lt;/a&gt;. With version 2.0, PulseUI has a purpose-built macOS view designed to work well when integrated into the existing app. Pulse now also supports remote logging on a Mac, which is easy to enable using the Settings screen.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-2/pulseui-macos.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;any-platform&quot;&gt;Any Platform&lt;/h2&gt;

&lt;p&gt;In addition to iOS and macOS, Pulse is, as usual, also available on watchOS and tvOS. Both versions were updated to support most of the latest features (at practically no extra cost). For example, it also supports pending requests:&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot; alt=&quot;Remote logger on watchOS&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/pulse-watchos-remote-logger.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-2/pulse-watchos.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you are working on a tvOS app, you can also find Pulse useful. It provides the same features as a watchOS app: you can view logs directly on the device and it supports remote logging.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot; alt=&quot;Remote logger on watchOS&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/tvos-remote.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://kean-docs.github.io/pulse/documentation/pulse/&quot;&gt;documentation&lt;/a&gt; for Pulse is rewritten from scratch and generated using &lt;a href=&quot;https://developer.apple.com/documentation/docc&quot;&gt;DocC&lt;/a&gt;. With the addition of Pulse, all my frameworks now use DocC. It’s used not just for generating API references, but for articles as well. And most of the types have “extensions” as well as refining the documentation.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;PulseUI on macOS&quot; src=&quot;https://kean.blog/images/posts/pulse-2/docc.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;The new features are exciting, but there are also some significant performance improvements under the hood I would like to mention.&lt;/p&gt;

&lt;h3 id=&quot;space-savings&quot;&gt;Space Savings&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://kean-docs.github.io/pulsecore/documentation/pulsecore/loggerstore&quot;&gt;LoggerStore&lt;/a&gt; now uses up to &lt;strong&gt;90% less space&lt;/strong&gt;. The main improvement comes from a simple yet effective change: compression. The request and response blobs are now compressed using Apple’s &lt;a href=&quot;https://en.wikipedia.org/wiki/LZFSE&quot;&gt;lzfse&lt;/a&gt;. I found it to be about x3 times faster than zlib and with often better compression ratio. The same compression is also used for remote logging.&lt;/p&gt;

&lt;p&gt;The small blobs (&amp;lt;16 KB) are now &lt;a href=&quot;https://www.sqlite.org/intern-v-extern-blob.html&quot;&gt;efficiently stored&lt;/a&gt; directly in SQLite. The limit might seem small, but it applies &lt;em&gt;after&lt;/em&gt; compression. If you take an example &lt;a href=&quot;https://github.com/kean/Pulse/blob/master/App/Shared/Resources/repos.json&quot;&gt;repos.json&lt;/a&gt; file with 3020 lines, it compresses from 161 KB to just over 11 KB which fits the inline limit.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;When performing optimizations, it’s crucial to measure. Make sure your test data well represents the real data. If you are working with Core Data or SQLite, you can use &lt;a href=&quot;https://sqlitebrowser.org&quot;&gt;DB Browser for SQLite&lt;/a&gt; to analyze the data. You can also use &lt;a href=&quot;https://www.sqlite.org/sqlanalyze.html&quot;&gt;sqlite3_analyzer&lt;/a&gt; to see exactly how much space each table and index takes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There a bunch of other small improvements under the hood. For example, I switched from SHA256 to SHA1 for generating keys for blob deduplication. SHA1 is 30% faster and uses just 40 characters instead of 64 for file names.&lt;/p&gt;

&lt;p&gt;The new store also no longer stores any unstructured data – everything is stored in the database, including task metrics, and can be queried with SQL. You can track exactly how much space Pulse is taking on the new Store Details screen.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Store details&quot; src=&quot;https://kean.blog/images/posts/pulse-2/store-details.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;optimized-for-images&quot;&gt;Optimized for Images&lt;/h3&gt;

&lt;p&gt;The logger now stores compressed thumbnails instead of full-resolution images. It uses HEIF for efficient storage, and the deduplication used for other blobs also applies. The savings are dramatic. For the image I used in the demo, I saved 99.9% of space.&lt;/p&gt;

&lt;p&gt;Thanks to these improvements, it became feasible to use Pulse with Nuke, so starting with &lt;a href=&quot;https://github.com/kean/Nuke/releases/tag/11.1.0&quot;&gt;Nuke 11.1&lt;/a&gt;, there is now &lt;a href=&quot;https://github.com/kean/Nuke/pull/583&quot;&gt;a simple way&lt;/a&gt; to integrate the frameworks.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Pulse image viewer showing an image preview&quot; src=&quot;https://kean.blog/images/posts/pulse-2/image-viewer.png&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;document-format&quot;&gt;Document Format&lt;/h3&gt;

&lt;p&gt;Pulse allows you to share logs and open them on another machine. For that reason, it has a custom document format (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pulse&lt;/code&gt;). When I created the &lt;a href=&quot;/post/pulse-store&quot;&gt;original format&lt;/a&gt; for v1.0, I decided to use ZIP archives as its base because of its compression and the ability to decompress contents (blobs) on-demand. I used an excellent &lt;a href=&quot;https://github.com/weichsel/ZIPFoundation&quot;&gt;ZIPFoundation&lt;/a&gt; framework for this purpose. But after increasing the minimum deployment target in Pulse 2.0, I was finally able to use &lt;a href=&quot;https://developer.apple.com/documentation/compression/algorithm/lzfse&quot;&gt;lzfse&lt;/a&gt; and decided to re-evaluate this decision.&lt;/p&gt;

&lt;p&gt;In Pulse 2.0, the blobs are compressed not just before sharing, but also at rest, significantly reducing the store size. It also means they no longer need to be compressed before sharing, making it much faster. And with native &lt;a href=&quot;https://developer.apple.com/documentation/compression/algorithm/lzfse&quot;&gt;lzfse&lt;/a&gt; taking care of compression, I was able to remove &lt;a href=&quot;https://github.com/weichsel/ZIPFoundation&quot;&gt;ZIPFoundation&lt;/a&gt; reducing the framework size. I switched to SQLite, a &lt;a href=&quot;https://sqlite.org/appfileformat.html&quot;&gt;perfect choice&lt;/a&gt; for creating custom document formats.&lt;/p&gt;

&lt;p&gt;These are not the only improvements to sharing. The new Sharing Options screen allows you to limit how much data you want to share. By default, it only sends the logs from the current session, often dramatically reducing the shared file size.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; alt=&quot;Sharing options screen&quot; src=&quot;https://kean.blog/images/posts/pulse-2/sharing-options.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;integrations&quot;&gt;Integrations&lt;/h2&gt;

&lt;p&gt;In addition to support for &lt;a href=&quot;https://github.com/kean/Nuke&quot;&gt;Nuke&lt;/a&gt;, Pulse is easy to integrate with any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt;-based API clients, such as &lt;a href=&quot;https://github.com/kean/Get&quot;&gt;Get&lt;/a&gt;. You can integrate it with Pulse 2.0 in under 20 seconds to view your network traffic in-app.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video muted=&quot;&quot; controls=&quot;&quot; playsinline=&quot;&quot; alt=&quot;Custom context menu preview&quot;&gt;
  &lt;source src=&quot;https://kean.blog/videos/pulse-2/get-pulse.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;I can’t close this without talking a bit about SwiftUI, which 90%+ of Pulse is built with. I developed it in Xcode 14 (betas 3-5), and I was pleasantly surprised with the Canvas improvements. I think we are now at the inflection point where the pros of using SwiftUI outweigh the cons, assuming you can target only the latest OS versions. The issue isn’t even the lack of the new APIs on the earlier versions, but the differences in the behavior of the existing ones.&lt;/p&gt;

&lt;p&gt;Both &lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; and &lt;a href=&quot;https://github.com/kean/PulsePro&quot;&gt;Pulse Pro&lt;/a&gt; are open-source and free, as usual. And if you decide to use it in your app, please &lt;a href=&quot;https://github.com/sponsors/kean&quot;&gt;support&lt;/a&gt; it on GitHub.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;If you want pending tasks to work on the previous versions of the OS, you can track the same events manually using &lt;a href=&quot;https://kean-docs.github.io/pulse/documentation/pulse/networklogger/logtaskcreated(_:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NetworkLogger/logTaskCreated(_:)&lt;/code&gt;&lt;/a&gt; API. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 16 Aug 2022 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/pulse-2</link>
        <guid isPermaLink="true">https://kean.blog/post/pulse-2</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Introducing CreateAPI</title>
        <description>&lt;p&gt;If you’ve tried OpenAPI spec generators, you know how it goes. They get you about 60-80% there, but you end up having to modify the code by hand. For one of the specs that I tested – &lt;a href=&quot;https://github.com/github/rest-api-description&quot;&gt;GitHub REST API spec&lt;/a&gt; – a popular code generator produces more than 300 compile-time errors, which is not ideal. With &lt;a href=&quot;https://github.com/kean/CreateAPI&quot;&gt;CreateAPI&lt;/a&gt;, I pushed well beyond just making sure the generated code compiles.&lt;/p&gt;

&lt;div class=&quot;blog-new-li&quot;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;It’s Fast&lt;/strong&gt;: processes specs with 100K lines of YAML in less than a second&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Reliable&lt;/strong&gt;: tested on 1KK lines of &lt;a href=&quot;https://apis.guru&quot;&gt;publicly available&lt;/a&gt; OpenAPI specs producing correct code every time&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Smart&lt;/strong&gt;: generates Swift code that looks like it’s carefully written by hand&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Customizable&lt;/strong&gt;: offers a ton of customization options&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;I started with a powerful foundation: &lt;a href=&quot;https://github.com/mattpolzin/OpenAPIKit&quot;&gt;OpenAPIKit&lt;/a&gt; by &lt;a href=&quot;https://github.com/mattpolzin&quot;&gt;Mathew Polzin&lt;/a&gt;. It takes care of the parsing and validation, so CreateAPI can focus purely on code generation.&lt;/p&gt;

&lt;h2 id=&quot;snapshot-testing&quot;&gt;Snapshot Testing&lt;/h2&gt;

&lt;p&gt;I went to the extreme when testing CreateAPI. I wrote a simple snapshot testing utility that takes a couple of hundred publicly available OpenAPI specs from &lt;a href=&quot;https://apis.guru&quot;&gt;APIs Guru&lt;/a&gt; and other sources and feeds them to CreateAPI. It then makes sure that the generated code doesn’t miss any schemas, paths, or properties from the input specs, and that all of the generated code compiles successfully.&lt;/p&gt;

&lt;p&gt;One of the main examples I used was &lt;a href=&quot;https://github.com/github/rest-api-description&quot;&gt;GitHub REST API spec&lt;/a&gt;. It’s a massive spec with more than 70K lines of YAML. For this spec, I went further than just making sure it compiles. I went through all (or almost all) paths and entities making sure they match the documentation. I also wrote unit tests that take mock JSONs provided by GitHub and make sure the Codable models handle them correctly at runtime. I’m planning to release the generated code as a separate framework – &lt;a href=&quot;https://github.com/kean/OctoKit&quot;&gt;OctoKit&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;optimizations&quot;&gt;Optimizations&lt;/h2&gt;

&lt;p&gt;I just have to talk about performance. CreateAPI is optimized to run on multicore processors like M1 Pro/Max. There is parallelization at every stage; even parsing is partially parallelized. And code generation saturates &lt;em&gt;all&lt;/em&gt; available cores. As a result, it processes 100K lines of OpenAPI specs in &lt;em&gt;less than a second&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;CreateAPI was built entirely on a 10-Core M1 Pro, which made it feasible to work with these massive test inputs: 1KK of OpenAPI specs and 500K of generated Swift code. This beast compiles 500K lines of Swift code (heavily modularized) in less than 2 minutes – absolutely insane.&lt;/p&gt;

&lt;h2 id=&quot;code-generation&quot;&gt;Code Generation&lt;/h2&gt;

&lt;p&gt;And finally, let’s talk about some of the optimizations CreateAPI does to the produced code. Here are just a few examples:&lt;/p&gt;

&lt;h3 id=&quot;swifty-booleans&quot;&gt;Swifty Booleans&lt;/h3&gt;

&lt;blockquote class=&quot;quotation&quot;&gt;
  &lt;p&gt;Uses of Boolean methods and properties should read as assertions about the receiver when the use is nonmutating, e.g. x.isEmpty&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;From https://www.swift.org/documentation/api-design-guidelines/&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, it’s uncommon for web APIs to follow this convention. This is why CreateAPI automatically generates Swifty property names for you.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;boolean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Becomes:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;isVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s also smart enough not to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is&lt;/code&gt; when it’s not needed:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;has_issues&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;boolean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hasIssues&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;For every “smart” feature, CreateAPI has an option to either fully disable it, or add exceptions. For example, you can rename anything in the generated coded: properties, types, enums, etc:&lt;/p&gt;

  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rename:
  # Rename properties, example:
  #   - name: firstName
  #   - SimpleUser.name: firstName
  properties: {}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;abbreviations&quot;&gt;Abbreviations&lt;/h3&gt;

&lt;p&gt;CreateAPI automatically capitalizes common abbreviations, making them look more “Swifty”.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repoId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repoUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repoID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repoURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ordering-properties&quot;&gt;Ordering Properties&lt;/h3&gt;

&lt;p&gt;Most generators ignore the original order of properties from the specs. Yet they are often designed with a particular order in mind. CreateAPI preserves the order of the properties (thanks to the ordered dictionaries in &lt;a href=&quot;https://github.com/mattpolzin/OpenAPIKit&quot;&gt;OpenAPIKit&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;CreateAPI also has an option to order properties alphabetically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;removing-noise&quot;&gt;Removing Noise&lt;/h3&gt;

&lt;p&gt;Readability is important. CreateAPI makes sure that the generated code is clean and concise. For example, most generators add all enum case names by default – it’s just simpler to implement it this way:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Equatable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CaseIterable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;placed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;placed&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;approved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;approved&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delivered&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;delivered&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CreateAPI doesn’t add redundant names. It’s a small thing, but all these small things add up:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CaseIterable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;placed&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;approved&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delivered&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another example is requests and queries. For simple requests, CreateAPI doesn’t generate a dedicated class or struct to represent a request and generates a compact version instead. It significantly reduces the amount of generated code.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;OctoKit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Reaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;accessToken&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same applies to simple URL queries:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;makeGetQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;perPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;per_page&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;perPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cursor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;inlining-typealiases&quot;&gt;Inlining Typealiases&lt;/h3&gt;

&lt;p&gt;Authors of OpenAPI specs often define arrays with items defined inline:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;search-result-text-matches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Search Result Text Matches&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;array&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;object_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;object_type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;nullable&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Many tools fail to produce anything useful for these specs. Here’s one example:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;typealias&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResultTextMatches&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SearchResultTextMatches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It doesn’t compile - the element definition is completely missing and the typealias refers to itself.&lt;/p&gt;

&lt;p&gt;In that situation, CreateAPI uses multiple techniques that together produce the following output:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResultTextMatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;objectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;objectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;objectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;objectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CodingKeys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CodingKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objectURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;object_url&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objectType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;object_type&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;property&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is what happens under the hood:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;CreateAPI encounters a schema named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;search-result-text-matches&quot;&lt;/code&gt; of an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;array&quot;&lt;/code&gt; type.&lt;/li&gt;
  &lt;li&gt;It checks the type of the element – it’s an anonymous object defined inline.&lt;/li&gt;
  &lt;li&gt;It generates a struct declaration for the element schema and names it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SearchResultTextMatch&lt;/code&gt; - a singularized form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;search-result-text-matches&quot;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;It skips adding a typealias because it can be inlined later (can be disabled with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isInliningTypealiases&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;search-result-text-matches&quot;&lt;/code&gt; schema is used as a reference anywhere else in the spec, CreateAPI inlines the typealias value:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Usage&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TopicSearchResultItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;textMatches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SearchResultTextMatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;comments&quot;&gt;Comments&lt;/h3&gt;

&lt;p&gt;CreateAPI adds all comments and examples from the spec, but it’s also smart enough not to add redundant comments.&lt;/p&gt;

&lt;p&gt;For example, in the following schema, the title matches the description, so it can keep only the title. But the title also matches the name of the entity, so the comments are completely redundant.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;validation-error&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Validation Error&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Validation Error&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;documentation_url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ValidationError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;documentationURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;edge-cases&quot;&gt;Edge Cases&lt;/h3&gt;

&lt;p&gt;After going through 1KK lines of OpenAPI specs, I hit a ton of edge cases. I just want to add one example. Here’s what I found in Telegram Bot spec:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;emoji&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;🎲&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;🎲&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;🎯&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;🏀&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;⚽&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;🎰&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;None of the generators I tried were able to handle it correctly. The problem is that a naive approach doesn’t work because you can’t use emojis as enum cases in Swift. CreateAPI uses String Unicode APIs to  generate the following case names:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Emoji&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Codable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CaseIterable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gameDie&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;🎲&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;directHit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;🎯&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;basketballAndHoop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;🏀&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;soccerBall&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;⚽&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slotMachine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;🎰&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;pre-release&quot;&gt;Pre-Release&lt;/h2&gt;

&lt;p&gt;I pushed the first pre-release version of &lt;a href=&quot;https://github.com/kean/CreateAPI&quot;&gt;CreateAPI&lt;/a&gt;, and it joined the rest of the frameworks for working with web APIs:&lt;/p&gt;

&lt;div class=&quot;blog-new-li&quot;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/Nuke&quot;&gt;Nuke&lt;/a&gt; - image loading and caching&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/NukeUI&quot;&gt;NukeUI&lt;/a&gt; - UI components for image loading&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/Get&quot;&gt;Get&lt;/a&gt; - web API client built using async/await&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/CreateAPI&quot;&gt;CreateAPI&lt;/a&gt; - code generator for OpenAPI specs&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; - network logger and inspector&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/URLQueryEncoder&quot;&gt;URLQueryEncoder&lt;/a&gt; - URL query encoder based on Codable&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/NaiveDate&quot;&gt;NaiveDate&lt;/a&gt; - working with dates without timezones&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/kean/HTTPHeaders&quot;&gt;HTTPHeaders&lt;/a&gt; - simple handling of HTTP response headers&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;There are still a ton of improvements in the backlog, and I would also appreciate any community contributions!&lt;/p&gt;
</description>
        <pubDate>Tue, 04 Jan 2022 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/create-api</link>
        <guid isPermaLink="true">https://kean.blog/post/create-api</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Web API Client in Swift</title>
        <description>&lt;div class=&quot;UpdatesSections&quot;&gt;
  &lt;p&gt;&lt;strong&gt;Updates&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Aug 19, 2022. Updated to Get 2.0&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;It’s been more than four years since my previous &lt;a href=&quot;/post/api-client&quot;&gt;API Client in Swift&lt;/a&gt; (archived) post. A lot has changed since then. With the addition of Async/Await and Actors, it’s now easier and more fun than ever to design custom web API clients in Swift. The new version went through a radical redesign and I can’t wait to share more about it.&lt;/p&gt;

&lt;p&gt;I’m going to focus on REST APIs and use &lt;a href=&quot;https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api&quot;&gt;GitHub API&lt;/a&gt; as an example. Before I jump in, here is a quick look at the final result:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Create a client&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://api.github.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Sending requests&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;value&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/user/emails&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kean@example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;The code from this article is the basis of &lt;a href=&quot;https://github.com/kean/Get&quot;&gt;kean/Get&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;Every backend has its quirks and usually requires a client optimized for it. This article is a collection of ideas that you can use for writing one that matches your backend perfectly.&lt;/p&gt;

&lt;p&gt;The previous version of the client was built using &lt;a href=&quot;https://github.com/Alamofire/Alamofire&quot;&gt;Alamofire&lt;/a&gt; and &lt;a href=&quot;https://github.com/ReactiveX/RxSwift&quot;&gt;RxSwift&lt;/a&gt;. It was a good design at the time, but with the recent Swift changes, I don’t think you need dependencies anymore.  I’m going with Apple technologies exclusively: &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsession&quot;&gt;URLSession&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/swift/codable&quot;&gt;Codable&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10132/&quot;&gt;Async/Await&lt;/a&gt;, and &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10133/&quot;&gt;Actors&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;implementing-a-client&quot;&gt;Implementing a Client&lt;/h2&gt;

&lt;p&gt;Let’s start by defining a type for representing requests. It can be as complicated as you need, but here is a good starting point:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;HTTPMethod&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Encodable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To execute the requests, you use a client&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. It’s a small wrapper on top of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; that is easy to modify and extend. You initialize it with a host making it easy to change the environments at runtime.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;actor&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLSession&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ..&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLSessionConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClientDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;DefaultAPIClientDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are two types of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send()&lt;/code&gt; methods – one for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Decodable&lt;/code&gt; types and one for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Void&lt;/code&gt; that isn’t decodable.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Decodable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;urlRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;makeURLRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;urlRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// The final implementation uses a custom URLSession wrapper compatible with iOS 13.0&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; takes full advantage of async/await, including the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10095/&quot;&gt;async/await APIs&lt;/a&gt;. It also performs encoding and decoding on detached tasks, reducing the amount of work done on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;detached&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Getting back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Codable&lt;/code&gt;, I think we, as a developer community, have finally tackled the challenge of parsing JSON in Swift. So I’m not going to focus on it. If you want to learn more, I wrote &lt;a href=&quot;https://kean.blog/post/codable-tips-and-tricks&quot;&gt;a post&lt;/a&gt; a couple of years ago with some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Codable&lt;/code&gt; tips – most are still relevant today.&lt;/p&gt;

&lt;p&gt;The rest of the code is relatively straightforward. I’m using &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlcomponents&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLComponents&lt;/code&gt;&lt;/a&gt; to create URLs, which is important because it &lt;a href=&quot;https://en.wikipedia.org/wiki/Percent-encoding&quot;&gt;percent-encodes&lt;/a&gt; the parts of the URLs that need it.&lt;/p&gt;

&lt;p&gt;The client works with JSON, so its sets the respective &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type&quot;&gt;“Content-Type”&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept&quot;&gt;“Accept”&lt;/a&gt; HTTP header values automatically.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;makeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Encodable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URLRequest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;allHTTPHeaderFields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;httpMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;httpBody&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;detached&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;encoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;AnyEncodable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;value&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;forHTTPHeaderField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;forHTTPHeaderField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;forHTTPHeaderField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;forHTTPHeaderField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Being able to use async functions right inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makeRequest()&lt;/code&gt; method without all the boilerplate of using closures is just pure joy.&lt;/p&gt;

&lt;p&gt;The only remaining bit is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;validate()&lt;/code&gt; method where I check that the response status code is acceptable. It also gives a delegate a chance to decide what error to throw. Most APIs will have a standard JSON format for errors – this is a good place to parse it.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;httpResponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;HTTPURLResponse&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;unacceptableStatusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it, a baseline &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt;. It already covers most of the needs of most users and there many ways to extend it.&lt;/p&gt;

&lt;h2 id=&quot;extending-the-client&quot;&gt;Extending the Client&lt;/h2&gt;

&lt;p&gt;Getting the basics right is usually easy, but what about more advanced use-cases?&lt;/p&gt;

&lt;h3 id=&quot;user-authorization&quot;&gt;User Authorization&lt;/h3&gt;

&lt;p&gt;Every authorization system has its quirks. If you use &lt;a href=&quot;https://oauth.net/2/&quot;&gt;OAuth 2.0&lt;/a&gt; or a similar protocol, you need to send an access token with every request. One of the common ways is by setting an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization&quot;&gt;“Authorization”&lt;/a&gt; header.&lt;/p&gt;

&lt;p&gt;One of the advantages of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alamofire&lt;/code&gt; is the infrastructure for adapting and retrying requests which is &lt;a href=&quot;https://www.avanderlee.com/swift/authentication-alamofire-request-adapter/&quot;&gt;often used for authorization&lt;/a&gt;. Reimplementing it with callbacks is no fun, but with async/await, it’s a piece of cake.&lt;/p&gt;

&lt;p&gt;The first piece is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client(_:willSendRequest:)&lt;/code&gt; delegate method that you can use to “sign” the requests.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;YourAPIClientDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClientDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;willSendRequest&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;inout&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;setValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Bearer: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;forHTTPHeaderField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;warning&quot;&gt;
  &lt;p&gt;If you look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setValue(_:forHTTPHeaderField:)&lt;/code&gt; &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlrequest/2011447-setvalue&quot;&gt;documentation&lt;/a&gt;, you’ll see a list of &lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsurlrequest#1776617&quot;&gt;Reserved HTTP Headers&lt;/a&gt; that you shouldn’t set manually. “Authorization” is one of them… &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; supports seemingly every authorization mechanism except the most popular one. Setting an “Authorization” header manually is still &lt;a href=&quot;https://developer.apple.com/forums/thread/89811&quot;&gt;the least worst&lt;/a&gt; option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client(_:willSendRequest:)&lt;/code&gt; method is also a good way to provide default headers, like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent&quot;&gt;“User-Agent”&lt;/a&gt;. But you can also provide fields that don’t change using &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;httpAdditionalHeaders&lt;/code&gt;&lt;/a&gt; property of &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessionconfiguration&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSessionConfiguration&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your access tokens are short-lived, it is important to implement a proper refresh flow. Again, easy with async/await.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;performRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;shouldRetry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;performRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I count four &lt;a href=&quot;https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md#suspension-points&quot;&gt;suspension points&lt;/a&gt;. Now think how more complicated and error-prone it would’ve been to implement it with callbacks.&lt;/p&gt;

&lt;p&gt;Now all you need is to implement a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shouldClientRetry(_:withError:)&lt;/code&gt; method in your existing delegate and add the token refresh logic.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;YourAPIClientDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClientDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shouldClientRetry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;withError&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;unacceptableStatusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;YourError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;refreshAccessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;refreshAccessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// TODO: Refresh access token&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;warning&quot;&gt;
  &lt;p&gt;The client might call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shouldClientRetry(_:withError:)&lt;/code&gt;  multiple times (once for each failed request). Make sure to coalesce the requests to refresh the token and handle the scenario with an expired refresh token.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t show this code in the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; implementation to not over-complicate things, but the project you find at GitHub already &lt;a href=&quot;https://github.com/kean/Get&quot;&gt;supports it&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;If you are thinking about using auto-retries for connectivity issues, consider using &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessionconfiguration/2908812-waitsforconnectivity&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;waitsForConnectivity&lt;/code&gt;&lt;/a&gt; instead. If the request does fail with a network issue, it’s usually best to communicate an error to the user. With &lt;a href=&quot;https://developer.apple.com/documentation/network/nwpathmonitor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NWPathMonitor&lt;/code&gt;&lt;/a&gt; you can still monitor the connection to your server and retry automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;client-authorization&quot;&gt;Client Authorization&lt;/h3&gt;

&lt;p&gt;On top of authorizing the user, most services will also have a way of authorizing the client. If it’s an API key, you can set it using the same way as an “Authorization” header. You may also want to &lt;a href=&quot;https://nshipster.com/secrets/&quot;&gt;obfuscate&lt;/a&gt; it, but remember that client secrecy is impossible.&lt;/p&gt;

&lt;p&gt;Another less common but more interesting approach is &lt;a href=&quot;https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/&quot;&gt;mTLS&lt;/a&gt; (mutual TLS) where it’s not just the server sending a certificate – the client does too. One of the advantages of using certificates is that the secret (private key) never leaves the device.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; supports mTLS natively and it’s easy to implement, even when using the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async/await&lt;/code&gt; API (thanks, Apple!).&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;YourTaskDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;URLSessionTaskDelegate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;urlSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;didReceive&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;challenge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLAuthenticationChallenge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;URLSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;AuthChallengeDisposition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;protectionSpace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;challenge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;protectionSpace&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protectionSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;authenticationMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;NSURLAuthenticationMethodServerTrust&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// You&apos;ll probably need to create it somewhere else.&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;credential&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;certificates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;persistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;useCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;credential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;performDefaultHandling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The main challenge with mTLS is getting the private key to the client. You can embed an obfuscated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.p12&lt;/code&gt; file in the app, making it hard to discover, but it’s still not impenetrable.&lt;/p&gt;

&lt;h3 id=&quot;ssl-pinning&quot;&gt;SSL Pinning&lt;/h3&gt;

&lt;p&gt;The task delegate method for handling the server challenges is also a good place for &lt;a href=&quot;https://www.raywenderlich.com/1484288-preventing-man-in-the-middle-attacks-in-ios-with-ssl-pinning&quot;&gt;implementing SSL pinning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning&quot;&gt;multiple&lt;/a&gt; ways how to approach this, especially when it comes to &lt;em&gt;what&lt;/em&gt; to pin. If you pin leaf certificates, you sign up for constant maintenance because certificates need to be rotated. The simplest option seems to be pinning CA certificates or public keys. Starting with iOS 14, it can be &lt;a href=&quot;https://developer.apple.com/news/?id=g9ejcf8y&quot;&gt;done very easily&lt;/a&gt; by adding the keys to the app’s plist file.&lt;/p&gt;

&lt;h3 id=&quot;http-caching&quot;&gt;HTTP Caching&lt;/h3&gt;

&lt;p&gt;Caching is a great way to improve application performance and end-user experience. Developers often overlook &lt;a href=&quot;https://tools.ietf.org/html/rfc7234&quot;&gt;HTTP cache&lt;/a&gt; natively &lt;a href=&quot;https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html&quot;&gt;supported&lt;/a&gt; by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; To enable HTTP caching the server sends special HTTP headers along with the request. Here is an example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
Expires: Mon, 26 Jan 2016 17:45:57 GMT
Last-Modified: Mon, 12 Jan 2016 17:45:57 GMT
ETag: &quot;686897696a7c876b7e&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This response is cacheable and will be &lt;em&gt;fresh&lt;/em&gt; for 1 hour. When it becomes &lt;em&gt;stale&lt;/em&gt;, the client validates it by making a &lt;em&gt;conditional&lt;/em&gt; request using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If-Modified-Since&lt;/code&gt; and/or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If-None-Match&lt;/code&gt; headers. If the response is still fresh the server returns status code &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;304 Not Modified&lt;/code&gt;&lt;/a&gt; to instruct the client to use cached data, or it would return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 OK&lt;/code&gt; with a new data otherwise.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;By default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLCache.shared&lt;/code&gt; with a small disk and memory capacity. You might not know it, but already be taking advantage of HTTP caching.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTTP caching is a flexible system where both the server and the client get a say over what gets cached and how. With HTTP, a server can set restrictions on which responses are cacheable, set an expiration age for responses, provide validators (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ETag&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Last-Modified&lt;/code&gt;) to check stale responses, force revalidation on each request, and more.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt; (and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLCache&lt;/code&gt;) support HTTP caching out of the box. There is a &lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsurlsessiondatadelegate/1411612-urlsession&quot;&gt;set of requirements&lt;/a&gt; for a response to be cached. It’s not just the server that has control. For example, you can use &lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsurlrequest/cachepolicy&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLRequest.CachePolicy&lt;/code&gt;&lt;/a&gt; to modify caching behavior from the client. You can easily extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; to support it if needed&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;cachePolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;CachePolicy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;useProtocolCachePolicy&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;Response&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;Decodable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Pass the policy to the URLRequest&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;downloads&quot;&gt;Downloads&lt;/h3&gt;

&lt;p&gt;By default, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send()&lt;/code&gt; method of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; uses &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsession/3767352-data&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data(for:delegate:)&lt;/code&gt;&lt;/a&gt; method from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt;, but with the existing design, you can easily extend it to support other types of tasks, such as downloads.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;Void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;makeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;xv&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; type is not working for you, it’s easy to extend too. You can also just as easily add new request types, or even &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLRequest&lt;/code&gt; directly if you need to. Although for a typical REST API, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; is all you are ever going to need.&lt;/p&gt;

&lt;h3 id=&quot;environments&quot;&gt;Environments&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; is initialized with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseURL&lt;/code&gt;, making it easy to switch between the environments in runtime. I typically have a debug menu in the apps with all sorts of debug settings, including an environment picker – it’s fast and easy to build with SwiftUI. I added the code generating this screen in &lt;a href=&quot;https://gist.github.com/kean/846eba91cf3471071760ec0db3ddc23e&quot;&gt;a gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;452px&quot; class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/api-client/02.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;defining-an-api&quot;&gt;Defining an API&lt;/h2&gt;

&lt;p&gt;For smaller apps, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; directly without creating an API definition can be acceptable. But it’s generally a good idea to define the available APIs somewhere to reduce the clutter in the rest of the code and remove possible duplication.&lt;/p&gt;

&lt;p&gt;I’ve tried a few different approaches for defining APIs using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt;, but couldn’t decide which one was the best. They all have their pros and cons and are often just a matter of personal preference.&lt;/p&gt;

&lt;p&gt;REST APIs are designed around resources. One of the ideas I had was to create a separate type to represent each of the resources and expose HTTP methods that are available on them. It works best for APIs that closely follow (&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design&quot;&gt;REST API design&lt;/a&gt;). GitHub API is a great example of a REST API, so that’s why I used it in the examples.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// MARK: - /users/{username}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;UsersResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kc&quot;&gt;UsersResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/users/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;UsersResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;

        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// MARK: - /users/{username}/repos&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;UsersResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;ReposResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;ReposResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/repos&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;ReposResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;String&lt;/span&gt;

        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Usage:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;repos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kean&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This API is visually appealing, but it can be a bit tedious to write and less discoverable than simply listing all available calls. I’m also still a bit cautious about over-using nesting. I used to avoid it in the past, but the recent improvements to the Xcode code completion made working with nested APIs much easier. But again, this is just an example.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are man &lt;a href=&quot;https://github.com/Moya/Moya/blob/master/docs/Examples/Basic.md&quot;&gt;suggestions&lt;/a&gt; online to model APIs as an enum. This approach might make your code harder to read and modify and lead to merge conflicts. When you add a new call, you should only need to make changes in one place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tools&quot;&gt;Tools&lt;/h2&gt;

&lt;p&gt;It’s not just Swift that’s getting better. The modern tooling is also fantastic.&lt;/p&gt;

&lt;h3 id=&quot;openapi&quot;&gt;OpenAPI&lt;/h3&gt;

&lt;p&gt;Remember the infamous &lt;a href=&quot;https://developer.apple.com/swift/blog/?id=37&quot;&gt;Working with JSON in Swift&lt;/a&gt; post from 2016? Yeah, we are way ahead now. Ask your backend developers to provide &lt;a href=&quot;https://www.openapis.org/&quot;&gt;OpenAPI&lt;/a&gt; spec for their APIs and use code generation to create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Codable&lt;/code&gt; entities.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.postman.com&quot;&gt;Postman&lt;/a&gt; and &lt;a href=&quot;https://paw.cloud&quot;&gt;Paw&lt;/a&gt; are great ways to explore APIs. You can import an OpenAPI spec and it will generate a Collection for you so that you have all the APIs always at your fingertips. GitHub API that’s I’m using in the examples also &lt;a href=&quot;https://github.blog/2020-07-27-introducing-githubs-openapi-description/&quot;&gt;recently git&lt;/a&gt; its own OpenAPI spec. You can &lt;a href=&quot;https://github.com/github/rest-api-description&quot;&gt;download&lt;/a&gt; it and import it into Postman to try it.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/api-client/03.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Generating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Codable&lt;/code&gt; entities is also extremely easy. There are &lt;a href=&quot;https://openapi.tools&quot;&gt;a ton of tools&lt;/a&gt; available for working with OpenAPI specs. I also created one optimized for &lt;a href=&quot;https://github.com/kean/Get&quot;&gt;Get&lt;/a&gt;, named &lt;a href=&quot;https://github.com/kean/CreateAPI&quot;&gt;CreateAPI&lt;/a&gt; – check it out.&lt;/p&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;If you don’t have an OpenAPI spec, you can turn a sample JSON into a Codable struct using &lt;a href=&quot;https://quicktype.io/&quot;&gt;quicktype.io&lt;/a&gt; and tweak it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;logging&quot;&gt;Logging&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://kean.blog/pulse/home&quot;&gt;Pulse&lt;/a&gt; is a powerful logging system for Apple Platforms.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/api-client/pulse.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It requests a single line to setup to work with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt;. The advantage of doing it at that level is that you don’t need to worry about TLS and SSL pinning, and you collect more information than a typical network proxy does thanks to the direct access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLSession&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://api.github.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;sessionDelegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;PulseCore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;URLSessionProxyDelegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote class=&quot;info&quot;&gt;
  &lt;p&gt;For a complete guide on using Pulse, see the &lt;a href=&quot;https://kean.blog/pulse/guides/overview&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;mocking&quot;&gt;Mocking&lt;/h3&gt;

&lt;p&gt;My preferred way of testing ViewModels is by writing integration tests where I mock only the network responses. There are a many mocking tools to chose from. I’m used WeTransfer’s &lt;a href=&quot;https://github.com/WeTransfer/Mocker&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mocker&lt;/code&gt;&lt;/a&gt; for unit-testing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APIClient&lt;/code&gt; itself.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;api.github.com&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URLSessionConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;default&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;protocolClasses&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;MockingURLProtocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Given&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;xc&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;xv&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)\(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
    &lt;span class=&quot;kc&quot;&gt;Mock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;dataType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;named&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// When&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;Resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                                        
    &lt;span class=&quot;c1&quot;&gt;// Then&lt;/span&gt;
    &lt;span class=&quot;xv&quot;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kean&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;curl&quot;&gt;cURL&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://curl.se&quot;&gt;cURL&lt;/a&gt; doesn’t need an introduction. There is an &lt;a href=&quot;https://gist.github.com/kean/cacb6d2e6bafa912bf130d3db1c2f116&quot;&gt;extension&lt;/a&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLRequest&lt;/code&gt; that I borrowed from Alamofire that I love. It creates a cURL command for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLRequest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ideally, you should call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cURLDescription&lt;/code&gt; on task’s &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiontask/1411649-currentrequest&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;currentRequest&lt;/code&gt;&lt;/a&gt; - it’s has all of the cookies and &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders&quot;&gt;additional HTTP headers&lt;/a&gt; automatically added by the system.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Before I started using Async/Await in Swift, I thought it was mostly syntax sugar, but oh how wrong I was. The way Async/Await is integrated into the language is just brilliant. It solves a bunch of common problems in an elegant way and I barely touched the surface in this article. For example, I’m particularly excited about the new threading model that can reduce or eliminate timesharing of threads. In the future async APIs should be defined exclusively using async functions – bye callbacks, bye &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[weak self]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Actors take full advantage of Async/Await and the new threading model. It’s just as amazing of an addition to the language, and it solves a very common problem. It’s a bit unfortunate that in the case of the API client, I only used them to move work to the background, but there was no mutable state to protect.&lt;/p&gt;

&lt;p&gt;If you want to learn more about Async/Await and Structured Concurrency, look no further than this year &lt;a href=&quot;https://developer.apple.com/videos/wwdc2021/&quot;&gt;WWDC session videos&lt;/a&gt;. They are all fantastically well made, and, if you want to dive a bit deeper, read the Swift Evolution proposals.&lt;/p&gt;

&lt;p&gt;If you just look at the surface level, let’s see how much code I wrote to implement most of the features from this article:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloc&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Sources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APIClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/.&lt;/span&gt;
       &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
       &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
       &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;files&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AlDanial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cloc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.90&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;305.1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;17795.7&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;Language&lt;/span&gt;                     &lt;span class=&quot;n&quot;&gt;files&lt;/span&gt;          &lt;span class=&quot;n&quot;&gt;blank&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;comment&lt;/span&gt;           &lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;Swift&lt;/span&gt;                            &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;             &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;             &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;            &lt;span class=&quot;mi&quot;&gt;148&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;                             &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;             &lt;span class=&quot;mi&quot;&gt;26&lt;/span&gt;             &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;            &lt;span class=&quot;mi&quot;&gt;148&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-------------------------------------------------------------------------------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is not a direct comparison, but Alamofire version 5.4.4 has 7428 lines. This is bonkers. And to think how far we’ve come in just about 10 years – remember &lt;a href=&quot;https://github.com/pokeb/asi-http-request&quot;&gt;ASIHTTPRequest&lt;/a&gt;?&lt;/p&gt;

&lt;div class=&quot;blog-post-references&quot;&gt;
  &lt;h2 class=&quot;no_toc&quot;&gt;References&lt;/h2&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10132/&quot;&gt;Meet async/await in Swift&lt;/a&gt; (WWDC21)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10134/&quot;&gt;Explore structured concurrency in Swift&lt;/a&gt; (WWDC21)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10194/&quot;&gt;Swift concurrency: Update a sample app&lt;/a&gt; (WWDC21)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10133/&quot;&gt;Protect mutable state with Swift actors&lt;/a&gt; (WWDC21)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10254/&quot;&gt;Swift concurrency: Behind the scenes&lt;/a&gt; (WWDC21)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design&quot;&gt;RESTful web API design&lt;/a&gt; (Azure / Architecture)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system&quot;&gt;URL Loading System&lt;/a&gt; (Apple)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers&quot;&gt;Increasing Application Performance with HTTP Cache Headers&lt;/a&gt; (Heroku Dev Center)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7234&quot;&gt;RFC 7234. HTTP/1.1 Caching&lt;/a&gt; (IETF)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.openapis.org/&quot;&gt;OpenAPI Initiative&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.blog/2020-07-27-introducing-githubs-openapi-description/&quot;&gt;Introducing GitHub’s OpenAPI Description&lt;/a&gt; (GitHub Blog)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning&quot;&gt;Certificate and Public Key Pinning&lt;/a&gt; (OWASP)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.avanderlee.com/swift/authentication-alamofire-request-adapter/&quot;&gt;Authentication with signed requests in Alamofire 5&lt;/a&gt; (SwiftLee)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://nshipster.com/secrets/&quot;&gt;Secret Management on iOS&lt;/a&gt; (NSHipster)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/&quot;&gt;What is mutual TLS (mTLS)?&lt;/a&gt; (Cloudflare)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.raywenderlich.com/1484288-preventing-man-in-the-middle-attacks-in-ios-with-ssl-pinning&quot;&gt;Preventing Man-in-the-Middle Attacks in iOS with SSL Pinning&lt;/a&gt; (Ray Wanderlich)&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/news/?id=g9ejcf8y&quot;&gt;Identity Pinning: How to configure server certificates for your app&lt;/a&gt; (Apple)&lt;/li&gt;
  &lt;/ul&gt;

&lt;/div&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The client is defined as an actor, but in this case, it doesn’t have to be – in the sample code there is no mutable state to protect. But by making it actor, I make sure the requests are created and started in the background. Based on my &lt;a href=&quot;/post/nuke-9&quot;&gt;performance testing&lt;/a&gt; in Nuke, creating requests is a relatively expensive operation and it can be advantageous to move it out of the main thread. In the case of the client, it’s just a matter of changing “class” to “actor” – the rest of the APIs are already async, so there is no change in the APIs needed. But it can be swapped out back to “class”. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;The framework’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send()&lt;/code&gt; method takes a closure with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inout URLRequest&lt;/code&gt; as a parameter allowing the user to modify any of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URLRequest&lt;/code&gt; properties. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 21 Nov 2021 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/new-api-client</link>
        <guid isPermaLink="true">https://kean.blog/post/new-api-client</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Best Resources for iOS Engineers</title>
        <description>&lt;p&gt;Last week, I made &lt;a href=&quot;https://twitter.com/a_grebenyuk/status/1458158375942950916?s=20&quot;&gt;a poll&lt;/a&gt; on Twitter asking to share the best learning materials for iOS engineers and was overwhelmed with the number of responses. It was very insightful, and I thought it would be nice to compile the results into a single post, which I did here, adding a few recommendations of my own.&lt;/p&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;blockquote style=&quot;margin-top: 30px&quot; class=&quot;twitter-tweet&quot; data-theme=&quot;dark&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I need your help, Twitter. What are the best learning resources for beginner/intermediate iOS engineers these days (can be paid)?&lt;/p&gt;&amp;mdash; Alex Grebenyuk (@a_grebenyuk) &lt;a href=&quot;https://twitter.com/a_grebenyuk/status/1458158375942950916?ref_src=twsrc%5Etfw&quot;&gt;November 9, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;most-recommended&quot;&gt;Most Recommended&lt;/h2&gt;

&lt;p&gt;There were a couple of outliers that got an absolutely massive number of recommendations.&lt;/p&gt;

&lt;h3 id=&quot;standford-cs193p---developing-app-for-ios&quot;&gt;&lt;a href=&quot;https://cs193p.sites.stanford.edu&quot;&gt;Standford: CS193p - Developing App for iOS&lt;/a&gt;&lt;/h3&gt;
&lt;p class=&quot;blog-inner-subtitle&quot;&gt;Beginner, Free&lt;/p&gt;

&lt;p&gt;The Stanford University’s course CS193p was by far the most recommended resource. I went through the first few videos myself and was blown away by how packed they are with information.&lt;/p&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; data-theme=&quot;dark&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Stanford hands down. I recommend those lectures to everyone. It&amp;#39;s free and it&amp;#39;s some of the highest quality content out there: &lt;a href=&quot;https://t.co/Msv1E7wb2M&quot;&gt;https://t.co/Msv1E7wb2M&lt;/a&gt;&lt;/p&gt;&amp;mdash; Lauren N. Roth (@lauren_n_roth) &lt;a href=&quot;https://twitter.com/lauren_n_roth/status/1458207125679792133?ref_src=twsrc%5Etfw&quot;&gt;November 9, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;p&gt;There are a couple of things to note about this course:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It focuses on SwiftUI. We are still in the transition period where most companies still use UIKit, but it’s already worth investing time into SwiftUI. And you learn much more than just SwiftUI in this course.&lt;/li&gt;
  &lt;li&gt;The course’s primary target audience is students, and it assumes they are familiar with programming fundamentals. If you are learning programming from scratch, you might want to consider going through other resources first. Having said that, the best way to learn programming is by building things.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;100-days-of-swift&quot;&gt;&lt;a href=&quot;https://www.hackingwithswift.com/100&quot;&gt;100 Days of Swift&lt;/a&gt;&lt;/h3&gt;
&lt;p class=&quot;blog-inner-subtitle&quot;&gt;Beginner, Free&lt;/p&gt;

&lt;p&gt;The second most recommended course is &lt;a href=&quot;https://www.hackingwithswift.com/100&quot;&gt;100 Days of Swift&lt;/a&gt;. And it’s not the only resource on &lt;a href=&quot;https://www.hackingwithswift.com&quot;&gt;Hacking with Swift&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/twostraws&quot;&gt;Paul Hudson&lt;/a&gt;. There is a similar course on SwiftUI – &lt;a href=&quot;https://www.hackingwithswift.com/100/swiftui&quot;&gt;100 Days of SwiftUI&lt;/a&gt;, and there are a ton of other articles and videos.&lt;/p&gt;

&lt;p&gt;Unlike Standford’s course, &lt;a href=&quot;https://www.hackingwithswift.com/100&quot;&gt;100 Days of Swift&lt;/a&gt; focuses not on app development, but on Swift itself. If you are just starting learning programming, this course is for you.&lt;/p&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; data-theme=&quot;dark&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Highly recommend&lt;a href=&quot;https://twitter.com/twostraws?ref_src=twsrc%5Etfw&quot;&gt;@twostraws&lt;/a&gt; 100 days of swift &lt;a href=&quot;https://t.co/Ud94Ju0aYg&quot;&gt;https://t.co/Ud94Ju0aYg&lt;/a&gt;&lt;br /&gt;And &lt;a href=&quot;https://twitter.com/objcio?ref_src=twsrc%5Etfw&quot;&gt;@objcio&lt;/a&gt; courses &lt;a href=&quot;https://t.co/DBs9fGdsi1&quot;&gt;https://t.co/DBs9fGdsi1&lt;/a&gt;&lt;/p&gt;&amp;mdash; inailuy☁️ (@cloudssayhello) &lt;a href=&quot;https://twitter.com/cloudssayhello/status/1458159633579843584?ref_src=twsrc%5Etfw&quot;&gt;November 9, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; data-theme=&quot;dark&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://t.co/50rv302VMv&quot;&gt;https://t.co/50rv302VMv&lt;/a&gt; and take the 100 days of Swift course. There also a 100 days of SwiftUI course&lt;/p&gt;&amp;mdash; Nikolaj Nielsen (@NikolajMosb) &lt;a href=&quot;https://twitter.com/NikolajMosb/status/1458323026064314374?ref_src=twsrc%5Etfw&quot;&gt;November 10, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;udemy---the-complete-ios-app-development-bootcamp&quot;&gt;&lt;a href=&quot;https://www.udemy.com/course/ios-13-app-development-bootcamp&quot;&gt;Udemy - The Complete iOS App Development Bootcamp&lt;/a&gt;&lt;/h3&gt;
&lt;p class=&quot;blog-inner-subtitle&quot;&gt;Beginner, Paid&lt;/p&gt;

&lt;p&gt;It seems uncommon for a paid course to get a lot of recommendations, but this one is an exception and it made the shortlist. I wasn’t able to check it out myself, but if you look at the course’s content, it does look &lt;em&gt;complete&lt;/em&gt;. I think right now it might be a better option than Standford’s CS193p if you want to focus on the &lt;em&gt;current&lt;/em&gt; technologies, such as UIKit.&lt;/p&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; data-theme=&quot;dark&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Amazing and thorough. This course is hands down one of the best around: &lt;a href=&quot;https://t.co/y7IG0KD2fk&quot;&gt;https://t.co/y7IG0KD2fk&lt;/a&gt; &lt;a href=&quot;https://twitter.com/yu_angela?ref_src=twsrc%5Etfw&quot;&gt;@yu_angela&lt;/a&gt; &lt;a href=&quot;https://twitter.com/LondonAppBrewer?ref_src=twsrc%5Etfw&quot;&gt;@LondonAppBrewer&lt;/a&gt;&lt;/p&gt;&amp;mdash; Greg Watkins (@GregWatkins69) &lt;a href=&quot;https://twitter.com/GregWatkins69/status/1458498150726094853?ref_src=twsrc%5Etfw&quot;&gt;November 10, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;ray-wanderlich&quot;&gt;&lt;a href=&quot;https://www.raywenderlich.com&quot;&gt;Ray Wanderlich&lt;/a&gt;&lt;/h3&gt;
&lt;p class=&quot;blog-inner-subtitle&quot;&gt;Beginner/Indermediate, Free/Paid&lt;/p&gt;

&lt;p&gt;This site doesn’t need an introduction. I often find myself using it in my daily work. Ray Wanderlich is the go-to place for finding tutorials on specific topics. But it has more than that. It also has &lt;a href=&quot;https://www.raywenderlich.com/ios/paths&quot;&gt;courses&lt;/a&gt;, &lt;a href=&quot;https://www.raywenderlich.com/ios/videos&quot;&gt;videos&lt;/a&gt;, &lt;a href=&quot;https://www.raywenderlich.com/ios/articles&quot;&gt;articles&lt;/a&gt;, and even &lt;a href=&quot;https://www.raywenderlich.com/ios/books&quot;&gt;books&lt;/a&gt;. If there is an iOS topic you are interested in, chances are you’ll find it on this site.&lt;/p&gt;

&lt;h2 id=&quot;first-party-resources&quot;&gt;First-Party Resources&lt;/h2&gt;

&lt;p&gt;I’m surprised nobody linked official Apple documentation in the thread. If I made this poll 6-8 years ago, I’m sure a lot of people would’ve jumped in the thread recommending it. Today, there are a ton of third-party resources that can oftentimes be better than what Apple provides. But despite that, the Apple documentation will always be my primary source of all the information on Apple technologies.&lt;/p&gt;

&lt;div class=&quot;blog-new-li&quot;&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/videos/&quot;&gt;Apple WWDC videos&lt;/a&gt; – probably the main source for me. You can often find information not available anywhere else. It’s a bit unfortunate because finding what you are looking for in a video can be time-consuming.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/&quot;&gt;Apple Developer documentation&lt;/a&gt; – not quite as good as it used to be, but is still a valuable resource&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/tutorials/app-dev-training/&quot;&gt;Apple Tutorials: Develop Apps for iOS&lt;/a&gt; – Apple recent venture in the tutorial-creation business. I went through these tutorials and they are well made and informative, but a bit dry.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/&quot;&gt;The Swift Programming Language&lt;/a&gt; – reading this book is how I learned Swift. It’s a concise and well-written book and is a must-read. You can learn through it instead using a course.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://apple.github.io/swift-evolution/&quot;&gt;Swift Evolution&lt;/a&gt; – the programming guide doesn’t cover everything and sometimes the best source of information on the newest Swift features can be the Swift Evolution proposals&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes/&quot;&gt;Human Interface Guidelines (HIG)&lt;/a&gt; – design is a major part of iOS development and engineers are often relied on to provide their input. This is a must-read resource for any Apple engineer.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/news/&quot;&gt;Apple Developer News and Updates&lt;/a&gt; – add it to your RSS reader to learn about the latest updates on the platforms. For example, about deadlines to switching to new Xcode vresions or SDKs.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://forums.swift.org&quot;&gt;Swift Forums&lt;/a&gt; – if you want to keep be up to date with the latest proposals or even participate in discussions&lt;/li&gt;
  &lt;/ul&gt;

&lt;/div&gt;

&lt;p&gt;There are so many &lt;a href=&quot;https://developer.apple.com/documentation/technologies&quot;&gt;technologies&lt;/a&gt; on Apple platforms that you can’t just go and preemptively learn everything. It’s best when you have a project and you have a need. When you have a need, you have all the motivation you need to study something. Writing an article can also be a good motivator.&lt;/p&gt;

&lt;h2 id=&quot;other-recommendations&quot;&gt;Other Recommendations&lt;/h2&gt;

&lt;p&gt;I received a ton of recommendations in the original &lt;a href=&quot;https://twitter.com/a_grebenyuk/status/1458158375942950916?s=20&quot;&gt;poll&lt;/a&gt;. Here I’m going to list the ones that didn’t make the shortlist, in no particular order. A word of caution – Apple platforms are constantly evolving, make sure what you are reading is up-to-date.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;objc.io&quot;&gt;objc.io&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/DonnyWals&quot;&gt;Donny Walls&lt;/a&gt;’s &lt;a href=&quot;https://www.donnywals.com/the-blog/&quot;&gt;blog&lt;/a&gt; and &lt;a href=&quot;https://www.donnywals.com/books/&quot;&gt;books&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.pointfree.co&quot;&gt;Point·Free&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://editorscut.gumroad.com/l/kickstart-bundle&quot;&gt;The Ultimate Kickstart Bundle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://flight.school&quot;&gt;Flightschool&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.swiftbysundell.com&quot;&gt;Swift by Sundel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.avanderlee.com&quot;&gt;SwiftLee&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/c/LetsBuildThatApp&quot;&gt;Let’s Build that App&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nsscreencast.com/episodes&quot;&gt;NSScreencast&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bignerdranch.com/books/&quot;&gt;Big Nerd Ranch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://seanallen.co&quot;&gt;Sean Allen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.essentialdeveloper.com&quot;&gt;Essential Developer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.appcoda.com/swift-ios15-programming/&quot;&gt;App Coda&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/mattneub&quot;&gt;Matt Neuburg&lt;/a&gt;’s &lt;a href=&quot;https://www.amazon.com/Matt-Neuburg/e/B001H6OITU%3Fref=dbs_a_mng_rwt_scns_share&quot;&gt;books&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://designcode.io&quot;&gt;designcode.io&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/CodeWithChris&quot;&gt;Chris Ching&lt;/a&gt;’s &lt;a href=&quot;https://learn.codewithchris.com/courses/start&quot;&gt;courses&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://books.apple.com/eg/book/develop-in-swift-data-collections/id1556365920&quot;&gt;Develop in Swift Data Collections&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/c/SwiftfulThinking&quot;&gt;Swiftful Thinking&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.udemy.com/user/mohammad-azam-2/&quot;&gt;Azam on Udemy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/c/vincentpradeilles&quot;&gt;Vincent Pradeilles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/StewartLynch&quot;&gt;Stewart Lynch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/BigMtnStudio&quot;&gt;Mark Moeykens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://benscheirman.com&quot;&gt;Ben Scheirman&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gallaugher.com/swift/&quot;&gt;Learn to Build iOS Apps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’d like to add a couple links myself: &lt;a href=&quot;https://iosdevweekly.com&quot;&gt;iOS Dev Weekly&lt;/a&gt;, &lt;a href=&quot;https://nshipster.com&quot;&gt;NSHipster&lt;/a&gt;, &lt;a href=&quot;https://useyourloaf.com&quot;&gt;Use Your Loaf&lt;/a&gt;, &lt;a href=&quot;https://www.cocoawithlove.com&quot;&gt;Cocoa with Love&lt;/a&gt;, &lt;a href=&quot;https://littlebitesofcocoa.com&quot;&gt;Little Bites of Cocoa&lt;/a&gt;, &lt;a href=&quot;http://merowing.info/post/&quot;&gt;Krzysztof Zabłocki&lt;/a&gt;, &lt;a href=&quot;https://oleb.net&quot;&gt;Ole Begemann&lt;/a&gt;, &lt;a href=&quot;https://increment.com/issues/&quot;&gt;Increment&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;software-engineering&quot;&gt;Software Engineering&lt;/h2&gt;

&lt;p&gt;There is more to software engineering than learning a programming language and a platform you are working on. If you want to get a competitive advantage among other engineers, especially if you don’t have a CS degree, invest time in learning the fundamentals. The good news is that degrees are overrated – you can learn everything they do and more by yourself. It’s not like you need any special equipment or anything.&lt;/p&gt;

&lt;p&gt;An incomplete list of everything that you should consider learning (in no particular order):&lt;/p&gt;

&lt;div class=&quot;blog-new-li&quot;&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Git&lt;/strong&gt;. There are a ton of resources on Git, but I found &lt;a href=&quot;https://github.com/git-guides/&quot;&gt;Pro Git&lt;/a&gt; to be the most insightful.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Release and Branching Strategies&lt;/strong&gt;. I think &lt;a href=&quot;https://trunkbaseddevelopment.com&quot;&gt;Trunk-Based Development&lt;/a&gt; is hands down the best branching strategy, especially for larger teams.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Algorithms and Data Structures&lt;/strong&gt;. There are many ways to learn them. I like &lt;a href=&quot;https://www.amazon.com/Algorithm-Design-Manual-Steven-Skiena/dp/1849967202&quot;&gt;The Algorithm Design Manual&lt;/a&gt;, Steven Skiena and &lt;a href=&quot;https://www.amazon.com/Introduction-Algorithms-3rd-MIT-Press/dp/0262033844&quot;&gt;Introduction to Algorithms&lt;/a&gt;, Thomas H. Cormen. The former is more approachable.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Terminal and Bash&lt;/strong&gt;. For me, Swift largely replaced Bash as a scripting language of choice, but I still find myself using Bash often enough. Regardless of your choice, it’s also always worth learning &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference&quot;&gt;regex&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Concurrency and Reactive Programming&lt;/strong&gt;. Concurrency is hands down the most challenging aspect of UI development, and there are a ton of things to learn: &lt;a href=&quot;https://developer.apple.com/news/?id=2o3euotz&quot;&gt;Swift Structured Concurrency&lt;/a&gt;, &lt;a href=&quot;https://developer.apple.com/documentation/combine&quot;&gt;Combine&lt;/a&gt;, parallel programming, synchronization mechanisms, threading. It’s hard, but it comes with practice.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Databases&lt;/strong&gt;. Learn the basics of relational databases, &lt;a href=&quot;https://developer.apple.com/documentation/coredata&quot;&gt;Core Data&lt;/a&gt;, try using &lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; directly, and you are mostly set as far as mobile development goes.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Continuous Integration and Delivery&lt;/strong&gt;. It’s not just the tools, it’s the practice. You can learn a lot about it by reading Martin Fowler’s &lt;a href=&quot;https://martinfowler.com/articles/continuousIntegration.html&quot;&gt;articles&lt;/a&gt;. If you want to dive deeper, there are a few good &lt;a href=&quot;https://www.amazon.com/Continuous-Integration-Improving-Addison-Wesley-Signature-ebook/dp/B0026772IS&quot;&gt;books&lt;/a&gt; available too. But it’s also tools. On iOS, it largely means learning &lt;a href=&quot;https://fastlane.tools&quot;&gt;Fastlane&lt;/a&gt; and a CI/CD tool of your choice. You can start by creating a sample project on GitHub and setting up a simple pipeline using &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Functional Programming&lt;/strong&gt;. It’s finally mainstream. If you use Swift, you are already using functional programming. But Swift isn’t a pure functional language and it can be a lot of fun learning one. I recommend &lt;a href=&quot;https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book.html&quot;&gt;Structure and Interpretation of Computer Programs&lt;/a&gt; (Lisp version) or Haskell (probably the most well-established and well-documented pure functional language out there).&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Software Networking&lt;/strong&gt;. Almost every app uses networking. You generally don’t &lt;em&gt;need&lt;/em&gt; to know anything beyond basics HTTP and JSON, but it’s a lot of fun learning how it actually works under the hood. I suggest learning the basics of the networking protocols: TCP/IP, DNS, go through the HTTP specs. Tools like &lt;a href=&quot;https://www.charlesproxy.com&quot;&gt;Charles&lt;/a&gt; allow you to inspect network traffic. &lt;a href=&quot;https://www.wireshark.org&quot;&gt;Wireshark&lt;/a&gt; can help you learn network protocols. &lt;a href=&quot;https://www.postman.com&quot;&gt;Postman&lt;/a&gt; is the best tool to explore and test APIs. &lt;a href=&quot;https://www.openapis.org&quot;&gt;OpenAPI&lt;/a&gt; is how you document the APIs.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;. I think “best practices” often do more harm than good, especially taken to the extreme. But they can still be valuable. I would suggest going through &lt;a href=&quot;https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670&quot;&gt;Code Complete&lt;/a&gt; by Steve McConnel and skipping everything else.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Processes&lt;/strong&gt;. Atlassian has a pretty good guide on &lt;a href=&quot;https://www.atlassian.com/agile&quot;&gt;Agile&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Soft Skills&lt;/strong&gt;. This is probably more important than half of the list. I don’t think there is a shortcut to learning them. You have to practice and always pay close attention to what you are doing. Reading the following books can give you some valuable insights about this side of engineering: The Pragmatic Programmer, The Mythical Man Month, Herding Cats, The Five Dysfunctions of a Team.&lt;/li&gt;
  &lt;/ul&gt;

&lt;/div&gt;

&lt;p&gt;This list is far from being complete. There are a variety of specializations in software engineering. You can learn anything if you put your mind to it. I think adding new perspectives is valuable. For example, if you are a mobile engineer, it doesn’t mean that you can’t explore a bit of backend development to expand your perspective (especially now that’s it can be done in Swift).&lt;/p&gt;

&lt;h2 id=&quot;interviews&quot;&gt;Interviews&lt;/h2&gt;

&lt;p&gt;So you learned everything about iOS and software engineering, now you are done, right? Not quite. There is software engineering, and there are software engineering interviews. There is some intersection between the two, but completing coding challenges on an interview is very different from what you typically do at work.&lt;/p&gt;

&lt;p&gt;The main difference is speed. Not only do you need to be quick to be able to complete a task within a given timeframe, but you also need to explain your thinking to the interviewer, making it even more complicated. So it’s crucial to set a timer when you are preparing and optimize for speed and practice explaining what you are doing.&lt;/p&gt;

&lt;p&gt;Fortunately, there are a ton of tools to help you prepare for interviews. The gold standard is &lt;a href=&quot;https://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/0984782850/ref=sr_1_1?gclid=Cj0KCQiAhMOMBhDhARIsAPVml-Enj4_92p_aL0INssZJFmQR4bpQuhIu9TMlOySo3et6QtwnzR_dEqYaAqIgEALw_wcB&amp;amp;hvadid=241870593966&amp;amp;hvdev=c&amp;amp;hvlocphy=9003488&amp;amp;hvnetw=g&amp;amp;hvqmt=e&amp;amp;hvrand=16845426662624439861&amp;amp;hvtargid=kwd-20040243067&amp;amp;hydadcr=16409_10304044&amp;amp;keywords=cracking+the+coding+interview&amp;amp;qid=1636928527&amp;amp;qsid=147-7803069-3350963&amp;amp;sr=8-1&amp;amp;sres=0984782850%2C1466208686%2CB09BGKJ3FL%2C0984782869%2CB09559NJKL%2C1537713949%2C1793296634%2C111941847X%2C1519089864%2CB00ISYMUR6%2CB08B3FWYBX%2C0578973839%2CB00U2YQ1Z2%2CB08VL1BLHB%2CB08X8ZXT15%2CB01D24NAL6&amp;amp;srpt=ABIS_BOOK&quot;&gt;Cracking the Coding Interview&lt;/a&gt;, this is hands down the best resource that can help you prepare. I would also recommend &lt;a href=&quot;https://leetcode.com&quot;&gt;leetcode&lt;/a&gt;. Another great resource is &lt;a href=&quot;https://techinterviewhandbook.org/introduction/&quot;&gt;Tech Interview Handbook&lt;/a&gt;. Passing interviews is a special skill and you need to allocate time into learning it unless you want to miss out on some opportunities.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Everyone’s path in software engineering is different. It’s highly technical and it requires a ton of time investment. But it can also be extremely rewarding and fun. I hope you’ll find some of the resources from this post useful and they’ll inspire you to learn something new!&lt;/p&gt;

&lt;div class=&quot;blog-small-v-spacer&quot;&gt;&lt;/div&gt;
&lt;p class=&quot;blog-inner-subtitle&quot;&gt;If you have any suggestions to the list, feel free to &lt;a href=&quot;https://github.com/kean/articles/blob/master/2021-11-15-ios.markdown&quot;&gt;open a PR&lt;/a&gt; on GitHub or &lt;a href=&quot;https://twitter.com/a_grebenyuk&quot;&gt;reach out&lt;/a&gt; to me on Twitter. I intend on keeping this post up-to-date.&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Nov 2021 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/learn-ios</link>
        <guid isPermaLink="true">https://kean.blog/post/learn-ios</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Pulse Pro</title>
        <description>&lt;p&gt;There is always this friction when debugging native apps. You can’t look behind the scenes, unlike browsers with their developer tools. The idea behind Pulse is to bring the same or better debugging experience to native apps.&lt;/p&gt;

&lt;p&gt;With the &lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; framework, you can record logs and network requests, view them directly on your device, and share later. And with a new Pulse Pro app for macOS, you can inspect logs remotely in real-time.&lt;/p&gt;

&lt;p&gt;The new app is designed to be flexible, expansive, and precise while using all the familiar macOS patterns. It makes it easy to navigate large log files with table and text modes, filters, scroller markers, an all-new network inspector, JSON filters, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://kean.blog/images/posts/pulse-pro/main-console-04.png&quot;&gt;
&lt;img class=&quot;full-width JustVertMargins&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/main-console-04.png&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;remote-logging&quot;&gt;Remote Logging&lt;/h2&gt;

&lt;p&gt;The headlining feature of Pulse Pro is Remote Logging which allows you to view logs and network requests in real-time on your Mac. It’s very easy to use, fast, and reliable. To starting using it, first launch the Pulse Pro app and enable Remote Logging.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/remote-logging-03.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;When enabled, your Mac becomes discoverable on your local network. You only need to enable it once. Next time you launch Pulse Pro, it will happen automatically.&lt;/p&gt;

&lt;p&gt;Then, enable Remote Logging on the device with the Pulse framework installed. When your Mac appears on the list, select it to pair the devices. This extra step is needed to ensure that in a space with multiple devices running Pulse Pro, you’ll have a way to choose which one to connect to. You only need to do the pairing once.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/remote-logging-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Pulse is written in SwiftUI and is available on all Apple platforms. The same is true with remote logging. It works on iOS, watchOS, tvOS, and macOS if you need to. And it even works in simulators.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/remote-logging-06.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/remote-logging-07.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;As soon as the devices are paired, there is zero additional interaction needed. When the device connects to the Pulse Pro app, the console opens automatically. You can turn this option off later in settings. And the device remembers which Mac it is paired with, so the next time it’s launched, it will connect right away without your interaction.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/remote-logging-05.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;The logs are stored persistently both on the device with the Pulse framework and by the Pulse Pro app, so you can get back to them later. You can also easily delete them if needed.&lt;/p&gt;

&lt;p&gt;Remote Logging is implemented using the new Apple &lt;a href=&quot;https://developer.apple.com/documentation/network&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Network&lt;/code&gt;&lt;/a&gt; framework. It advertises the Bonjour service on the local network and listens to the TCP connections. The client app replicates all the logs to the server using a simple custom binary protocol. It even sends the logs recorded during the app launch while the connection isn’t up yet.&lt;/p&gt;

&lt;h2 id=&quot;table-mode&quot;&gt;Table Mode&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://kean.blog/pulse/guides/pulse-macos&quot;&gt;previous version&lt;/a&gt; of the Pulse macOS app was fairly limited. It had an inflexible interface with many iOS-isms. There was only one mode to display information – a list view. You couldn’t toggle between vertical and horizontal layouts. Most of the text in the app was non-selectable. Overall, it just wasn’t taking full advantage of the platform. It was a nice native macOS app, but you couldn’t call it a professional app by any stretch of the imagination.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/cover-macos.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mac apps are flexible and adjust to how we individually use them. The first change that I made for Pulse Pro was to replace the list view with a fully adjustable table&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;When you launch the console for the first time, you are greeted with a simple and clean view with only a few columns and no details panel. You can remove and hide columns, and the app remembers your choice. And, of course, you can sort by a column. The high information density and flexibility allow you to really interact with your data.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/table-view-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTableView&lt;/code&gt; felt surprisingly refreshing. I appreciate how well the delegate-based API design scales with complexity. UIs are inherently hacky, and with delegates (and classes) in AppKit, you can customize every bit of it and also make sure you refresh data efficiently to support big data sets.&lt;/p&gt;

&lt;p&gt;When you click to select a row, the details panel is open. You can select between vertical and horizontal layouts, and your choice is saved. If you double-click, the details are opened in a separate window instead.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/table-view-02.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;OK, at this point, you might think these are the table stakes for a macOS app. And you’ll be right. But it was important for me to get these right. I’m primarily an iOS engineer, and I’m breaking new ground here. And bare with me, there is more coming.&lt;/p&gt;

&lt;h2 id=&quot;filters&quot;&gt;Filters&lt;/h2&gt;

&lt;p&gt;Filters were also fully redesigned. You know how in many apps you configure the filters, but then your only options are to either reset them all or change them back to the defaults manually. In Pulse Pro, you can toggle and reset each filter individually, which is surprisingly convenient. You can collapse the sections you rarely use, and the app saves your preferences. To show filters, hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cmd+Option+F&lt;/code&gt; or click the toolbar item.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/table-view-05.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;text-mode&quot;&gt;Text Mode&lt;/h2&gt;

&lt;p&gt;Tables are perfect for many use cases, but sometimes nothing beats good old text. But plain text logs have their own problems. As soon as you go plain text, you lose all of the structure. Text usually has no formatting, you can’t open details for the message, and you can’t filter text dynamically like you can a table view. But what if you could?&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/text-view-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Toggle the “List” mode, and Pulse Pro will generate the formatted text based on your current filters. Change the filters, and it re-generates the text. I’ve done a ton of optimizations to make it as quick as possible. For example, I cache the strings generated for the individual messages and simply re-compose them later.&lt;/p&gt;

&lt;p&gt;The text view itself is also pretty powerful. You can search, filter by line, show line numbers, and change some basic settings like font size. I’m also going the same minimap as in the console soon – more on it later.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/text-view-02.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can also toggle “Show Responses” to see nicely formatted network responses inline with your logs. Unlike the traditional plain text logging system, you can make this choice on the display time instead of the time of logging, which is great.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/text-view-03.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;network-tab&quot;&gt;Network Tab&lt;/h2&gt;

&lt;p&gt;Now let’s talk a bit about networking. The previous app also had a separate “Network” tab, but in reality, it was just the same interface reused interface from the “Console” tab with a filter applied. With Pulse Pro, network tab was completely redesigned. It now has its own view designed from scratch with its own columns and its own filters.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/networking-tab-01.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Networks filters were also designed from scratch. The most powerful filter is probably “General” where you can add as many custom filters as you’d like with an easy-to-use interface. You can filter by URL, host, method, request or response body or headers, and more.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/networking-tab-02.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;response-viewer&quot;&gt;Response Viewer&lt;/h2&gt;

&lt;p&gt;Response viewer has also gotten some love in this release. I added line numbers, the way to change some basic settings, like font size. There is a new color theme that matches Xcode. And, one of the features I’m super excited about, is a &lt;a href=&quot;https://github.com/stedolan/jq&quot;&gt;jq&lt;/a&gt; integration.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/stedolan/jq&quot;&gt;jq&lt;/a&gt; is a tool for processing JSON, applying the given filter. You can find some examples of what it can do at the &lt;a href=&quot;https://stedolan.github.io/jq&amp;quot;&quot;&gt;jq website&lt;/a&gt;. It’s really-really powerful and Pulse Pro makes it super nice to use with side-by-side live results view, and nice output formatting.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/jq-01.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;pins&quot;&gt;Pins&lt;/h2&gt;

&lt;p&gt;When you scan through the logs, you sometimes find important messages you may want to get back to later. With pins, you can easily do that. Just click on the row number that you’d like to pin, and it will appear in the scroller. You can easily navigate back to this message by simply clicking on the mark in the scroller – just like breakpoints and errors in the Xcode scroller.&lt;/p&gt;

&lt;div class=&quot;BlogVideo NewScreenshot&quot;&gt;
&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; preload=&quot;auto&quot;&gt;
  &lt;source src=&quot;https://kean.blog/images/posts/pulse-pro/pins.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;In the point release, I’m also adding error marks to the scroller to make it easier to find them. After you use it, you’ll start wondering how you ever read the logs without it.&lt;/p&gt;

&lt;h2 id=&quot;swiftui-and-appkit&quot;&gt;SwiftUI and AppKit&lt;/h2&gt;

&lt;p&gt;I &lt;a href=&quot;https://kean.blog/post/appkit-is-done&quot;&gt;wrote&lt;/a&gt; the previous version of the app primarily using SwiftUI. You are probably wondering whether that’s the case with Pulse Pro.&lt;/p&gt;

&lt;p&gt;The app is a hybrid of SwiftUI and AppKit, but I think it’s fair to say that most of it is still done in SwiftUI. Let’s go through some examples.&lt;/p&gt;

&lt;p&gt;The all-new filters panel doesn’t have any AppKit code:&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;433px&quot; class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-01.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The preferences window and the new status bar menu&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; I written entirely in SwiftUI. The preferences appear differently in the Canvas than in the app because you can’t use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Settings&lt;/code&gt; in the previews.&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;612px&quot; class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-08.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img width=&quot;352px&quot; class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-09.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wrote table views using AppKit. I could probably build them using SwiftUI’s new &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/table&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Table&lt;/code&gt;&lt;/a&gt; API, but it’s available only in Monterey. By briefly going through it, I’m not sure it supports user-configurable columns, and there are probably more limitations. Fortunately, there is no pressure to write your views using only SwiftUI, and &lt;a href=&quot;https://developer.apple.com/documentation/appkit/nstableview&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTableView&lt;/code&gt;&lt;/a&gt; is very easy to use.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-03.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The text views are also built AppKit (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTextView&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSAttributedString&lt;/code&gt;). But the toolbar at the bottom is built using SwiftUI.&lt;/p&gt;

&lt;p&gt;I’m pretty sure I can also replace it with SwiftUI in the future by adopting &lt;a href=&quot;https://developer.apple.com/documentation/foundation/attributedstring&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AttributedString&lt;/code&gt;&lt;/a&gt; – also Monterey-only. I’m not sure about the line numbers. I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSRulerView&lt;/code&gt; to implement it, so the lack of it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Text&lt;/code&gt; might end up being a deal-breaker. But, again, I don’t see any problem with using AppKit for it.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-04.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Metrics view was largely unchanged in Pulse Pro and it’s still written with SwiftUI. The only change I made was to replace the “key-value” views with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTextView&lt;/code&gt; to make the text selectable. In hindsight, I could probably use the new &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/textselection(_:)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;textSelection(_:)&lt;/code&gt;&lt;/a&gt; API, but too late for that.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-07.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And the existing welcome screen is also implemented using only SwiftUI. I updated it a bit for the new version.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;NewScreenshot&quot; src=&quot;https://kean.blog/images/posts/pulse-pro/canvas-11.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And it’s not just the individual screens or components. The entire app navigation is also done using SwiftUI. I’m using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigationView&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowGroup&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Settings&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handlesExternalEvents&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Commands&lt;/code&gt; to manage windows, toolbars, and command menus. The window management API is fairly limited, so I have to access the underlying windows sometimes. I do that to close windows programmatically, listen to &lt;a href=&quot;https://developer.apple.com/documentation/appkit/nswindow/1419400-willclosenotification&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;willCloseNotification&lt;/code&gt;&lt;/a&gt; events, making the existing window a key window, and more.&lt;/p&gt;

&lt;p&gt;Even where I didn’t use SwiftUI I still used Xcode Canvas, and it significantly sped up the development. If I told you how quickly I wrote it, you probably won’t believe it. I wish it was a bit more stable though.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/kean/Pulse&quot;&gt;Pulse&lt;/a&gt; is open-source and is available on GitHub as usual. You can integrate it into your app now and view logs using the on-device console. The latest version also includes remote logging, but it requires the Pulse Pro app.&lt;/p&gt;

&lt;p&gt;Pulse Pro is &lt;a href=&quot;https://github.com/kean/PulsePro&quot;&gt;available today&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Technically, the previous version of the app was already using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTableView&lt;/code&gt; under the hood for &lt;a href=&quot;https://kean.blog/post/not-list&quot;&gt;performance reasons&lt;/a&gt;, but it was running in a limited mode with no header and only one column. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I created a single menu item with a custom SwiftUI view wrapped into &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/nshostingview&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSHostingView&lt;/code&gt;&lt;/a&gt;. Initially, there was an issue where it would only receive user events when one of the app’s windows was key. But thanks to &lt;a href=&quot;https://stackoverflow.com/a/64089921/1486308&quot;&gt;this answer&lt;/a&gt;, I got it working. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 05 Oct 2021 14:00:00 +0000</pubDate>
        <link>https://kean.blog/post/pulse-pro</link>
        <guid isPermaLink="true">https://kean.blog/post/pulse-pro</guid>
        
        <category>programming</category>
        
        
        <category>programming</category>
        
      </item>
    
  </channel>
</rss>
