<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Mobile Engineer]]></title><description><![CDATA[Mobile engineering from the inside. Real world software development best practices, techniques, architecture, software development lifecycle, process, and more. Useful for mobile engineers junior to senior, tech leads, engineering managers. ]]></description><link>https://newsletter.mobileengineer.io</link><image><url>https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png</url><title>The Mobile Engineer</title><link>https://newsletter.mobileengineer.io</link></image><generator>Substack</generator><lastBuildDate>Sun, 24 May 2026 19:15:46 GMT</lastBuildDate><atom:link href="https://newsletter.mobileengineer.io/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Alex Bush]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[mobileengineer@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[mobileengineer@substack.com]]></itunes:email><itunes:name><![CDATA[Alex Bush]]></itunes:name></itunes:owner><itunes:author><![CDATA[Alex Bush]]></itunes:author><googleplay:owner><![CDATA[mobileengineer@substack.com]]></googleplay:owner><googleplay:email><![CDATA[mobileengineer@substack.com]]></googleplay:email><googleplay:author><![CDATA[Alex Bush]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[[The Mobile Report] Issue #1: Two Kinds of Teams. AI Tools. Mobile Fullstack.]]></title><description><![CDATA[New newsletter format in addition to regular articles. Two kinds of teams and AI-augmented development. Push notifications and mobile fullstack development.]]></description><link>https://newsletter.mobileengineer.io/p/the-mobile-report-issue-1-two-kinds</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/the-mobile-report-issue-1-two-kinds</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Thu, 21 May 2026 11:03:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>About this new format</strong></h2><p>I want to change things up a little bit with <em>The Mobile Engineer</em> and try out a periodical publication like this one, in addition to the occasional pieces I&#8217;ve been doing here. We&#8217;ll start with every two weeks for now to see if I can keep up with that cadence.</p><p>In each issue I&#8217;ll be sharing the latest stuff I&#8217;ve been reading (news, interesting things I run into, developments, etc.) from the mobile development world. Along with my opinion and take as a senior mobile engineer on each one of them.</p><p>I&#8217;ll be trying to keep some topics ongoing across issues, things like <em>Mobile at Scale</em>, <em>Architecture &amp; Design Patterns</em>, and probably <em>AI-augmented development</em> (since it&#8217;s the new wave to ride and it&#8217;s genuinely useful). And I&#8217;ll also point back to my own archive at least once per issue so you have somewhere to dig deeper.</p><p>This doesn&#8217;t mean that my newsletter is going to turn into yet another article-review &#8220;weekly&#8221; newsletter, which there are plenty of out there. I will still post my regular articles with architecture and insights I find throughout my work building mobile apps. But what I&#8217;m trying to do here is just have something more regular that helps me digest what is going on in our fast-paced changing world, and hopefully is also useful to you guys.</p><p>I&#8217;m happy to hear feedback on this and how I can improve it to be more valuable to you, so let me know, email me, leave a comment, or DM me.</p><div><hr></div><h2><strong>The Take</strong></h2><p>Two groups of teams are starting to emerge in software development these days, the ones that are embracing AI and the ones that aren&#8217;t.</p><p>It feels like we&#8217;ve been through this before.</p><p>Before the AI, mobile teams could be bucketed into two groups: those that put effort into the craft, care about architecture, design patterns, SOLID principles, CI/CD, etc. and keep their apps and codebases easy to change, and those not caring about any of that and following Apple dogma that MVCs and storyboards are great and that you should code one simple way no matter how complex or large your application and teams get.</p><p>The first group of teams moved evenly and steadily in the beginning, and continued to ship things steadily as their app and team grew over time.</p><p>The second group of teams, on the other hand, would ship fast at the MVP stage and maybe through a couple more iterations after that. But then they would slowly ground to a halt under the weight of bugs, tech debt, and an unwieldy codebase that nobody could touch anymore without breaking several other unrelated things along the way.</p><p>The same kind of divide is starting to happen now with AI.</p><p>The teams that are putting in the effort and building guardrails, changing their processes to adapt to this new tool, etc., are starting to move faster. And the ones that aren&#8217;t are starting to be left behind already.</p><p>Here&#8217;s the irony though.</p><p>The things that mattered for that first group of teams (clean architecture, solid design patterns, real CI/CD, etc.) are exactly the same things that let them move faster <em>now</em> with AI. Because the structure is already in place and AI just accelerates it from there.</p><p>Boilerplate is no longer a concern, architecture provides a clear structure and repeating patterns to work within. And devs can handle a lot more and ship faster than before without being afraid to break things along the way.</p><p>Meanwhile, the teams that skipped all that work are just vibe-coding with their agents, and they&#8217;re crashing and burning, just faster than before.</p><p>I am personally fully all in on AI-augmented development now. Lately I barely leave Claude Code and the terminal as I work on my iOS projects, and those projects keep expanding more and more into full-stack development encompassing the backend as well.</p><p>The biggest takeaway from my recent research into how other teams are doing this isn&#8217;t that you need more agents. It&#8217;s that your AI workflow is only as good as the codebase and context you give it.</p><p>With all that, there are still a couple of bottlenecks I keep hitting myself working with AI, such as cognitive overhead of context-switching across multiple agents working on different things in parallel, and the code review process being difficult given the amount and speed of code AI is now generating.</p><p>If you&#8217;ve cracked either of those, please let me know.</p><p>Let&#8217;s get into it.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Mobile at Scale</strong></h2><p>Two pieces this issue on the kind of testing infrastructure that pays off and compounds over time as your codebase grows.</p><h3><strong><a href="https://engineering.gusto.com/building-resilient-mobile-apps-a-layered-testing-strategy-for-long-term-stability-d035c78bad31">Building Resilient Mobile Apps: A Layered Testing Strategy for Long-Term Stability</a></strong></h3><p>Gusto has a piece here where they walk through the test layers they have for their iOS app, with actual Swift code samples and all (protocol-based mocking for ViewModels, swift-snapshot-testing for visual regressions, URLSession stubbing for deterministic XCUITests, parallel execution on Bitrise, etc.).</p><p>What got me to actually click through and read it was the before/after numbers they shared. They brought down their test suite from 75 minutes to 39 minutes, and ended up with zero late-discovered release regressions after that.</p><p>I have difficulty with iOS UI tests myself, hands down. So I&#8217;m planning to sit down with this one and read it in depth and dissect.</p><h3><strong><a href="https://bitrise.io/blog/post/cross-workflow-integration-testing-on-ios-a-recipe-for-macos-docker-pipelines">Cross-workflow integration testing on iOS: a recipe for macOS + Docker pipelines</a></strong></h3><p>This one is technically a vendor marketing piece from Bitrise, but it&#8217;s still worth reading because there are a couple of interesting concepts in here that apply regardless of where your CI actually runs.</p><p>The first one is integration testing your mobile app against a real running backend, hosted in a parallel Linux workflow next to your macOS test workflow (on Bitrise or whatever CI of your choice), instead of just mocking the server in your tests.</p><p>The second one is the monorepo setup they mention, where you&#8217;d have backend, frontend, and mobile code all in one repo so that changes can be atomic across the stack.</p><p>I&#8217;ve been toying with both of those concepts at work and they&#8217;re a lot more interesting than they sound on the surface. So probably a separate article from me coming on this in the future.</p><div><hr></div><h2><strong>AI-augmented development</strong></h2><p>This is The Take&#8217;s thesis in practice, with five pieces from people who are actually putting in the work to make AI accelerate their iOS development.</p><h3><strong><a href="https://blog.jacobstechtavern.com/p/ios-at-an-ai-unicorn">My agentic engineering workflow at an AI unicorn &#129412;</a> ($ paywalled)</strong></h3><p>This is probably the most complete public account of agentic iOS engineering that I&#8217;ve come across so far.</p><p>The author Jacob Bartlett is a senior iOS engineer at Granola (a London startup that just hit unicorn status). He&#8217;s been averaging around 8 PRs a day in his role with roughly 99% of the code being AI-written at this point.</p><p>The article walks through how he gets to that point in his day-to-day. He talks about stacked diffs in Graphite that make AI code review workable, a 1-writer-plus-2-readers multi-agent pattern he uses, the verification chains he sets up that let Codex run overnight on flaky Bitrise CI loops, and detailed timelines for every step of his workflow.</p><p>He also goes into what he&#8217;s calling &#8220;knowledge debt contagion.&#8221; Which is the framing every team going all-in on AI needs right now, and that a lot of people don&#8217;t have a language for yet.</p><p>Both of the bottlenecks I called out in The Take get partially addressed in his article as well, so I&#8217;m planning to deep dive into this one and dissect it in detail myself.</p><h3><strong><a href="https://www.donnywals.com/how-i-use-flowdeck-to-let-my-ai-agent-build-and-run-my-apps/">How I use FlowDeck to let my AI agent build and run my apps</a></strong></h3><p>FlowDeck is a CLI that wraps Xcode&#8217;s build and run pipeline in a way that&#8217;s friendly to AI coding agents. Cleaner output, less noise for the agent to chew through (compared to raw xcodebuild output anyway). In this piece Donny Wals walks through his workflow letting his agent build, install, and run his apps end-to-end.</p><p>I&#8217;ve been using XcodeBuildMCP for the same kind of reason myself, barely leaving the terminal and Claude Code these days as I work on my iOS projects. But FlowDeck looks like a more complete package than what I&#8217;m currently using, so I want to try it out at some point soon.</p><h3><strong><a href="https://www.avanderlee.com/ai-development/xcode-instruments-time-profiler-improve-performance-with-ai/">Xcode Instruments Time Profiler: Improve performance with AI</a></strong></h3><p>I&#8217;m not going to lie, I&#8217;ve shied away from performance work in my own apps in the past because opening up Instruments and sifting through its data was always annoying and time-consuming for me.</p><p>But in this piece Antoine van der Lee shows how you can hand that whole boring part off to your AI. You feed it the profile data, let it find the hot spots, and propose changes to your code from there.</p><p>The piece also argues that you should expose your app as a CLI so the AI can verify its own output. And while I do see the value in that, I&#8217;d push back a little here. Because if your architecture cleanly separates business logic from UI then you&#8217;d already be able to test things easily, and the CLI part practically comes for free with that kind of setup anyway.</p><p>Good architecture has always made apps easier to test. And turns out it also makes them easier for AI to work with too.</p><h3><strong><a href="https://www.avanderlee.com/ai-development/network-requests-optimization-using-xcodes-simulator-agents/">Network Requests Optimization using Xcode&#8217;s Simulator &amp; Agents</a></strong></h3><p>This piece is the same shape as the previous one, just applied to network performance instead. The idea is to capture runtime networking data from the Simulator, feed it to your agent along with the relevant code, and then ask the agent for optimization suggestions from there.</p><p>There&#8217;s a bit of self-promotion for Antoine&#8217;s own tools in there, but the underlying workflow itself is sound.</p><p>And again, you can see how a codebase with clear separation makes this kind of analysis productive. Because if your networking layer is buried in view controllers, your AI has nowhere to start. Whereas if duplicate calls or redundant fetches are sitting in one place, they&#8217;re easy to spot, either by you or by your agent.</p><h3><strong><a href="https://swift.org/blog/expanding-swift-ide-support/">Expanding Swift&#8217;s IDE Support</a></strong></h3><p>Swift now has first-class language support in Cursor, VSCodium, and other agentic IDEs through the Open VSX Registry. With auto-install for Cursor users right out of the box. And this is fantastic news for those of us who are trying to break out of the shackles of Xcode at some point.</p><p>The caveat though is that you&#8217;re not leaving Xcode entirely just yet, because simulators, Instruments, signing, and the whole build system all still live there. But for everyday editing and code-thinking work, the gap is closing fast.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2><strong>From the Archive</strong></h2><h3><strong><a href="https://newsletter.mobileengineer.io/p/mobile-dev-interview-in-the-age-of">Mobile Dev Interview In The Age of GenAI</a></strong></h3><p>If you&#8217;re prepping for an iOS interview right now or hiring for one yourself, this is the piece I&#8217;d point you to.</p><p>I wrote it back in late 2024 right after Gergely Orosz published his piece on how GenAI was reshaping tech hiring. And most of the points I argued in there have only gotten more true since then.</p><p>Cover letters are mostly noise now in this AI world. Resumes are low signal, so you might as well just use LinkedIn instead. And algorithmic puzzle interviews are finally dying off, which is honestly just good riddance in my books.</p><p>The signal that still survives all of this is take-home assignments combined with a pair programming session on the code the candidate submitted. You can see almost immediately who actually understands what they shipped, regardless of whether they used AI to write it.</p><p>Worth preparing for that loop, on either side of the table.</p><div><hr></div><h2><strong>Architecture &amp; Design Patterns</strong></h2><p>Two deep reads this issue on what&#8217;s actually happening underneath the surface when your code runs.</p><h3><strong><a href="https://blog.jacobstechtavern.com/p/urlsession-to-electrons">URLSession to Electrons: How Networking works on iOS</a> ($ paywalled)</strong></h3><p>This is a good one. Jacob has been on a roll lately with these kinds of in-depth deep-dive articles. In this particular one you&#8217;ll get to learn a lot about how data sent from iOS apps (or really any other app in general for that matter) travels over the internet layers across the network.</p><p>Practical stuff to take away from this article for mobile engineers:</p><p><strong>Batch Your Requests to Save Battery</strong></p><ul><li><p><em>The Why:</em> The article notes that your cellular radio stays in a high-power state for a while after any burst of traffic. Frequent, small &#8220;drips&#8221; of network activity keep the radio awake constantly, which drains the battery.</p></li><li><p><em>The Approach:</em> Batch your network requests. Instead of sending five separate analytical pings or minor data fetches back-to-back, combine them into a single endpoint request or queue them to fire together at strategic intervals.</p></li></ul><p><strong>Ask Your Backend Team About HTTP/3 (QUIC)</strong></p><ul><li><p><em>The Why:</em> The article highlights QUIC as a newer transport protocol built on top of UDP that provides both speed and reliability. HTTP/3 utilizes QUIC to bypass the latency of TCP&#8217;s connection setup and avoid head-of-line blocking.</p></li><li><p><em>The Approach:</em> Start a conversation with your backend/infrastructure team about enabling HTTP/3 on your servers. Once enabled server-side, recent iOS versions often handle the multiplexing improvements automatically, making your app feel significantly faster on flaky networks.</p></li></ul><h3><strong><a href="https://www.avanderlee.com/concurrency/unexpected-task-suspension-points-in-swift-concurrency/">Unexpected Task suspension points in Swift Concurrency</a></strong></h3><p>In this article Antoine flags something that a lot of Swift devs tend to miss. The concurrency runtime can introduce suspension points beyond just your explicit <code>await</code> calls. And actor re-entrancy bugs can sneak into code that looks safe because of that.</p><p>That said though, I largely agree with the counter-point on this one. Because if you have proper actor isolation set up for your background work, then it&#8217;s mostly a non-issue for you anyway. And the same goes for if you happen to be on RxSwift or some other mature concurrency layer in your codebase.</p><div><hr></div><h2><strong>And finally&#8230;</strong></h2><h3><strong><a href="https://codakuma.com/pushy/">A ridiculously-lightweight push notification service</a></strong></h3><p>In this article an indie iOS dev walks through how to send push notifications by talking directly to APNs over HTTP/2. With just a handful of Swift code plus a 200-line Cloudflare Worker for the server-side bit of it.</p><p>No OneSignal, no Firebase Cloud Messaging, and none of the 23K+ lines of SDK code that come along with either of those options.</p><p>This is where I think mobile development is generally heading these days, toward less reliance on bloated third-party SDKs and more end-to-end ownership across the stack. Especially now that AI handles a lot of the parts of &#8220;going full stack&#8221; that used to slow us down for us (things like picking up new backend frameworks, writing all the boilerplate, navigating unfamiliar tooling, etc.).</p><p>The constraints we live with every day as mobile devs (memory, battery, intermittent connectivity, tiny screens, you name it) sharpen us in a way that frontend and backend engineers don&#8217;t always have to deal with. And with AI now removing the friction of crossing stack boundaries, that sharpness carries over surprisingly well.</p><p>I wrote more about this argument in <a href="https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack">a recent piece on why mobile engineers are best positioned to go full-stack in the AI-augmented world</a>. And Pushy here is just one small good example of what I was talking about in there.</p><p>That&#8217;s it for this issue. Thanks for reading.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/the-mobile-report-issue-1-two-kinds?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/the-mobile-report-issue-1-two-kinds?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/the-mobile-report-issue-1-two-kinds?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[On-Demand Mobile Releases: How We Got There and Why You Should Too]]></title><description><![CDATA[How we got to continuous deployment on mobile to ship like a web team &#8212; on demand, to 100% of users. You don&#8217;t really need phased rollout!]]></description><link>https://newsletter.mobileengineer.io/p/on-demand-mobile-releases-how-we</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/on-demand-mobile-releases-how-we</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Wed, 06 May 2026 11:02:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every few weeks, one of our iOS engineers lost a day to a checklist.</p><p>Not a metaphorical checklist. A real one &#8212; a Confluence doc with 25 steps, every box ticked manually, every tool opened in sequence. GitHub to cut the branch. Tuist to bump the version. Jira to create the release, find every ticket in &#8220;ready to deploy&#8221; state, tag each one with the release version. App Store Connect to create the new version, write release notes in every supported language, configure the rollout settings, select the build, submit. Slack to ping testers, post in the deploy channel. Then wait for regression sign-off, then back to App Store Connect to push to production. Then merge the release branch, create the sync PR back to develop, update the Confluence doc title from &#8220;Deploying&#8221; to &#8220;Released.&#8221;</p><p>Best case: 1&#8211;2 hours if everything went smoothly and you had no interruptions. Average: 3 hours. Bad day: 5 hours.</p><p>And we rotated. Nobody owned releases permanently &#8212; that would be too cruel. So every couple of weeks, a different engineer drew the short straw.</p><p>This is not a unique problem. Most mobile teams I&#8217;ve talked to have some version of it. You&#8217;re context-switching between half a dozen tools, the App Store submission process has steps that can&#8217;t be skipped, and over time the checklist grows because the app grows and so does the complexity &#8212; there&#8217;s just more to do.</p><p>Engineer time is expensive, we need to save it. This was pure overhead &#8212; not a feature, not a bug fix, not a code review. Just ceremony.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What we set out to do</h2><p>We didn&#8217;t set out to reinvent how we deploy mobile apps. The goal was modest: automate the checklist. Get as close to one-click as possible. Get this off our engineers&#8217; plates.</p><p>We adopted <a href="https://www.runway.team/">Runway</a>, a mobile release management platform. It replaces a lot! Runway cuts the release branch, bumps the version and raises the PR, calculates the diff between this release and the last one, auto-tags every Jira ticket with the release version, creates the Jira release page, generates the build, and handles App Store and Play Store submission. The steps that used to require a human navigating six tools in sequence now happen automatically.</p><p>The release engineer&#8217;s job went from running a 25-step checklist to: kick off the release, verify your tickets work, approve the build, submit. Runway handles the rest.</p><p>That alone was worth it.</p><div><hr></div><h5>Sponsored</h5><h4><a href="https://jobs.ashbyhq.com/triumph-arcade/a96e8422-6b1a-4bd9-b5ea-8073e5c26880?utm_source=mobile_engineer">Join One of the Top Consumer App Teams in San Francisco</a></h4><p>Triumph builds two #1 App Store apps: Triumph Arcade, a casual skill gaming platform, and Rips, the best way to discover and buy trading cards. Hundreds of thousands of MAUs, a tiny mobile team of three, and some of the best engineers you'll ever work with. Salary: $200-400k</p><p><a href="https://jobs.ashbyhq.com/triumph-arcade/a96e8422-6b1a-4bd9-b5ea-8073e5c26880?utm_source=mobile_engineer">https://jobs.ashbyhq.com/triumph-arcade/a96e8422-6b1a-4bd9-b5ea-8073e5c26880?utm_source=mobile_engineer</a></p><div><hr></div><h2>The question we didn&#8217;t expect to ask</h2><p>Once releasing was cheap, we started asking questions we hadn&#8217;t asked before.</p><p>The obvious one: why are we still releasing every two weeks?</p><p>If a release takes a few clicks instead of half a day, there&#8217;s no reason to batch changes. You can ship as things are ready. So we did. We moved to on-demand releases &#8212; ship when there&#8217;s something worth shipping, not on a calendar. Effectively, we got as close to continuous deployment as possible on mobile. Just like a good web or backend team would.</p><p>The less obvious question came next: why are we doing 7-day phased rollouts?</p><p>Phased rollout is standard practice on iOS &#8212; 7 days is the default. You ship to 1% of users, then slowly ramp up over the week, watching your crash rate and reviews as you go. The idea is that if something goes wrong, you pause the rollout before it reaches everyone. You fix it, submit a new version, and start over &#8212; back to day one, back to 1%. Then the new version rolls out over another 7 days.</p><p>We had been following this without question. It&#8217;s a dogma of the iOS community &#8212; a sacred cow. Everyone is scared of shipping a bug. OMG what if it crashes for someone! So you roll out slowly, keep your finger on the pause button, and feel safe.</p><p>But then we actually looked at our numbers, and they didn&#8217;t support the fear.</p><h2>The phased rollout probably isn&#8217;t helping you as much as you think it does</h2><p>Phased rollout protects you from client-side bugs &#8212; code you shipped that breaks something for users. For it to be worth the delay, two things need to be true:</p><p>1. You ship crappy code regularly &#8212; crashes, broken flows, bugs that make it through to production. If your defect rate is low, there&#8217;s nothing much to catch.</p><p>2. You have the monitoring tools to get an early warning signal in that first 1%, and the discipline to actually watch them and act. Without that, the rollout is just delay with no feedback loop.</p><p>For us, neither was really true.</p><p>Our crash rate from client-side changes is low &#8212; super low, something like 3.6x less than our web team&#8217;s. We&#8217;re a careful team, and our architecture &#8212; we use <a href="https://github.com/uber/RIBs-iOS">RIBs</a> &#8212; gives us a structure that&#8217;s easy to reason about, easy to code review, and hard to break in ways that don&#8217;t surface during development. The QA gate before every release means engineers verify their own tickets before anything ships.</p><blockquote><p><em>If RIBs is new to you: I&#8217;ve written about <a href="https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part">our full architecture at UpKeep</a> and I&#8217;m also <a href="https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer">the primary iOS maintainer of the repo</a>.</em></p></blockquote><p>What actually goes wrong for our users mostly comes from the backend &#8212; API contract changes, service issues, things the mobile app has no control over and that a phased rollout does nothing to address. If the backend breaks something, it breaks it for 1% of users and 100% of users equally.</p><p>On top of that, phased rollout creates a queuing problem. Start a 7-day rollout on Monday. By Wednesday, there&#8217;s a fix ready to ship. Now you either wait out the rollout &#8212; so your fix doesn&#8217;t reach users for days &#8212; or you submit a new release and restart the clock from 1% again. Neither option is good.</p><p>So we stopped. We switched to releasing directly to 100% of users.</p><p>The only thing we can&#8217;t control is Apple&#8217;s review process, which normally takes less than a day. After that, users get the update immediately.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What actually makes this safe</h2><p>This isn&#8217;t reckless. You have to have your shit together to deploy continuously.</p><p>For us, that comes from three things.</p><p><strong>Architecture.</strong> RIBs gives us a structure that&#8217;s easy to develop in and easy to code review. Features are isolated. Changes are contained. The codebase is large, but it&#8217;s navigable. This keeps our client-side defect rate low. </p><blockquote><p>If you want to understand why architecture is important, <a href="https://newsletter.mobileengineer.io/p/clean-architecture-a-new-frontier">I&#8217;ve written about clean architecture and why it matters</a></p></blockquote><p><strong>Feature flags.</strong> Anything risky goes behind a flag. You can merge and deploy the code before the feature is visible to users. If something goes wrong after you flip the flag, you turn it off &#8212; no new release needed.</p><p><strong>Ownership.</strong> Engineers test their own tickets before every release. Regression isn&#8217;t a separate team&#8217;s job &#8212; it&#8217;s yours. You know what you built. You verify it works.</p><blockquote><p>This is part of a broader shift I&#8217;ve been thinking about &#8212; <a href="https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack">mobile engineers owning far more of the stack than they used to</a>.</p></blockquote><p>These three together mean that by the time a release ships, we&#8217;re genuinely confident in it. Not because we&#8217;re lucky, but because we built a process that we can trust.</p><h2>What about when you do ship a bug?</h2><p>You will eventually. Everyone does.</p><p>With on-demand deployment, a hotfix is just the next release. Same pipeline, same process. Fix it, merge it, kick off a release, ship it. Users get the fix quickly &#8212; often faster than they would have under a phased rollout cycle where you&#8217;d have to wait for the current rollout to complete before starting another.</p><p>No special hotfix process. No war room. Just another release.</p><h2>This isn&#8217;t for everyone</h2><p>Large teams need different tools. At companies like Uber, there&#8217;s a weekly release train. Your code either makes it through the CI queue before the cut or waits for next week. That structure exists because there are many engineers, many features, and the coordination overhead alone is significant. A release train makes that manageable.</p><p>If you&#8217;re at a company with dozens of engineers all committing to the same mobile app, a release train is reasonable. But most mobile teams aren&#8217;t that. Most mobile teams are small, working on a focused product, and operating with far more ceremony than needed.</p><p>If you ship clean code and your defect rate is low, you probably don&#8217;t need bi-weekly releases. You don&#8217;t need 7-day rollouts. You&#8217;re just slowing yourself down for no good reason &#8212; or out of fear.</p><h2>Where to start</h2><p>The tools are out there. Runway is what we use and it works well for us. You could also wire something similar yourself with GitHub Actions and the App Store Connect API &#8212; the automation isn&#8217;t magic, it&#8217;s scripting the steps you currently do by hand.</p><p>The harder part is the philosophy. Phased rollout feels safe. Frequent releases feel risky. That intuition is backwards if your team has good architecture and a low defect rate.</p><p>Start with the checklist. Automate the parts that don&#8217;t need a human. Once releasing is cheap, the rest of the questions answer themselves.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/on-demand-mobile-releases-how-we?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/on-demand-mobile-releases-how-we?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/on-demand-mobile-releases-how-we?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[I’m Back. Here’s What I’ve Been Up To — And What’s Coming]]></title><description><![CDATA[So many things have happened lately! AI Stuff! Continues Deployment! Working Fullstack! RIBs! And a poll! :)]]></description><link>https://newsletter.mobileengineer.io/p/im-back-heres-what-ive-been-up-to</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/im-back-heres-what-ive-been-up-to</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Wed, 22 Apr 2026 15:24:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It&#8217;s been a couple of months since my last article.</p><p>I was on the tail end of my paternity leave. I&#8217;m lucky enough to work for a company that gives generous time to bond with your baby. It&#8217;s been great, but also kind of exhausting. I need sleep, lol.</p><p>Now that I&#8217;m back, here&#8217;s what&#8217;s been happening with me &#8212; and the world. If you haven&#8217;t noticed, there&#8217;s been a bit of an AI wave in tech lately. You may have heard of it.</p><div><hr></div><h2>The book</h2><p>My last posts were about the revised async/await chapter for <em>The iOS Interview Guide</em> 2nd edition: </p><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:187982681,&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-async-chapter-revision&quot;,&quot;publication_id&quot;:1604673,&quot;publication_name&quot;:&quot;The Mobile Engineer&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!TDVz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;title&quot;:&quot;[Book Update] Async Chapter Revision Based On Feedback + Chapter Conclusion&quot;,&quot;truncated_body_text&quot;:&quot;Thank you everyone who responded to my call for feedback on the async chapter of The iOS Interview Guide 2nd edition!&quot;,&quot;date&quot;:&quot;2026-02-14T20:54:53.608Z&quot;,&quot;like_count&quot;:4,&quot;comment_count&quot;:0,&quot;bylines&quot;:[{&quot;id&quot;:27258834,&quot;name&quot;:&quot;Alex Bush&quot;,&quot;handle&quot;:&quot;alexbush&quot;,&quot;previous_name&quot;:null,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/566faa25-1bc3-4c38-bf51-f3299d4c1c67_512x512.jpeg&quot;,&quot;bio&quot;:&quot;Author of The iOS Interview Guide book and iOS System Design course. Co-host of Inside iOS Dev podcast. Previously at Uber, Wayfair.&quot;,&quot;profile_set_up_at&quot;:&quot;2022-05-31T17:09:34.274Z&quot;,&quot;reader_installed_at&quot;:&quot;2022-05-31T17:09:13.084Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:1576260,&quot;user_id&quot;:27258834,&quot;publication_id&quot;:1604673,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:true,&quot;publication&quot;:{&quot;id&quot;:1604673,&quot;name&quot;:&quot;The Mobile Engineer&quot;,&quot;subdomain&quot;:&quot;mobileengineer&quot;,&quot;custom_domain&quot;:&quot;newsletter.mobileengineer.io&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Mobile engineering from the inside. Real world software development best practices, techniques, architecture, software development lifecycle, process, and more. Useful for mobile engineers junior to senior, tech leads, engineering managers. &quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;author_id&quot;:27258834,&quot;primary_user_id&quot;:27258834,&quot;theme_var_background_pop&quot;:&quot;#67BDFC&quot;,&quot;created_at&quot;:&quot;2023-04-21T03:59:15.436Z&quot;,&quot;email_from_name&quot;:&quot;The Mobile Engineer&quot;,&quot;copyright&quot;:&quot;Alex Bush&quot;,&quot;founding_plan_name&quot;:&quot;Founding Member&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;enabled&quot;,&quot;language&quot;:null,&quot;explicit&quot;:false,&quot;homepage_type&quot;:&quot;newspaper&quot;,&quot;is_personal_mode&quot;:false,&quot;logo_url_wide&quot;:null}}],&quot;twitter_screen_name&quot;:&quot;alex_v_bush&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null,&quot;status&quot;:{&quot;bestsellerTier&quot;:null,&quot;subscriberTier&quot;:null,&quot;leaderboard&quot;:null,&quot;vip&quot;:false,&quot;badge&quot;:null,&quot;paidPublicationIds&quot;:[],&quot;subscriber&quot;:null}}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:false,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;,&quot;source&quot;:null}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://newsletter.mobileengineer.io/p/book-update-async-chapter-revision?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!TDVz!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png"><span class="embedded-post-publication-name">The Mobile Engineer</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">[Book Update] Async Chapter Revision Based On Feedback + Chapter Conclusion</div></div><div class="embedded-post-body">Thank you everyone who responded to my call for feedback on the async chapter of The iOS Interview Guide 2nd edition&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">3 months ago &#183; 4 likes &#183; Alex Bush</div></a></div><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:187041280,&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-async-chapter-is-done&quot;,&quot;publication_id&quot;:1604673,&quot;publication_name&quot;:&quot;The Mobile Engineer&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!TDVz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;title&quot;:&quot;[Book Update] Async Chapter is Done! Looking for Feedback.&quot;,&quot;truncated_body_text&quot;:&quot;I&#8217;m finally done with the Async chapter of The iOS Interview Guide! It took a while, been busy with work, took a detour, but now I&#8217;m back to working on the second edition reinvigorated.&quot;,&quot;date&quot;:&quot;2026-02-06T01:09:30.214Z&quot;,&quot;like_count&quot;:4,&quot;comment_count&quot;:12,&quot;bylines&quot;:[{&quot;id&quot;:27258834,&quot;name&quot;:&quot;Alex Bush&quot;,&quot;handle&quot;:&quot;alexbush&quot;,&quot;previous_name&quot;:null,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/566faa25-1bc3-4c38-bf51-f3299d4c1c67_512x512.jpeg&quot;,&quot;bio&quot;:&quot;Author of The iOS Interview Guide book and iOS System Design course. Co-host of Inside iOS Dev podcast. Previously at Uber, Wayfair.&quot;,&quot;profile_set_up_at&quot;:&quot;2022-05-31T17:09:34.274Z&quot;,&quot;reader_installed_at&quot;:&quot;2022-05-31T17:09:13.084Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:1576260,&quot;user_id&quot;:27258834,&quot;publication_id&quot;:1604673,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:true,&quot;publication&quot;:{&quot;id&quot;:1604673,&quot;name&quot;:&quot;The Mobile Engineer&quot;,&quot;subdomain&quot;:&quot;mobileengineer&quot;,&quot;custom_domain&quot;:&quot;newsletter.mobileengineer.io&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Mobile engineering from the inside. Real world software development best practices, techniques, architecture, software development lifecycle, process, and more. Useful for mobile engineers junior to senior, tech leads, engineering managers. &quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;author_id&quot;:27258834,&quot;primary_user_id&quot;:27258834,&quot;theme_var_background_pop&quot;:&quot;#67BDFC&quot;,&quot;created_at&quot;:&quot;2023-04-21T03:59:15.436Z&quot;,&quot;email_from_name&quot;:&quot;The Mobile Engineer&quot;,&quot;copyright&quot;:&quot;Alex Bush&quot;,&quot;founding_plan_name&quot;:&quot;Founding Member&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;enabled&quot;,&quot;language&quot;:null,&quot;explicit&quot;:false,&quot;homepage_type&quot;:&quot;newspaper&quot;,&quot;is_personal_mode&quot;:false,&quot;logo_url_wide&quot;:null}}],&quot;twitter_screen_name&quot;:&quot;alex_v_bush&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null,&quot;status&quot;:{&quot;bestsellerTier&quot;:null,&quot;subscriberTier&quot;:null,&quot;leaderboard&quot;:null,&quot;vip&quot;:false,&quot;badge&quot;:null,&quot;paidPublicationIds&quot;:[],&quot;subscriber&quot;:null}}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:false,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;,&quot;source&quot;:null}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://newsletter.mobileengineer.io/p/book-update-async-chapter-is-done?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!TDVz!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png"><span class="embedded-post-publication-name">The Mobile Engineer</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">[Book Update] Async Chapter is Done! Looking for Feedback.</div></div><div class="embedded-post-body">I&#8217;m finally done with the Async chapter of The iOS Interview Guide! It took a while, been busy with work, took a detour, but now I&#8217;m back to working on the second edition reinvigorated&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">4 months ago &#183; 4 likes &#183; 12 comments &#183; Alex Bush</div></a></div><p>I incorporated a round of reader feedback and added four new sections covering legacy API bridging, actor reentrancy, debounced search, and a multi-paradigm network request comparison. Thank you to everyone who submitted feedback.</p><p>During the break I also made solid progress on the <em>UIKit chapter</em> &#8212; it&#8217;s essentially done, I just need to polish it up and post a feedback call the same way I did for the async chapter. That&#8217;s next on my list.</p><p>The book is still very much in progress. There&#8217;s a lot left to write, but I&#8217;m moving forward.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>AI experiments on the side</h2><p>Turns out paternity leave is great for late-night rabbit holes when the baby finally sleeps.</p><p>I spent a lot of that time playing with AI-augmented coding. Vibe coding, web development experiments, building out personal projects. The models have gotten dramatically better. The agentic stuff especially &#8212; it caught up fast. I have some stuff to share &#8212; thoughts, opinions, and current best practices. Will be posting about it, stay tuned.</p><p>I also went down a rabbit hole trying to run everything locally. I got OpenClaw running in a Docker container on an external drive &#8212; so it wouldn&#8217;t wreck my main machine &#8212; and connected it to locally running models via Ollama. It didn&#8217;t go well. Turns out an M1 MacBook Pro with 64GB of memory is not enough to do anything meaningful with local models. The models are just too big. Oh well. I did learn a lot about Docker, so there&#8217;s that.</p><div><hr></div><h2>What I came back to at work</h2><p>This is where it gets interesting, and where I have a lot more to say in a dedicated article.</p><p>Short version: we went all in on AI-first development &#8212; not just engineering, but the whole organization. Every team got subscriptions to things like Claude and Cursor with very generous token budgets. I came back and dove right into this &#8220;token-galore&#8221; as I call it, and I really don&#8217;t want to go back to how I worked before.</p><p>My scope also changed. I&#8217;m now doing product work alongside engineering &#8212; customer research, data analysis, synthesizing feedback from users. Stuff I always wanted to do but never had the bandwidth for. AI makes it feasible. The scope of what a software engineer can actually build, own, and influence has gotten so much wider now than ever before. I already shared some of my thoughts on mobile devs being fullstack.</p><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:183396782,&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack&quot;,&quot;publication_id&quot;:1604673,&quot;publication_name&quot;:&quot;The Mobile Engineer&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!TDVz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;title&quot;:&quot;Mobile Engineers - You&#8217;re All Full Stack Now&quot;,&quot;truncated_body_text&quot;:&quot;Back in the day I started my dev career by making MVP apps for entrepreneurs and startups in Silicon Valley. They were mobile apps, Android and iOS. I started with Android but as I would build the apps for my clients they would keep asking to make the same thing on iOS. So, I picked up a few books on iOS dev and built them the iOS apps they wanted.&quot;,&quot;date&quot;:&quot;2026-01-06T12:03:38.190Z&quot;,&quot;like_count&quot;:16,&quot;comment_count&quot;:3,&quot;bylines&quot;:[{&quot;id&quot;:27258834,&quot;name&quot;:&quot;Alex Bush&quot;,&quot;handle&quot;:&quot;alexbush&quot;,&quot;previous_name&quot;:null,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/566faa25-1bc3-4c38-bf51-f3299d4c1c67_512x512.jpeg&quot;,&quot;bio&quot;:&quot;Author of The iOS Interview Guide book and iOS System Design course. Co-host of Inside iOS Dev podcast. Previously at Uber, Wayfair.&quot;,&quot;profile_set_up_at&quot;:&quot;2022-05-31T17:09:34.274Z&quot;,&quot;reader_installed_at&quot;:&quot;2022-05-31T17:09:13.084Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:1576260,&quot;user_id&quot;:27258834,&quot;publication_id&quot;:1604673,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:true,&quot;publication&quot;:{&quot;id&quot;:1604673,&quot;name&quot;:&quot;The Mobile Engineer&quot;,&quot;subdomain&quot;:&quot;mobileengineer&quot;,&quot;custom_domain&quot;:&quot;newsletter.mobileengineer.io&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Mobile engineering from the inside. Real world software development best practices, techniques, architecture, software development lifecycle, process, and more. Useful for mobile engineers junior to senior, tech leads, engineering managers. &quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;author_id&quot;:27258834,&quot;primary_user_id&quot;:27258834,&quot;theme_var_background_pop&quot;:&quot;#67BDFC&quot;,&quot;created_at&quot;:&quot;2023-04-21T03:59:15.436Z&quot;,&quot;email_from_name&quot;:&quot;The Mobile Engineer&quot;,&quot;copyright&quot;:&quot;Alex Bush&quot;,&quot;founding_plan_name&quot;:&quot;Founding Member&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;enabled&quot;,&quot;language&quot;:null,&quot;explicit&quot;:false,&quot;homepage_type&quot;:&quot;newspaper&quot;,&quot;is_personal_mode&quot;:false,&quot;logo_url_wide&quot;:null}}],&quot;twitter_screen_name&quot;:&quot;alex_v_bush&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null,&quot;status&quot;:{&quot;bestsellerTier&quot;:null,&quot;subscriberTier&quot;:null,&quot;leaderboard&quot;:null,&quot;vip&quot;:false,&quot;badge&quot;:null,&quot;paidPublicationIds&quot;:[],&quot;subscriber&quot;:null}}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:true,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;,&quot;source&quot;:null}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!TDVz!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" loading="lazy"><span class="embedded-post-publication-name">The Mobile Engineer</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">Mobile Engineers - You&#8217;re All Full Stack Now</div></div><div class="embedded-post-body">Back in the day I started my dev career by making MVP apps for entrepreneurs and startups in Silicon Valley. They were mobile apps, Android and iOS. I started with Android but as I would build the apps for my clients they would keep asking to make the same thing on iOS. So, I picked up a few books on iOS dev and built them the iOS apps they wanted&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">5 months ago &#183; 16 likes &#183; 3 comments &#183; Alex Bush</div></a></div><p>This is similar. I want to have a proper write-up on this.</p><div><hr></div><h2>What&#8217;s coming</h2><p>I have a backlog. Here&#8217;s what I want to get to &#8212; tell me what you want to read first.</p><p><strong>KMP deep dive, part 2 &#8212; and then part 3 about RIBs</strong></p><p>I already published an <a href="https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part">intro to KMP and how we use it</a> to share business logic across iOS and Android. I want to go deeper &#8212; a part 2 on the implementation details of the KMP side, and then a part 3 on how the iOS architecture fits together with RIBs. The full picture, end to end.</p><p><strong>Mobile Continues Deployment on Demand</strong></p><p>Mobile teams have always been stuck behind App Store review cycles and phased rollouts. We decided to close that gap with web deployment as much as possible. Automated everything, adopted <a href="https://www.runway.team/">Runway</a>, and now ship to 100% of users with basically a click. We&#8217;ve been working this way for several quarters new and it surprisignly works! I want to write it up in detail.</p><p><strong>Mobile engineers owning the full stack</strong></p><p><a href="https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack">I&#8217;ve argued before</a> that mobile engineers make great full-stack engineers. Now we&#8217;re living it. Our team owns everything end-to-end &#8212; from the app screen and mobile business logic to the API contract and the backend implementation with the database queries, the whole thing. AI makes this actually feasible. I want to expand on this with real examples.</p><p><strong>RIBs + Swift concurrency</strong></p><p>As a maintainer of <a href="https://github.com/uber/RIBs-iOS">Uber&#8217;s iOS RIBs repo</a>, I&#8217;ve been slowly modernizing it for Swift concurrency. <a href="https://github.com/uber/RIBs-iOS/pull/49">I have a working approach &#8212; main actor isolation</a>, full backwards compatibility, and a clear path for Swift 6 &#8212; and I want to document it properly for anyone running RIBs in production.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What do you want to read first?</h2><p>Reply to this email, leave a comment, or vote in the poll below. I read everything.</p><p>And if none of this sounds interesting to you &#8212; tell me that too. I can take it :)</p><p>Happy coding!</p><div class="poll-embed" data-attrs="{&quot;id&quot;:500102}" data-component-name="PollToDOM"></div><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/im-back-heres-what-ive-been-up-to?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/im-back-heres-what-ive-been-up-to?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/im-back-heres-what-ive-been-up-to?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><p></p>]]></content:encoded></item><item><title><![CDATA[[Book Update] Async Chapter Revision Based On Feedback + Chapter Conclusion]]></title><description><![CDATA[Thank you everyone for your feedback on the async chapter! It helped me a lot to polish it! Also, wanted to share the conclusion of the chapter here on substack.]]></description><link>https://newsletter.mobileengineer.io/p/book-update-async-chapter-revision</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/book-update-async-chapter-revision</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Sat, 14 Feb 2026 20:54:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Thank you everyone who responded to <a href="https://newsletter.mobileengineer.io/p/book-update-async-chapter-is-done">my call for feedback</a> on the async chapter of <a href="https://iosinterviewguide.com/">The iOS Interview Guide</a> 2nd edition!</p><p>I&#8217;ve added several major sections based on your input:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p><strong>Continuations for bridging legacy APIs</strong> - How to wrap old completion handler code into async/await using <code>withCheckedContinuation</code>. This was a big omission since basically every real codebase needs to bridge between old and new async patterns.</p></li><li><p><strong>Actor reentrancy solutions</strong> - Added the pattern for fixing reentrancy bugs. Shows how concurrent callers can await the same task instead of creating race conditions.</p></li><li><p><strong>Debounced search with Task cancellation</strong> - Practical example of search bar implementation showing how to store task handles, cancel them on new input, and use <code>Task.sleep</code> for debouncing. Includes both async/await and RxSwift versions since that&#8217;s what you&#8217;ll actually encounter in production code.</p></li><li><p><strong>Network request comparison across all paradigms</strong> - This was the biggest addition. Same user profile fetch implemented four ways: completion handlers, async/await, Combine, and RxSwift. All using a unified APIClient with proper Swift 6 concurrency compliance. Interviewers love asking this type of question because it shows you understand the trade-offs between approaches, not just the latest Apple marketing.</p></li></ul><div><hr></div><p><strong>And here&#8217;s how the chapter concludes</strong> - my take on async work in iOS:</p><p>Async work on iOS has evolved from manual threads to GCD to reactive frameworks to Swift Concurrency. Each approach solved real problems for its time.</p><p><strong>Here&#8217;s what the industry expects:</strong> Swift Concurrency is the default for new code. If you&#8217;re interviewing or joining teams building new features, expect async/await, actors, and structured concurrency everywhere. You need to know it cold. That said, production codebases have years of existing code - you&#8217;ll maintain GCD dispatches, RxSwift chains, Combine publishers. Real work means bridging all these paradigms.</p><p><strong>Here&#8217;s my actual opinion though:</strong> Rx (whether RxSwift, RxJS, or RxJava) is a superior model for asynchronous code. It handles simple cases just as well as async/await, but gives you real power for complex reactive flows. More importantly, learning Rx once translates across every platform and language - your RxSwift knowledge works in RxJS, RxJava, RxKotlin. That portability matters.</p><p>Apple&#8217;s attempt to eliminate concurrency bugs through Swift Concurrency sounds great on paper, but the infuriatingly strict compiler constraints often do more harm than good. That&#8217;s not elegant - that&#8217;s fighting the compiler to do reasonable things.</p><p>If you have <strong>solid architecture</strong>, <em><strong>most concurrency problems disappear</strong></em>. When your application state, domain models, and UI layer are clearly separated, when you avoid global state and singletons, race conditions become rare. The bugs Swift Concurrency tries to prevent at compile time just don&#8217;t happen in well-architected apps.</p><p>So my message to mobile engineers: don&#8217;t obsess over using the most recent stuff just because Apple said so. <strong>Focus on getting your architecture right.</strong> Avoid singletons and global state. Once you have clean separation of concerns, the async code becomes easy regardless of which tool you pick.</p><p>Know Swift Concurrency because you&#8217;ll be asked about it in interviews. Understand GCD and reactive patterns for maintenance work. But don&#8217;t treat any of these as religion - they&#8217;re tools. Pick what fits your problem and your architecture.</p><p>The comparison patterns in the chapter - especially the network request implementations across all four paradigms - show how the same problem gets solved differently. Use them as templates when evaluating which approach fits your specific situation.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[[Book Update] Async Chapter is Done! Looking for Feedback.]]></title><description><![CDATA[The Async Chapter is one of the new additions to the 2nd edition of The iOS Interview Guide. It covers all things async: GCD, Swift Concurrency, RxSwift, Combine, Threads, etc.]]></description><link>https://newsletter.mobileengineer.io/p/book-update-async-chapter-is-done</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/book-update-async-chapter-is-done</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Fri, 06 Feb 2026 01:09:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m finally done with the Async chapter of <a href="https://iosinterviewguide.com/">The iOS Interview Guide</a>! It took a while, been busy with work, took a detour, but now I&#8217;m back to working on the second edition reinvigorated. </p><p>This chapter is the new addition to the book, not a refactoring of an existing chapter. That&#8217;s also partially why it took me a while to finish it. It covers all things async: Threads, Swift Concurrency, MainActor, Actors, AsyncStream, GCD, NSOperation, RxSwift, Combine, etc. </p><p>The goal is to give the reader a broad overview of options to execute asynchronous work on iOS so that they know what to pick best for the job and problem at hand.</p><p>Any type of feedback is welcome, from tiny typos to critique of the whole chapter structure.</p><p>Let me know if you want to get the draft by emailing me, commenting, or DMing on Substack.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mobile Engineers - You’re All Full Stack Now]]></title><description><![CDATA[Specialization used to be the thing&#8212;mobile, frontend, backend, etc. Now with AI we all can (and should) work on a higher full-stack level, which I believe mobile devs are best positioned for.]]></description><link>https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Tue, 06 Jan 2026 12:03:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Back in the day I started my dev career by making MVP apps for entrepreneurs and startups in Silicon Valley. They were mobile apps, Android and iOS. I started with Android but as I would build the apps for my clients they would keep asking to make the same thing on iOS. So, I picked up a few books on iOS dev and built them the iOS apps they wanted.</p><p>I hit a wall pretty quickly. The apps I built could only do so much without a backend. My clients kept asking for more features that required a backend for my mobile apps, so I picked up the books again, this time on Ruby on Rails, and started to build backends and APIs for my mobile apps. On occasion I&#8217;d even have to build a dashboard or two in javascript (and dart) or a landing page for those projects so I&#8217;d even dabble in frontend a little bit (just enough to know I hate it).</p><p>Eventually, I specialized in iOS because building all of those things and being the jack of all trades spread me too thin and burned me out. I liked iOS development the most. This, in a nutshell, is the story of how I became an iOS specialist. But it is also a story of how I became a versatile generalist who doesn&#8217;t care what platform or language to work with (unless it&#8217;s javascript, ick) and builds things full-stack or with a full-stack mindset.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Years went by and two things that I observed struck me:</p><p>First, it seems like almost no one in the mobile community builds full-stack and/or touches or even understands backend development. Second, as far as I can tell, the mobile devs, on average, are better devs because they pay more attention to details and grow up within a constrained environment.</p><p>Let me expand on both of these points.</p><h2>Mobile Devs Are Rarely Fullstack</h2><p>It is common in the JS/TS world to be fullstack. You build the backend, you build the frontend for it. Often, if it&#8217;s JS/TS, even the same language is used in both. It is pretty normal and sometimes expected for devs to build entire features top to bottom maybe with an exception of design.</p><p>Now, you could argue that it is a good thing or bad thing whichever way and you probably would be right. Devs not specializing and having enough knowledge about each side is a bad thing. But at the same time, the same devs holistically solving a problem for the user and not making disjointed implementations by pointing fingers to the other side is a good thing as well.</p><p>But somehow mobile devs have avoided this over the years. They just sit in their own silo and make not just the mobile side without the backend, but get this, ONLY one platform at a time!? &#128561;</p><p>It&#8217;s probably, mostly or partially, due to the fact that the language, like in JS/TS case, is not shared between mobile and backend. But that said, I&#8217;ve seen plenty of Rails devs who are also perfectly comfortable making JS frontend stuff all in one go. I suspect that the difference is that mobile development has enough complexity for one engineer to dive deeply for their entire career. But, nowadays, the same thing can be said about both frontend and backend development I suppose.</p><p>And I think the lack of good and &#8220;fun&#8221; backend frameworks like Ruby on Rails written in Objective-C/Swift or Java/Kotlin is not helping either. Although, these days Kotlin,Ktor and Spring Boot are helping in that department.</p><h2>Mobile Devs Are Best Suited To Be Fullstack</h2><p>I would argue that mobile devs are the best suited to be fullstack engineers. Consistently I find that mobile devs have a higher attention to detail, polish, architecture, and design patterns than their frontend and backend counterparts. In general, they care more and want to do more.</p><p>I suspect that this is due to the constrained environment that we &#8220;grew up in.&#8221; Mobile development is a very challenging and constraining environment to be coding in. Yes, it&#8217;s not some low level middleware device optimization code, but since day one mobile devs have to think about memory constraints, network bandwidth, CPU utilization, battery, etc., where their frontend web JS dev counterparts are completely clueless about these things.</p><p>Heck, the intermittent internet connection alone forces mobile devs to pay attention and always ask&#8212;&#8220;what is the loading state for this feature?&#8221; and &#8220;what is the error/empty state if the internet connection is down for this feature?&#8221; and so on. These are the things that frontend devs, and a lot of backend devs, usually don&#8217;t have to worry about much (even though they should!).</p><p>Another big constraint that mobile devs have to work with is the screen real estate itself. It is much more forgiving to be working in a desktop-sized environment where there is a lot of room for design and feature arrangement and where you, perhaps, could display &#8220;all the things&#8221; at the same time in one single screen. Then you don&#8217;t have to worry about navigation and routing and relationship between your screens as much.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>On mobile you simply can&#8217;t fit all that stuff on your screen at the same time so you have to be careful about how you arrange screens, how you navigate, how you present data, etc. Yea, yea, I know, you could say&#8212;&#8220;Alex, this is the designers&#8217; problem&#8221; but in reality it&#8217;s the devs who need to take care of it and the ones who need to implement it dealing with the underlying code complexity. Non-mobile devs simply lack the appreciation (and skill/knowledge) for it.</p><p>I think all of these make mobile devs perfectly suited to not only &#8220;sweat the details&#8221; on mobile but also give the same care and attention to the backend (or maybe even frontend) implementations. Mobile devs are missing out and holding themselves back by not venturing outside of their mobile world into a wider fullstack development lane.</p><p>I believe (and have observed first hand) that mobile devs can shine building features end-to-end, starting with the mobile client side and then crafting the perfect backend API and implementation for them on the backend side. They would be keenly attuned to the challenges and demands of mobile such as backend API versioning that often (ahem, always) gets overlooked by the backend and frontend engineers (where the former don&#8217;t want to deal with the complexity and the latter don&#8217;t even understand the issue).</p><h2>AI Can Help You Expand Your Scope</h2><p>So, why am I bringing it up now?</p><p>AI gives us mobile devs a golden opportunity to move up and down the stack with ease. AI augmented development has been nothing short of magic for me lately. It&#8217;s enabled me to move between codebases, mobile, backend, frontend, whatever with even more ease now and utilize LLMs as a helper, aide, automation, and a crutch (haha) to fill in the gaps of my specialized knowledge, in addition to helping aid my overall high-level system understanding of the problem I&#8217;m trying to solve and of the code I&#8217;m working in.</p><p>Are you building a new mobile screen/feature? Great, you got the iOS or Android implementation done, now use the AI to help you <a href="https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile">craft specific, bespoke, highly optimized, VERSIONED, API endpoint/s for this feature</a>. Hand it off to your backend counterparts and demand they build it this way, the right way, so that your mobile app works the best it can.</p><p>Better yet, just pull out Cursor (or your other favorite IDE/LLM) and start building out a PR with the implementation of that endpoint you want. Don&#8217;t know certain intricacies of backend development? Trust me, it is not as hard as some of the hardest stuff you had to deal with on mobile. Have LLMs help you. Open up a PR and ask your backend folks to review it, see what they say.</p><p>Or build the whole thing yourself end-to-end and integrate it all in one go. You know the constraints of mobile, you know what the API should look like, you know how to consume it, you know what needs to be done. Go for it!</p><h2>Conclusion</h2><p>AI opens up a golden opportunity for every mobile engineer to be &#8220;unshackled&#8221; and build things full stack.</p><p>Go for it, see how far you can get!</p><p>I firmly believe that you&#8217;ll be able to pick up all the intricacies of backend and frontend development much faster than the frontend and backend engineers can figure out how to build mobile apps properly!</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/mobile-engineers-youre-all-full-stack?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Swift deinit with Strict Concurrency]]></title><description><![CDATA[Deinit in strict Swift concurrency behaves differently from prior Swift versions and might cause unexpected compile or runtime issues. I&#8217;m getting annoyed with it :)]]></description><link>https://newsletter.mobileengineer.io/p/swift-deinit-with-strict-concurrency</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/swift-deinit-with-strict-concurrency</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 29 Dec 2025 12:01:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I continue my research and work on <a href="https://newsletter.mobileengineer.io/p/ribs-modernizing-ribs-swift-concurrency">modernizing the RIBs framework and adding strict Swift Concurrency support</a> to it, I stumble upon things I would&#8217;ve never otherwise.</p><p>One of them is how <code>deinit</code> behaves with the new strict Swift Concurrency.</p><p>Apparently, if you have a <code>@MainActor</code> annotated type, its <code>deinit</code> will be assumed to execute in a nonisolated context even though the type is <code>@MainActor</code>. Supposedly, this is because the object can get deallocated (its last retain count reference released) on any thread, not only the one it&#8217;s isolated for (the main in this case).</p><p>This becomes an issue if you have any type of cleanup logic in your deinit where you need to free up the resources your object is holding on to or to call any cleanup logic such as closing streams, open connections, subscriptions, etc. The compiler now will yell at you and tell you that you can&#8217;t be calling methods on <code>self</code> in <code>deinit</code>. Even if you wrap self in a <code>weak self</code> and run the methods via a <code>Task { @MainActor in }</code> wrapper, it still would not work because by the time it hops to the threads main thread, your weak self reference is going to be nil.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Now, to me this was a huge surprise and a big pain for two reasons:</p><ol><li><p>In my application codebases, I never ever ever used deinit as a logical cleanup. It just doesn&#8217;t make sense and is very error-prone to let the Swift language/framework control the flow of application logic. An explicit cleanup at the appropriate time/lifecycle is always much better.</p></li><li><p>Now I am forced to deal with this issue because I need to modernize the RIBs codebase and make it compatible with the new strict Swift concurrency.</p></li></ol><p>The way deinits are used under the hood of RIBs is for data cleanup and lifecycle propagation (such as letting all the children RIBs know that the parent is detached/deallocated so that they do their own cleanup) and for internal RIBs reference counting to keep track of memory leaks.</p><p>I&#8217;m exploring different solutions. I can&#8217;t use <code>isolated deinit</code> because I need to support older iOS/Swift versions, but most likely I&#8217;ll end up refactoring away from <code>deinit</code> entirely and have explicit cleanup methods when RIBs are detached. That way I can keep the same isolation context and have no restrictions on method access and be guaranteed that my object is still in memory.</p><h3>Conclusion</h3><p>The more I dig into Swift Concurrency and the new strict rules, the more I get annoyed with it. I understand the desire and the good intentions behind it&#8212;let&#8217;s help developers with concurrency thread hopping so that they don&#8217;t call things on the wrong thread (such as UI updates on a non-main thread) and don&#8217;t have as many race conditions and other subtle concurrency problems.</p><p>But the approach Apple took with Swift seems to me too rigid and prescriptive. Better education of the Swift community on better design patterns and architectures that avoid these threading issues would&#8217;ve sufficed. Now, instead, we all are struggling with compiler issues caused by strict concurrency rules because we want to help a few devs to write better code.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Pragmatic Backend APIs For Mobile Apps]]></title><description><![CDATA[The best mobile apps are optimized, from the client to the backend API. There are different approaches for the API: REST, RPC, GraphQL, etc. None are the silver bullet. You have to make your own blend]]></description><link>https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Fri, 19 Dec 2025 12:02:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the years of mobile development, I&#8217;ve seen all sorts of API implementations&#8212;from crudely assembled XML and JSON endpoints all the way to strict REST and OpenAPI contracts.</p><p>What&#8217;s interesting is that, quite often, not only the backend engineers designing the APIs do not fully understand the constraints and best practices for mobile, but even mobile engineers themselves don&#8217;t know what a good backend API for mobile should look like.</p><p>In this article, I&#8217;ll outline my high-level approach and what I care about when designing backend APIs for mobile apps.</p><h2>The Problem. Typical Approach. Abdication of Responsibility.</h2><p>When I ask mobile developers what makes a good API, they typically give me vague answers like, &#8220;It should be JSON, it should be RESTful, and it should be optimized for mobile and scale well.&#8221; Sometimes I even hear &#8220;it all should be GraphQL!&#8221;</p><p>At the end of the day the tendency of mobile engineers is to just blindly accept the API that they are given or rely on the backend engineers to create something decent that would be convenient and effective for the mobile app to consume.</p><p>Mobile engineers should either advocate for and educate backend developers about effective mobile-first API design, or ideally, take the lead by designing the API contract themselves for the backend team to implement.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Mobile App Constraints</h2><p>Mobile apps run on mobile devices (duh!). Despite advancements in chip design and battery life they are still much more limited than an application on the desktop that has persistent internet and power connection.</p><ol><li><p><strong>Connectivity:</strong> Networks are spotty, slow, and often intermittent (e.g., commuting in a subway).</p></li><li><p><strong>Battery:</strong> Every network request and CPU cycle drains the battery.</p></li><li><p><strong>Memory:</strong> Limited RAM (I mean relatively speaking nowadays) means working large data sets is still a challenge on mobile</p></li><li><p><strong>System Optimization</strong>: because of all of these challenges there are system optimizations in place where the work/compute of your app can be paused and resumed or limited by the OS itself.</p></li><li><p><strong>API Versioning is important</strong>: mobile apps do not update on users devices in a blink of an eye/page refresh like the web apps do so maintaining several versions behind of your API is prudent to keep older versions of the app still working.</p></li></ol><p>Because of all of these constraints it usually leads to issues and degraded user experience if you reuse the same backend APIs that power, let&#8217;s say, your web/desktop application, and use them in your mobile application.</p><p>Typically reusing the API like this leads to mobile apps stitching several API requests of poorly optimized bloated JSON together to give something meaningful to the user like a complete feature or full screen of data for a given user experience.</p><h2>Pragmatic JSON API Design for Mobile</h2><p>There are several different approaches, architectures, design patterns, and conventions out there on how to organize and assemble your backend APIs. Here are some of the most common ones you would encounter:</p><h3>REST vs GraphQL vs RPC</h3><p>The debate around API design often revolves around what architectural style to pick for the API. Is it GraphQL that&#8217;s the best? Or REST? Or RPC? Or something else?</p><p>At the end of the day they have their own pros and cons. I am not going to dive deep into them here but here&#8217;s my brief take.</p><h4>GraphQL. the best for public APIs. poor choice for internal API.</h4><p>GraphQL - sounds good in theory but in practice a poor choice for API design for an internal API for your own mobile app consumption. The abundance of choice and permutations and configurability of the &#8220;queries&#8221; from the client side put the burden of API design on the client rather than where it belongs on the backend. This leaves backend with highly unpredictable and hard to optimize mess of requests/queries that is difficult to support and scale.</p><p>GraphQL is better reserved for situations where you don&#8217;t know the use case for your API upfront - aka for publicly facing APIs, because you don&#8217;t know how your users will be actually getting and sending the data to your backend and what is actually important to them, so yuo optimize for abundance of choice rather than on specific pre-determined/prescribed set of actions/choices/user flows.</p><h4>RPC. problem domain specific but not standardized.</h4><p>RPC is where the API organized around &#8220;remote procedure calls&#8221; (hence the name) and the idea behind it is that you make a call on the client side like it was &#8220;yet another method call for yet another procedure of your app&#8221; but under the hood, instead of executing it locally, it calls a &#8220;remote server&#8221; to do the compute and then it returns the result asynchronously. It&#8217;s a convoluted way of describing an API where it doesn&#8217;t have strict standardization and rules and has a much more free form arrangement/implementation where the endpoints you call mimic potential method calls if you were to call them locally rather than on the backend server.</p><p>The upside of this design pattern for API is that you could be very specific with the domain problem you&#8217;re solving and the API you&#8217;re serving for mobile but the downside is that there is no standard and it could lead to a bit of a chaos with widely different implementations even on the same backend API server. REST has a much better story on standardizing how the API endpoints look and behave.</p><h4>REST. the golden standard. Solid overall choice.</h4><p>REST is a fantastic standard and API design/architecture that nowadays is a golden standard and the golden foundational best practice for designing your own APIs. It encompases everything from the low level stuff like using HTTP status codes and verbs properly and extensively all the way to the high level prescription of organizing your endpoints around resources/entities in your system.</p><p>REST is probably the best choice if you don&#8217;t know what you&#8217;re optimizing for yet and just need to get started. It will carry you forward far, sometimes very far, but the downside is that you can&#8217;t always model everything around resources, especially when it comes to rich user experiences on mobile where one single screen might do a lot of stuff for the user. You will find yourself stitching multiple REST endpoint requests to multiple resources/endponits quite fast as you try to fulfill the data requirements for your mobile application screens.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Bespoke Backend APIs for Mobile User Experience (a little bit of RPC, a little bit of REST)</h3><p>So what does <strong>&#8220;API optimized for mobile&#8221;</strong> even mean? Afterall we have all these choices (and more that I haven&#8217;t covered) to design our backend APIs consumed by mobile apps. Which one to go for?</p><p>At the end of the day I came to a conclusion that there is no one best option to go with and instead a <strong>highly optimized backend API for mobile should contain in itself the best parts of every approach</strong>.</p><p>Basically it boils down to <strong>- the API mobile app consumes needs to be bespoke and highly optimized for the user experience this API supports</strong>.</p><h4>RPC style user experience endpoints</h4><p>In practice this means that instead of RESTful collection of resources API style where the mobile app needs to make multiple requests and stitch data together on the client side to serve a screen or a feature you instead would create an RPC style API where each endpoint returns full set of data needed for a given screen or feature to work.</p><p>Highly optimized mobile endpoints like that would receive and return only the data needed for the mobile app to function, no more no less. They would not have any extra fields, properties, data, etc. &#8220;just in case&#8221; or because the endpoint is shared with other clients such a desktop web app. It wastes resource if you do it this way.</p><p>There would be no generic /users/235 or /posts/123 and /posts/123/comments endpoints. Instead you get one RPC style endpoint such as /postDetails/123 that returns combined, and only necessary, fields from all 3 of those endpoints to serve a post details screen in a mobile app that displays some post info + the comments + some of the author user info.</p><h4>HTTP status codes and verbs</h4><p>Highly optimized mobile endpoints, unlike simple RPC ones, utilize HTTP status codes and HTTP verbs heavily, just like REST endpoints do. The reason is because the convention is highly understood and predictable and conveys a lot of information about the request or response in a very short and concise manner. This makes the API instantly recognizable and predictable in its behavior. You can even get a lot of code generation/automation with popular networking libraries out there &#8220;for free&#8221; out of the box if you stick with these conventions.</p><h4>API Versioning</h4><p>Highly optimized mobile endpoints are heavily versioned. As mentioned above, mobile apps do not update all at once and there are always laggard users who update their applications late or refuse to do so. Highly optimized mobile endpoints need to be versioned to support those older versions of the app that are still in users hands despite the new hot latest app update that you pushed to the app store. <br><br>There are different ways and approaches to versioning backend APIs and it could be an entire article on its own to explore all of them. I&#8217;ll just leave it here by sharing what worked for me - version endpoint by endpoint and version frequently. Rely on passing IDs/UDIDs of your entities as much as possible instead of full object shapes, it helps with backwards compatibility of your domain models.</p><h2>Conclusion</h2><p>Even though we, as mobile engineers, might not care as much about backend API design, it is actually very important for us to be not only aware but advocate for more highly optimized mobile API endpoints to make our apps better, our user&#8217;s experience better, and our coding lives easier.</p><p>Go talk to your backend engineers about this. It&#8217;s 2025, there is no excuse to keep accepting the backend API you&#8217;re given stitching together 10 different endpoint responses on the client side to simply display a screen in your mobile application.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/pragmatic-backend-apis-for-mobile?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Enum Switch/Case vs. Protocol with Multiple Method Calls]]></title><description><![CDATA[There is so much dogma in the iOS community. Enum based protocols with lots of switch/casing considered to be good where, most of the time a more straightforward message sending/delegate would suffice]]></description><link>https://newsletter.mobileengineer.io/p/api-design-debate-enum-switchcase</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/api-design-debate-enum-switchcase</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 17 Nov 2025 12:00:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9qLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9qLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9qLh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9qLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:198914,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.mobileengineer.io/i/178991436?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9qLh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9qLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bb12ebe-8b6b-40ca-9566-45a9df7ce0f3_1024x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In my development career, I jumped around different languages and platforms. Everything from Objective-C on iOS (and later Swift), to Java on Android (and later Kotlin), to Ruby on Rails and lately Typescript/Node, and a bunch of things in between (Dart before Flutter days), and more.</p><p>I saw different pros and cons and strengths and weaknesses that each language, framework, pattern, or community exhibits. At the end of the day, I wasn&#8217;t able to find a community, pattern, convention, framework, or language that fully satisfies me and my sense of what is good code or bad.</p><p>What I ended up doing is picking &#8220;the good parts&#8221;&#8212;different bits and pieces, conventions, approaches, etc.&#8212;from each one of those different languages, frameworks, and communities and mixing them together in whatever language or platform I happen to use and write code for at the time. It actually turned out pretty well and at the end of the day is grounded in SOLID principles and Clean Code/Clean Architecture which are all language, framework, and community agnostic.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>So given all that, it might not be surprising that I find some (if not a lot lol) parts of Swift and Swift community conventions odd or pointless or wrong. For example, the other day, I was reviewing some code in an iOS project that had to deal with connecting to a voice audio recording library. It was already implemented in our codebase and I had to take a look at its API to understand how to use it and at the current usage of it across the codebase.</p><p>My assumption was&#8212;well, there is probably some wrapper object around the library&#8217;s class/object that abstracts out starting, pausing, and stopping the recording. And I assumed there would be some sort of a delegation (via protocol, or callback closures, or a stream of data) happening upon recording start to &#8220;stream&#8221; the recorded data/voice/text as it happens. And sure enough it was; both of those assumptions were correct but the data streaming/delegate/callback part gave me a pause.</p><p>Here&#8217;s how the API looked like:</p><pre><code>enum RecorderState {
    case idle
    case recording
    case stopped(finalText: String)
    case failed(error: Error)
    case receivedText(chunk: String)
}

protocol AudioRecorderDelegate: AnyObject {
    func audioRecorder(didChangeStateTo state: RecorderState)
}

protocol AudioRecorderProtocol {
    var delegate: AudioRecorderDelegate? { get set }
    var currentState: RecorderState { get }
    func start()
    func stop()
}

final class AudioRecorder: AudioRecorderProtocol {
    weak var delegate: AudioRecorderDelegate?
    
    private(set) var currentState: RecorderState = .idle
    
    func start() {
        self.currentState = .recording
        delegate?.audioRecorder(didChangeStateTo: currentState)
    }
    
    func stop() {
        let finalText = &#8220;Final transcribed text here&#8221;
        self.currentState = .stopped(finalText: finalText)
        delegate?.audioRecorder(didChangeStateTo: currentState)
    }
    
    // ... more business logic to update currentState and notify delegate about recording process
}</code></pre><p>Everything is pretty straightforward, and the usage of it looks like this:</p><pre><code>final class ViewModel: AudioRecorderDelegate {
    
    private var audioRecorder: AudioRecorderProtocol
    
    var onStateChanged: ((RecorderState) -&gt; Void)?
    
    init(audioRecorder: AudioRecorderProtocol = AudioRecorder()) {
        self.audioRecorder = audioRecorder
        self.audioRecorder.delegate = self
    }
    
    func startRecording() {
        audioRecorder.start()
    }
    
    func stopRecording() {
        audioRecorder.stop()
    }
    
    func audioRecorder(didChangeStateTo state: RecorderState) {
        switch state {
        case .idle:
            // do stuff
            break
            
        case .recording:
            // do stuff
            break
            
        case .stopped(let finalText):
            // do stuff
            break
            
        case .failed(let error):
            // do stuff
            break
            
        case .receivedText(let chunk):
            // do stuff
            break
        }
    }
}</code></pre><p>At first glance any Swift developer would say - &#8220;Alex, I see nothing wrong about this. There is an enum with all the states, this is very Swifty!&#8221; And yes, this seems to be the pattern that the Swift community fancies - to pass all the possible states/scenarios like this via a callback in one property and to declare an enum that lists all the possible cases. I acknowledge that there are good parts to this - for example, you get the compiler time safety when you add new states/cases to this enum. The compiler will warn you and make you explicitly handle all of them.</p><p>For any Swift developer this would be just it - the dogma says that this is good and this is how it should be.</p><p>But the problem is this approach makes your API less explicit and breeds the switch/case ifelsing across your codebase. The more new enums you add the more cases in the switch you&#8217;d need to have, especially if you pass that enum value around in different parts of your code.</p><p>And on top of it, the worst issue is that this makes your API readability much worse - it is very hard to say at a glance what it does, what kinds of callbacks would you receive if you are the delegate of this audio recorder. All you get is just this one method declaration: func audioRecorder(didChangeStateTo state: RecorderState) in AudioRecorderDelegate. You have to now hunt down for the enum RecorderState declaration (hopefully it&#8217;s in the same file, but likely it won&#8217;t be, because reasons lol) to actually understand what the API is. And imagine if you had several callback methods like this in AudioRecorderDelegate and each one of them had their own set of enums with their own 3-5 cases - you multiplied the problem of this &#8220;treasure hunt for the API declaration&#8221; tenfold!</p><p>Bottom line, even though the Swift community loves it, this approach breaks SOLID principles and makes your API implicit, indirect, and scattered.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>A better approach is to stick to a more generic, tried and true, OOP/SOLID principles approach of sending messages and explicitly declaring your API as a set of dead simple callback methods:</p><pre><code>protocol AudioRecorderDelegateV2: AnyObject {
    func audioRecorderDidBecomeIdle()
    func audioRecorderDidStartRecording()
    func audioRecorderDidStop(with finalText: String)
    func audioRecorderDidFail(with error: Error)
    func audioRecorderDidReceiveText(chunk: String)
}

protocol AudioRecorderV2Protocol {
    var delegate: AudioRecorderDelegateV2? { get set }
    func start()
    func stop()
}

final class AudioRecorderV2: AudioRecorderV2Protocol {
    weak var delegate: AudioRecorderDelegateV2?
    
    func start() {
        delegate?.audioRecorderDidStartRecording()
    }
    
    func stop() {
        let finalText = &#8220;Final transcribed text here&#8221;
        delegate?.audioRecorderDidStop(with: finalText)
    }
    
    // ... more business logic to notify delegate about recording process
}</code></pre><p>And the consumption now looks like this:</p><pre><code>final class ViewModelV2: AudioRecorderDelegateV2 {
    
    private var audioRecorder: AudioRecorderV2Protocol
    
    var onIdle: (() -&gt; Void)?
    var onRecordingStarted: (() -&gt; Void)?
    var onRecordingStopped: ((String) -&gt; Void)?
    var onRecordingFailed: ((Error) -&gt; Void)?
    var onTextReceived: ((String) -&gt; Void)?
    
    init(audioRecorder: AudioRecorderV2Protocol = AudioRecorderV2()) {
        self.audioRecorder = audioRecorder
        self.audioRecorder.delegate = self
    }
    
    func startRecording() {
        audioRecorder.start()
    }
    
    func stopRecording() {
        audioRecorder.stop()
    }
    
    func audioRecorderDidBecomeIdle() {
        // do stuff
        onIdle?()
    }
    
    func audioRecorderDidStartRecording() {
        // do stuff
        onRecordingStarted?()
    }
    
    func audioRecorderDidStop(with finalText: String) {
        // do stuff
        onRecordingStopped?(finalText)
    }
    
    func audioRecorderDidFail(with error: Error) {
        // do stuff
        onRecordingFailed?(error)
    }
    
    func audioRecorderDidReceiveText(chunk: String) {
        // do stuff
        onTextReceived?(chunk)
    }
}</code></pre><p>The AudioRecorderDelegateV2 declaration is very straightforward and tells you everything it does at the first glance:</p><pre><code>protocol AudioRecorderDelegateV2: AnyObject {
    func audioRecorderDidBecomeIdle()
    func audioRecorderDidStartRecording()
    func audioRecorderDidStop(with finalText: String)
    func audioRecorderDidFail(with error: Error)
    func audioRecorderDidReceiveText(chunk: String)
}</code></pre><p>There is no ambiguity, there is no hunting for other declarations, it&#8217;s just all there, explicit as simple messages to be sent from one object to another.</p><p>There is also still the same benefit of compiler time safety - if you add another method to the delegate declaration about a new event happening, everyone who adopts that protocol will get a compiler time warning and will have to implement it, just like with the enum case.</p><p>This instantly reduces the amount of ifelsing/switch-casing across your codebase and makes skimming and understanding your API dead simple. And, I&#8217;d argue, especially so when using LLMs nowadays. On top of it, it reduces the temptation to breed state and pass along the enum for other parts of the code to figure out what to do with each switch/case.</p><h2><strong>Conclusion</strong></h2><p>At the end of the day, the difference between the two approaches boils down to <strong>Enum State Machine</strong> approach vs <strong>Event Emitter</strong> approach.</p><p>The &#8220;Swifty Enum State&#8221; approach is a great tool for modeling objects whose internal state needs to be guaranteed (e.g., a Result type, or a single state machine within a component).</p><p>However, when designing an API that is purely meant to act as an Event Emitter&#8212;sending asynchronous messages about changes&#8212;the traditional, multi-callback approach remains the superior choice for maintainability, simplicity, and adherence to the Interface Segregation Principle. It is cleaner, more readable, and respects the fact that your consuming ViewModel is the true gatekeeper of your application&#8217;s state.</p><p>We should embrace the flexibility of well-designed interfaces, rather than defaulting to monolithic enum structures just because the Swift language makes switch statements easy.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/api-design-debate-enum-switchcase?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/api-design-debate-enum-switchcase?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/api-design-debate-enum-switchcase?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Commentary on Jacob’s Swift for Android vs. KMP Article]]></title><description><![CDATA[Swift for Android is here. It is going to be a tough choice in a few years to decide which language to pick for multi-platform development but as of today Kotlin Multiplatform(KMP) is the clear winner]]></description><link>https://newsletter.mobileengineer.io/p/commentary-on-jacobs-swift-for-android</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/commentary-on-jacobs-swift-for-android</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Tue, 11 Nov 2025 12:03:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CVeQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently had the privilege of proofreading and sharing my thoughts with Jacob about his <strong>Swift for Android vs. Kotlin Multiplatform</strong> article before it was published.</p><p>Jacob&#8217;s done a great job fairly covering both KMP and Swift for Android implementations and showcasing their respective pros and cons! If you haven&#8217;t read his article go ahead and <a href="https://blog.jacobstechtavern.com/p/swift-for-android-vs-kmp">read it here</a>. While you&#8217;re at it don&#8217;t forget to subscribe to Jacob&#8217;s substack!!!</p><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:177580473,&quot;url&quot;:&quot;https://blog.jacobstechtavern.com/p/swift-for-android-vs-kmp&quot;,&quot;publication_id&quot;:1376173,&quot;publication_name&quot;:&quot;Jacob&#8217;s Tech Tavern&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!LJp-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee402e25-4e20-4683-ba5f-20ca86eb2b43_512x512.png&quot;,&quot;title&quot;:&quot;Swift for Android vs. Kotlin Multiplatform&quot;,&quot;truncated_body_text&quot;:&quot;If you&#8217;ve been living under a rock for a year, I have shocking news about the election*, and great news about the future of Swift portability.&quot;,&quot;date&quot;:&quot;2025-11-03T16:20:36.220Z&quot;,&quot;like_count&quot;:12,&quot;comment_count&quot;:0,&quot;bylines&quot;:[{&quot;id&quot;:126930235,&quot;name&quot;:&quot;Jacob Bartlett&quot;,&quot;handle&quot;:&quot;jacobbartlett&quot;,&quot;previous_name&quot;:&quot;Jacob&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!s80e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab1e065-dffc-4096-ad9e-826ddda8a6cd_1304x1304.png&quot;,&quot;bio&quot;:&quot;Master iOS. Boost your salary. Join 60,000 senior Swift devs learning advanced concurrency, SwiftUI, and iOS performance for 10 minutes a week. Sign up free today!&quot;,&quot;profile_set_up_at&quot;:&quot;2023-02-02T18:52:30.739Z&quot;,&quot;reader_installed_at&quot;:&quot;2023-04-04T22:17:31.487Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:1337542,&quot;user_id&quot;:126930235,&quot;publication_id&quot;:1376173,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:true,&quot;publication&quot;:{&quot;id&quot;:1376173,&quot;name&quot;:&quot;Jacob&#8217;s Tech Tavern&quot;,&quot;subdomain&quot;:&quot;jacobbartlett&quot;,&quot;custom_domain&quot;:&quot;blog.jacobstechtavern.com&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Master iOS. Boost your salary.\nJoin 60,000 senior Swift devs each month learning advanced concurrency, SwiftUI, and iOS performance for 10 minutes a week.\nSign up free today!&quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee402e25-4e20-4683-ba5f-20ca86eb2b43_512x512.png&quot;,&quot;author_id&quot;:126930235,&quot;primary_user_id&quot;:126930235,&quot;theme_var_background_pop&quot;:&quot;#B599F1&quot;,&quot;created_at&quot;:&quot;2023-02-02T18:56:42.742Z&quot;,&quot;email_from_name&quot;:&quot;Jacob&#8217;s Tech Tavern&quot;,&quot;copyright&quot;:&quot;Jacob Bartlett&quot;,&quot;founding_plan_name&quot;:&quot;Regular &#127866;&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;enabled&quot;,&quot;language&quot;:null,&quot;explicit&quot;:false,&quot;homepage_type&quot;:&quot;newspaper&quot;,&quot;is_personal_mode&quot;:false}}],&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:100,&quot;status&quot;:{&quot;bestsellerTier&quot;:100,&quot;subscriberTier&quot;:1,&quot;leaderboard&quot;:null,&quot;vip&quot;:false,&quot;badge&quot;:{&quot;type&quot;:&quot;bestseller&quot;,&quot;tier&quot;:100},&quot;paidPublicationIds&quot;:[159369,2355025,1815472],&quot;subscriber&quot;:null}}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:false,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;,&quot;source&quot;:null}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://blog.jacobstechtavern.com/p/swift-for-android-vs-kmp?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!LJp-!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee402e25-4e20-4683-ba5f-20ca86eb2b43_512x512.png"><span class="embedded-post-publication-name">Jacob&#8217;s Tech Tavern</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">Swift for Android vs. Kotlin Multiplatform</div></div><div class="embedded-post-body">If you&#8217;ve been living under a rock for a year, I have shocking news about the election*, and great news about the future of Swift portability&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">7 months ago &#183; 12 likes &#183; Jacob Bartlett</div></a></div><p>I thought I&#8217;d share my comments with the readers of <em>The Mobile Engineer</em> as well.</p><p>I mainly can comment on the <strong>Kotlin Multiplatform (KMP)</strong> side of things, as this is what we use at <strong>UpKeep</strong> and what I use in my personal projects. I&#8217;m still working on Part 2 and 3 of the architectural overview article that I published previously: <a href="https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part">Mobile Architecture at UpKeep, Part 1</a>. They will cover more in depth the usage of KMP that we have in our iOS and Android apps.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>On Platform-Specific Code and Decoupling</strong></h2><p>Jacob highlighted KMP&#8217;s expect/actual mechanism for injecting platform-specific code. </p><blockquote><p>Kotlin Multiplatform has a very neat way of neatly injecting platform-specific code. First, you declare a provider file with an expect fun.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WKN_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WKN_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 424w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 848w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 1272w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WKN_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png" width="1240" height="536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:536,&quot;width&quot;:1240,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WKN_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 424w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 848w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 1272w, https://substackcdn.com/image/fetch/$s_!WKN_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3591dd3-e0e4-4f80-b9b2-7d3e7a1bbdbd_1240x536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>Now the actual fun begins. For each platform you&#8217;re implementing on, you set up the expect fun&#8217;s implementation: &#8220;actual fun&#8221;. Here, we can inject Ktor&#8217;s built-in HTTP client for Darwin platforms.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VCDj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VCDj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 424w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 848w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 1272w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VCDj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png" width="1456" height="911" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:911,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VCDj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 424w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 848w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 1272w, https://substackcdn.com/image/fetch/$s_!VCDj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6632feec-d74f-4b95-8c14-9fbacccb4419_1456x911.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While neat, I lean toward a more decoupled approach using <strong>Dependency Injection (DI)</strong>.</p><p>Instead of expect/actual, which keeps the platform-specific code declaration in the common module, we favor having the common KMP module depend on a simple <strong>interface</strong>. The platform-specific code (in the Android or iOS codebase) then implements that interface, and we inject that concrete object into the KMP class.</p><p>This approach offers a few key benefits:</p><ol><li><p><strong>Decoupling:</strong> It keeps the shared KMP code <strong>pure</strong> and agnostic, free from any platform-specific boilerplate required by expect/actual.</p></li><li><p><strong>Clean Architecture:</strong> It adheres more closely to standard DI patterns, avoiding what some might view as &#8220;hacks or crutches&#8221; when platform capabilities are involved.</p></li></ol><h2><strong>Modularization: The Single Umbrella Framework</strong></h2><p>Jacob pointed out a major pain point for KMP users: having to expose the shared logic as a <strong>single umbrella framework</strong> to the platform apps (well, iOS in particular).</p><blockquote><p>Idiomatically, the KMP shared library is called &#8220;Common&#8221;. Or, sometimes, &#8220;Shared&#8221;. You can modularise the library as much as you like, but you will need to expose this to the platform apps as a single umbrella framework. This is pretty annoying for namespacing, but you stop noticing it pretty fast*.<br><a href="https://blog.jacobstechtavern.com/i/177580473/calling-kotlin-from-ios">https://blog.jacobstechtavern.com/i/177580473/calling-kotlin-from-ios</a></p></blockquote><p>This is absolutely true, and it creates annoying namespacing challenges that you eventually just have to live with.</p><p>At UpKeep, we ran into this exact issue when setting up our KMP integration. The reason for this single module constraint usually boils down to how fundamental types (like String, Int, Array or the fundamental custom types you share) are handled. If you export multiple modules that all depend on these foundational types, the platform (specifically Swift/Obj-C) can see them as incompatible, distinct types across the different modules.</p><p>However, there is good news: the KMP team is working on solving this. There is a <strong><a href="https://carrion.dev/en/posts/swift-export/">beta version of direct Swift export</a></strong> for KMP that appears to resolve this issue, which should allow us to export multiple, separate modules to Swift soon.</p><h2><strong>The SKIE Debate and Interoperability</strong></h2><p>The article mentioned <strong><a href="https://skie.touchlab.co/">SKIE</a> (Swift Kotlin Interface Enhancer)</strong> as a way to smooth out rough edges around things like suspend functions and sealed classes, making them more idiomatic Swift.</p><blockquote><p>When you&#8217;re running a long-term KMP project, you might want to use SKIE (Swift Kotlin Interface Enhancer). There&#8217;s a few rough edges around things like suspend functions, interfaces, and sealed classes which the library sands down into idiomatic Swift signatures.</p></blockquote><p>We went back and forth on adopting SKIE, but ultimately, we decided against it. Instead of relying on an external tool to bridge the gap, we manage the interoperability ourselves. On the iOS side, we use simple <strong>wrapper classes</strong> around the KMP interfaces to convert them into familiar platform types like <strong>RxSwift Observables/Singles</strong>. This gives us more explicit control over the final developer experience.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Headless Logic and Testing</strong></h2><p>I loved Jacob&#8217;s idea of creating a little <strong>CLI app target</strong> to interact with and test the multi-platform module outside of the UI. This is a neat way to handle testing and interaction!<br><br><a href="https://blog.jacobstechtavern.com/i/177580473/working-with-headless-libraries">https://blog.jacobstechtavern.com/i/177580473/working-with-headless-libraries</a></p><p>I&#8217;ve long been an advocate for <strong>headless business logic</strong> (logic that runs without any UI dependency), even before the multiplatform movement. Now, it makes even more sense. Personally, I achieve similar benefits by running real staging API calls directly from my <strong>unit and integration tests</strong> instead of relying on a separate CLI.</p><h2><strong>Architectural Boundaries: ViewModels</strong></h2><p>A great question Jacob posed was: &#8220;Where do the view models go?&#8221;</p><blockquote><p>This is the single most controversial point of debate when implementing a multiplatform solution. Where do we put the view models? &#8220;Shared business logic&#8221; unambiguously includes networking, caching, analytics, models, processing, queues, config, and most of your services. But view models (and, depending on your architecture, perhaps interactors, presenters, and navigators) could go either way. This is a double-edged sword.</p></blockquote><p><a href="https://blog.jacobstechtavern.com/i/177580473/where-do-the-view-models-go">https://blog.jacobstechtavern.com/i/177580473/where-do-the-view-models-go</a></p><p>In my view, <strong>View Models (VMs) are a platform-specific concept</strong>. If they are simply plain old objects used purely for managing UI state and exposing data from the shared layer (without containing business logic), they belong in the platform codebase (Android/iOS).</p><p>For sharing higher-level architecture components, like <strong>Interactors</strong> or <strong>Presenters</strong>, the debate continues. We don&#8217;t share these at UpKeep <em>yet</em>, but I&#8217;m actively considering porting some or all of a framework like <strong>RIBs</strong> to KMP for that exact reason. I hope to write an article about that process someday!</p><h2><strong>The Mental Overhead of IDE Context Switching</strong></h2><p>Haha, Jacob nailed naming the IDE context switching overhead with the title <a href="https://blog.jacobstechtavern.com/i/177580473/the-big-ide-fuckfest">The big IDE fuckfest</a> lol</p><blockquote><p>I doubt &#8220;the big IDE crapfest&#8221; would sufficiently convey the overwhelming mental overhead that comes with bouncing between 2 instances of Android Studio (or IntelliJ) and 2 instances of Xcode, for 2 different implementations of the same project.</p></blockquote><p>I understand that this is a huge pain for some developers but IMO - I don&#8217;t mind because I actually bounce between <strong>three</strong> IDEs on my mobile projects: <strong>Xcode</strong> (for iOS), <strong>Android Studio</strong> (for Android), and <strong>IntelliJ IDEA</strong> (for KMP/Kotlin code)! (oh and + Cursor these days for LLM code generation). While the overhead is real, the value gained from sharing the core logic often outweighs the annoyance. Plus, I personally appreciate the mental split - ok, I&#8217;m in Xcode, now I&#8217;m working on the iOS side, next I&#8217;m in IntelliJ IDEA, now I&#8217;m working on the shared business logic with no UI overhead, etc.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZY8r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZY8r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZY8r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZY8r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!ZY8r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82929b02-f4de-4b84-8037-e3cbe6be74cd_2032x1161.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O0EG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O0EG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O0EG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O0EG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!O0EG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2e85373-eff1-4bc2-aeba-173dd6a43f59_2032x1161.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CVeQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CVeQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CVeQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CVeQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!CVeQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f0ce1c-b2cc-44a1-b3fd-8bc9a4837bb5_2032x1161.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U6vD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U6vD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U6vD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U6vD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 424w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 848w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 1272w, https://substackcdn.com/image/fetch/$s_!U6vD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22db3568-c262-4aaf-8625-8f6367995376_2032x1161.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>Team Structure: Core vs. Full-Stack</strong></h2><p>Finally, Jacob noted that some KMP shops use &#8220;core engineers&#8221; (business logic) vs. &#8220;design engineers&#8221; (UI) model, which requires careful API contract coordination.</p><p>At UpKeep, we&#8217;ve taken a different approach. We are a smaller team&#8212;three iOS and three Android engineers, six total&#8212;and we intentionally encourage a <strong>&#8220;full-stack&#8221; approach</strong>. We make iOS developers learn KMP/Kotlin and Android developers become familiar with Swift and iOS compilation issues. This ensures that every developer has a stake in the shared logic and can troubleshoot issues across all parts of the mobile stack.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Conclusion</strong></h2><p>Jacob did a great job covering and comparing Swift for Android and KMP. Currently, Swift for Android is clearly not ready for primetime, lacking the polish and tooling that KMP provides. But, it will be interesting to see how they stack up against each other in a few years. My personal preference remains KMP, as I find Kotlin to be a more &#8220;sane&#8221; and unsurprising (which is a good thing) language compared to Swift, so I will continue to use it for multi-platform development for the time being.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/commentary-on-jacobs-swift-for-android?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/commentary-on-jacobs-swift-for-android?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/commentary-on-jacobs-swift-for-android?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[[RIBs] Modernizing RIBs: Swift Concurrency Support and Strict Isolation (POC)]]></title><description><![CDATA[I&#8217;m continuing to work on modernizing the RIBs framework for iOS. Last weekend, I opened a PR with a proof of concept (POC) showing how Swift Concurrency and the new Swift 6.2 strict concurrency rules can be integrated into RIBs.]]></description><link>https://newsletter.mobileengineer.io/p/ribs-modernizing-ribs-swift-concurrency</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/ribs-modernizing-ribs-swift-concurrency</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 20 Oct 2025 22:06:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!V-Zt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m continuing to work on modernizing the <strong>RIBs framework for iOS</strong>. Last weekend, I opened a PR with a proof of concept (POC) showing how <strong>Swift Concurrency</strong> and the new Swift 6.2 strict concurrency rules can be integrated into RIBs.</p><p>Before diving into my proposed POC (which is still a work in progress and might change!), I&#8217;ll give a quick overview of the RIBs architecture, its current state on iOS, and how it handles concurrency today.</p><h2>RIBs Architecture</h2><p>Overall, RIBs architecture allows you to model your application as a tree of business logic scopes, where each scope contains at least the 3 core things: <strong>Router</strong>, <strong>Interactor</strong>, and <strong>Builder</strong>. That&#8217;s where the acronym RIBs is coming from.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sGwr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sGwr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 424w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 848w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 1272w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sGwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png" width="1456" height="1391" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1391,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sGwr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 424w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 848w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 1272w, https://substackcdn.com/image/fetch/$s_!sGwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc9cf0e4-0de1-4279-ae41-7324dbad888a_1456x1391.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each RIB scopes business logic and can have an optional view. Because of this, RIBs do not necessarily correlate with screens that your application has, but for a lot of them, that&#8217;s the case.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0EzO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0EzO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 424w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 848w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 1272w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0EzO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png" width="1456" height="549" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:549,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0EzO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 424w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 848w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 1272w, https://substackcdn.com/image/fetch/$s_!0EzO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b7cee2-d2fa-4d68-945b-07eeec5aaad5_1456x549.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And a RIB has additional things that it utilizes based on the concrete needs and implementation of your application, like: Services, Managers, Storages, Streams, Use Cases, etc.</p><p>By its nature, RIBs architecture is business logic driven rather than UI driven. This is by design so that asynchronous background work and async reactive data streams in the business logic can be made the centerpiece of the application rather than fickle whims of UI/UX design changes.</p><p>Another key concept is that there is a <strong>unidirectional data flow</strong> in RIBs. The data flows from the top parent RIBs down in a one-to-many broadcasting fashion, and data flowing up from children to parents goes via a one-to-one relationship. This allows for very effective state management that helps avoid data races and other data issues in the architecture.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Concurrency with RxSwift</h2><h3>The Role of Reactive Streams</h3><p>To achieve concurrency and handle reactive data streams and asynchronous work in RIBs, <strong>RxSwift</strong> is traditionally used for its rich set of high-order functions, observables, subjects, and other reactive data stream implementations. RxSwift has the advantage of being a well-established and battle-tested reactive streams implementation standard that was ported, with minimal deviation, to every popular programming language.</p><p>Practically, when you work with RIBs, this doesn&#8217;t mean that you <em>have</em> to use RxSwift, but doing so will give you convenient helpers and integrations with RIB&#8217;s lifecycle to help you manage your observable streams and async work. You could, of course, roll out your own using Combine, Swift Concurrency&#8217;s async/await, or something else. You&#8217;d just have to manage your streams of data and async jobs yourself, making them work with the RIB&#8217;s lifecycle and ensuring they cancel when a RIB is detached.</p><h3>Best Practice: Decoupling UI from Streams</h3><p>Day to day, I found it to be the best practice in RIBs to have a clear separation and standard on how you use RxSwift in your applications: <strong>use streams/observables for async work everywhere in the business logic of the app but completely separate and keep it out of the UI view layer of the application.</strong></p><p>In practical terms, this means:</p><ul><li><p>If you need to do a one-off async job (like a network request), have a service object produce a Single that wraps the request.</p></li><li><p>The <strong>Interactor</strong> (the center of the business logic) injects the service, launches/subscribes to these streams, and binds them to its own lifecycle for data cleanup on detachment.</p></li></ul><p>When it comes to updating the UI based on the data the streams/async jobs produce, instead of passing those streams down to the UI, you instead call methods on your RIB&#8217;s <strong>Presenter</strong> to present the received data. This way, you achieve a clean separation of concerns, avoid cluttering the view layer with Rx subscription logic, and decouple the async operation&#8217;s lifecycle from the UI&#8217;s lifecycle.</p><p>But I digress... I can be talking about this for hours and this warrants its own post (which I will write in the future), but this is overall how asynchronous work is implemented in RIBs.</p><h2>The Problem</h2><p>The problem today is two-fold:</p><ol><li><p><strong>No Native Swift Concurrency Helpers:</strong> There is currently no helper or integration to work with Swift Concurrency&#8217;s async/await directly with RIBs. If you want to call async functions from your Interactor or its dependencies, you&#8217;d have to manage wrapping them yourself into a Task and then managing its cancellation with the RIB&#8217;s lifecycle (e.g., wrapping the Task into an RxSingle or similar).</p></li><li><p><strong>Swift 6.2 Strict Concurrency Violations:</strong> With the new Swift 6.2 strict concurrency rules enabled, several compiler warnings and errors related to actor isolation appear when using RIBs.</p></li></ol><p>While both issues are fixable by adjusting your codebase and jumping through a lot of hoops to make the compiler happy, it&#8217;s not optimal. The amount of changes required for existing RIBs projects could be overwhelming and impractical.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The Solution</h2><p>I outlined the POC of my solution in the PR I opened: <a href="https://github.com/uber/RIBs-iOS/pull/45">https://github.com/uber/RIBs-iOS/pull/45</a>. I&#8217;ll try to give it more context, detail, and a summary here.</p><p>Keep in mind this is still a proof of concept, and the proposed solution could dramatically change. There is a chance that I have a fundamental flaw or an irreconcilable issue with the way I&#8217;m thinking of implementing it, and I might have to redo the approach completely from scratch. But, as of today, I do have a working prototype and am looking for feedback on it!</p><p>Unfortunately, it is unlikely that breaking changes to existing codebases using RIBs can be avoided, but I think I was able to get them to a minimum.</p><p>Fundamentally, there are just two things that need to be done to satisfy the new Swift Concurrency strict rules:</p><ol><li><p><strong>UI/View Layer is MainActor Isolated:</strong> Everything that touches the UI/view layer (<code>Presenter</code>, <code>ViewableRouter</code>, <code>ViewController</code>, or parts of them) must be explicitly marked with <code>@MainActor</code> to indicate that they call methods to manipulate and re-render the UI on the main thread.</p></li><li><p><strong>Business Logic is Nonisolated:</strong> Everything else is inherently asynchronous and needs to be marked as such with <code>nonisolated</code> (or other directives/protocols/keywords).</p></li></ol><p>Along with those two changes, I added a couple of helper methods to the <code>PresentableInteractor</code> and the <code>ViewableRouter</code> to automate some of the boilerplate related to calling presenter methods or UI navigation methods on the main actor.</p><p>One thing that I haven&#8217;t added yet, but will likely add soon to this PR, is a convenience/automation/helper method/s to run async functions in the interactor and automatically create a wrapping <code>Task</code> and an Rx observable for it.</p><h2>Looking for Feedback!</h2><p>I&#8217;m looking for feedback on my PR. You don&#8217;t have to already work with RIBs or be an expert in Swift Concurrency, but I&#8217;d appreciate a second pair of eyes from someone who spent more time using Swift Concurrency than me. Perhaps I missed something critical there...</p><p>Also, how are the ergonomics of creating a new Swift 6.2 project and integrating this POC of the framework into it? How are the ergonomics and the experience updating an existing project using RIBs?</p><p>In the coming weeks, I&#8217;ll be testing all of that myself in new sample projects, in one of my personal projects, and in production at scale at work.</p><h2>Original PR Submission</h2><p>Here I&#8217;m posting the content of my POC <a href="https://github.com/uber/RIBs-iOS/pull/45">PR submission</a> that might add a bit more of a context and detail to the changes I made.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><div><hr></div><div><hr></div><h1>Swift Concurrency/Isolation Support</h1><p>This is a draft PR to introduce Swift Concurrency Isolation support in RIBs. It is a functioning prototype but it needs thorough testing real codebases. I&#8217;ll be testing it in one of our production codebases and also in all of my personal projects. But, I&#8217;d appreciate volunteers to try this out and give us feedback. You can find sample usage of this change pointing to my fork that constitutes this PR here <a href="https://github.com/alexvbush/RIBsSample1">https://github.com/alexvbush/RIBsSample1</a></p><p>This change is related to <a href="https://github.com/uber/RIBs-iOS/issues/6">https://github.com/uber/RIBs-iOS/issues/6</a> and <a href="https://github.com/uber/RIBs-iOS/issues/43">https://github.com/uber/RIBs-iOS/issues/43</a></p><p>Overall, I&#8217;m still not able to make it work without breaking changes, but I believe I brought them down to a minimum.</p><p>This is still a WIP and I am open for feedback to make changes to this solution.</p><h2>The Isolation and Threading Issues</h2><p>Fundamentally, everything in RIBs can be async and run on different threads, and you as a developer need to manage concurrent access to the data in RIBs on your own. Historically it&#8217;s been done via streams RxSwift Observables, Subjects, etc. and in combination with the helper methods such as func disposeOnDeactivate(interactor: Interactor) -&gt; Disposable it works pretty well out of the box. The only thing you&#8217;d really need to worry about was running your stream/observable observation callbacks on the main thread when you&#8217;d need to render the UI, aka call methods on your RIB&#8217;s presenter. This would normally be achieved by adding .observe(on: MainScheduler.instance) call onto observables so that their subscribe next callback block executes on the main thread. It works fine but requires developer discipline to remember to add.</p><p>The same applies to controlling multi-threaded access to data across your RIBs app - it is up to you how to implement it, but RxSwift that comes out of the box gives you a lot of tools and flexibility to do it.</p><p>With the new Swift Concurrency rules and compiler errors and warnings a lot of these things are either more explicit or attempted to be inferred and resolved at compile time.</p><p>If RIBs are used in projects with strict swift concurrency isolation enabled, then as it stands today one would experience a lot of compiler warnings about breaking isolation constraints and related issues such as these:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!V-Zt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!V-Zt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 424w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 848w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 1272w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!V-Zt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png" width="1456" height="541" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:541,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image" title="Image" srcset="https://substackcdn.com/image/fetch/$s_!V-Zt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 424w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 848w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 1272w, https://substackcdn.com/image/fetch/$s_!V-Zt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F187f10d0-039c-42d2-94d1-44e097c46940_1548x575.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x-da!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x-da!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 424w, https://substackcdn.com/image/fetch/$s_!x-da!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 848w, https://substackcdn.com/image/fetch/$s_!x-da!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 1272w, https://substackcdn.com/image/fetch/$s_!x-da!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x-da!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png" width="1456" height="173" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:173,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 6 29 42 PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 6 29 42 PM" title="Screenshot 2025-10-18 at 6 29 42 PM" srcset="https://substackcdn.com/image/fetch/$s_!x-da!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 424w, https://substackcdn.com/image/fetch/$s_!x-da!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 848w, https://substackcdn.com/image/fetch/$s_!x-da!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 1272w, https://substackcdn.com/image/fetch/$s_!x-da!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7412da7e-8879-492e-9c03-5ad1b7e068d2_1517x180.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Ultimately the compiler is trying to be helpful by enforcing stricter concurrency but it doesn&#8217;t know/can&#8217;t infer the RIBs ways of ensuring safe concurrent access to data via streams and Rx observables. This leads to warnings and compiler issues that are either excessive or incorrect/not-needed in the context of RIBs.</p><h2>Isolation Changes</h2><p>In order to fix the compiler issues with the new strict swift concurrency we have to modify and explicitly state what kind of access each class or interface in RIBs should have.</p><p>Almost every class or protocol was marked as nonisolated and everything else that touches the UI aka the main thread was marked with @MainActor:</p><ol><li><p>Presentable protocol and by extension Presenter class and its subclasses</p></li><li><p>LaunchRouting and its func launch(from window: UIWindow)</p></li><li><p>ViewControllable since its the one referring to UIViewController instances</p></li></ol><p>Everything else, such as components, interactors, builders, routers, etc., was marked as nonisolated since it&#8217;s explicitly up to the developer to deal with concurrency there.</p><p>Overall those are all the fundamental the changes. But they have some implications and breaking changes.</p><h2>What are the implications of all of these changes?</h2><p>Well, this now means that calls to presenter methods, either directly or in callback blocks from Rx or async/await tasks, will result in the following error: Call to main actor-isolated instance method &#8216;presentStuff()&#8217; in a synchronous nonisolated context.</p><p>This is a good thing and is what strict swift concurrency rules intend to help with. This will warn you if you are trying to call presenter methods from potentially not the main thread, this is forcing you to explicitly call them on the main thread which would reduce a lot of potential runtime errors.</p><p>The same type of warning occurs when we try to call methods on UIViewController instances while navigating the UI during viewable RIBs routing. Since router is nonisolated, and routing can actually be triggered from different threads, especially for headless routers, the only thing that really needs to be executed on the main thread is only the UI manipulation part which is the navigation between view controllers.</p><h3>How should we call presenter methods now?</h3><p>In the current incarnation (and keep in mind, I&#8217;m still exploring the solutions here) you have two options to properly call presenter methods on the main thread:</p><ol><li><p>declare the presenter as nonisolated(unsafe) let presenter = self.presenter inline and then call your presenter methods wrapped in a Task { @MainActor in }.</p></li><li><p>use a new convenience method on PresentableInteractor that automates this for you.</p></li></ol><p>Technically to make it work you could manually set up presenter inline as nonisolated(unsafe) local variable and then call it methods on it in Task { @MainActor in }. Since this would quickly become tedious, I added a helper method presentOnMainThread on PresentableInteractor that will do this for you. It can be called safely from an RxSwift observable callback or from a Task.</p><h3>What should we do in routes now?</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0V47!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0V47!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 424w, https://substackcdn.com/image/fetch/$s_!0V47!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 848w, https://substackcdn.com/image/fetch/$s_!0V47!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 1272w, https://substackcdn.com/image/fetch/$s_!0V47!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0V47!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png" width="724" height="254" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8544aded-158f-46bb-b405-761d55f93692_724x254.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:254,&quot;width&quot;:724,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 6 53 41&#8239;PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 6 53 41&#8239;PM" title="Screenshot 2025-10-18 at 6 53 41&#8239;PM" srcset="https://substackcdn.com/image/fetch/$s_!0V47!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 424w, https://substackcdn.com/image/fetch/$s_!0V47!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 848w, https://substackcdn.com/image/fetch/$s_!0V47!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 1272w, https://substackcdn.com/image/fetch/$s_!0V47!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8544aded-158f-46bb-b405-761d55f93692_724x254.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fO9p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fO9p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 424w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 848w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 1272w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fO9p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png" width="488" height="232" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:232,&quot;width&quot;:488,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 7 18 41&#8239;PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 7 18 41&#8239;PM" title="Screenshot 2025-10-18 at 7 18 41&#8239;PM" srcset="https://substackcdn.com/image/fetch/$s_!fO9p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 424w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 848w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 1272w, https://substackcdn.com/image/fetch/$s_!fO9p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba3dd196-61ec-48c3-961b-f9f4df153343_488x232.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Uy6i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Uy6i!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 424w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 848w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 1272w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Uy6i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png" width="535" height="392" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e4fb31b-41ea-4acc-9996-725326263643_535x392.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:392,&quot;width&quot;:535,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 6 56 20 PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 6 56 20 PM" title="Screenshot 2025-10-18 at 6 56 20 PM" srcset="https://substackcdn.com/image/fetch/$s_!Uy6i!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 424w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 848w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 1272w, https://substackcdn.com/image/fetch/$s_!Uy6i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e4fb31b-41ea-4acc-9996-725326263643_535x392.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Same as with the presenter, routing now has to slightly adjust to explicitly call things on the main thread when necessary.</p><p>Just like with the presenter you can either do it manually:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D2GA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D2GA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 424w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 848w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 1272w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D2GA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png" width="1397" height="236" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:236,&quot;width&quot;:1397,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 7 02 10 PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 7 02 10 PM" title="Screenshot 2025-10-18 at 7 02 10 PM" srcset="https://substackcdn.com/image/fetch/$s_!D2GA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 424w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 848w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 1272w, https://substackcdn.com/image/fetch/$s_!D2GA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff06051f5-17ab-43bb-9378-c4f1797fd722_1397x236.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Or you can use a new automation built into the ViewableRouter:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hv-I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hv-I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 424w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 848w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 1272w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hv-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png" width="1106" height="161" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:161,&quot;width&quot;:1106,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Screenshot 2025-10-18 at 7 02 42 PM&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Screenshot 2025-10-18 at 7 02 42 PM" title="Screenshot 2025-10-18 at 7 02 42 PM" srcset="https://substackcdn.com/image/fetch/$s_!Hv-I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 424w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 848w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 1272w, https://substackcdn.com/image/fetch/$s_!Hv-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcd5138f-3f51-40a1-8980-46f2ba4b78d8_1106x161.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Other Implications</h2><p>There are also other minor implications of these changes, such as Presentable interfaces having an associatedtype Listener and a nonisolated var listener: Listener? { get set } property declaration. The change is minor but breaking unfortunately, I am still exploring the ways to avoid this.</p><h1>Feedback Needed!!!</h1><p>This is an early work in progress exploration of how Swift Concurrency can work seamlessly with RIBs. There are a few more things that need to be done such as Task/async/await helper methods support that I&#8217;ll add a bit later.</p><p>In the meantime I am looking for feedback - please pull this PR or use the branch in my fork of this repo <a href="https://github.com/alexvbush/RIBs-iOS/tree/swift-concurrency-isolation">https://github.com/alexvbush/RIBs-iOS/tree/swift-concurrency-isolation</a> and test out these changes in your own projects. Let me know what works and what doesn&#8217;t. There will be compiler issues and you&#8217;d have to tweak and migrate a few things but I hope it&#8217;s not too many. Let me know what you had to migrate and I&#8217;ll try to add helpful tutorial/readmes based on your input. And also checkout my sample project where I applied my fork&#8217;s branch to a sample RIBs app: <a href="https://github.com/alexvbush/RIBsSample1">https://github.com/alexvbush/RIBsSample1</a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share The Mobile Engineer&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share The Mobile Engineer</span></a></p>]]></content:encoded></item><item><title><![CDATA[[RIBs] Update: I’m investigating Swift 6.2 concurrency isolation in RIBs ]]></title><description><![CDATA[Prepping a framework for Swift Concurrency is not as trivial as it might seem. It&#8217;s hard to strike a balance between the new features and breaking changes.]]></description><link>https://newsletter.mobileengineer.io/p/ribs-update-im-investigating-swift</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/ribs-update-im-investigating-swift</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Tue, 07 Oct 2025 11:03:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uCS4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I&#8217;ve been working on RIBs modernization and specifically on Swift 6.2 support in RIBs. There was a issue raised in the RIBs repo <a href="https://github.com/uber/RIBs-iOS/issues/43">https://github.com/uber/RIBs-iOS/issues/43</a> asking about some of the warnings associated with Main actor-isolated property &#8216;listener&#8217; cannot be used to satisfy <em>nonisolated</em> protocol requirement or similar warning.</p><p>This compiler warning (or compiler error, depending on how severe you set your settings) occurs when the framework is used in a Swift 6.2 project where the default concurrency isolation has changed. Essentially, now, if you do not explicitly specify otherwise, if your code is called from the main thread, from things like UIKit&#8217;s view controllers or from SwiftUI, you will get a bunch of warnings that you shouldn&#8217;t be doing so.</p><p>RIBs is a comprehensive architecture and framework and it addresses or touches all the high level aspects and layers of your application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uCS4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uCS4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 424w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 848w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 1272w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uCS4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png" width="1456" height="1371" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1371,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uCS4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 424w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 848w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 1272w, https://substackcdn.com/image/fetch/$s_!uCS4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef78eed6-5dd5-4974-ac10-c0942828c053_2048x1928.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>There are a lot of parts to it, but at the end of the day, things that need to be on the main thread are those that directly touch UI. Basically it&#8217;s the view layer composed of the view objects (UIViews, SwiftUI views, UIViewControllers, etc.) and presenters (ViewController in most cases or a standalone Presenter object implementing Presentable interface) + some aspects of Routing/Routers that touch the view for navigation (specifically ViewableRouter and not the Router).</p><p>Now, the theory is that we can just mark the things we need to be on the main thread with <em>@MainActor</em> and that&#8217;s it, where everything would just magically compile and give you appropriate compiler time warnings. But in reality, it is of course much more complicated and nuanced than that, especially with a framework that has high adoption and usage and is mission critical. To add Swift 6.2 concurrency isolation support, we want to make the minimum amount of changes and hopefully not introduce any breaking changes or compiler warnings (or a minimum amount of those) to the users of the framework as the migration could be a nightmare.</p><p>So, this is why I&#8217;ve been heads down figuring out how stricter Swift 6.2 compiler time rules for concurrency actually work in practice and what implications they have for RIBs framework and codebases that use it.</p><p>So far, marking things explicitly, where needed with <em>nonisolated</em> and <em>@MainActor</em> seems to be working&#8230; but I need to do more thorough testing. I&#8217;m in the beginning of my research so I don&#8217;t have all the answers yet but I&#8217;ll keep everyone posted on the findings.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/ribs-update-im-investigating-swift?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/ribs-update-im-investigating-swift?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/ribs-update-im-investigating-swift?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[[Book Update] How does Thread class work in Swift?]]></title><description><![CDATA[Threads are, for the most part, not used directly for app development on iOS anymore. But, sometimes, it comes up as a question on iOS interviews. This is an excerpt from the 2nd edition of my book.]]></description><link>https://newsletter.mobileengineer.io/p/book-update-how-does-thread-class</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/book-update-how-does-thread-class</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 08 Sep 2025 11:00:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><p><em>This is an excerpt from the upcoming 2nd edition of my book <a href="https://iosinterviewguide.com/">The iOS Interview Guide</a>. If you&#8217;re interested to hear more book updates like this and to receive draft chapters as I write them feel free to subscribe to my substack, I&#8217;ll post all the updates here.</em></p><div><hr></div><h2>How does Thread class work in Swift?</h2><p>\label{question:swift_threads}</p><p>This is a low-level question designed to gauge a candidate's understanding of the fundamental building blocks of concurrency on Apple's platforms. Even though direct thread management is rare in modern iOS development, knowing how it works provides important historical context for appreciating higher-level abstractions like Grand Central Dispatch (GCD) and Swift Concurrency.</p><p><strong>Expected Answer:</strong> The Thread class is a low-level, object-oriented way to manage a single thread of execution directly. It's part of the Foundation framework and provides you with manual control over creating, starting, and managing a thread's lifecycle.</p><p>You can create a new Thread object by passing it a closure or a selector and a target to execute on a separate thread. Another option is to subclass the Thread class, override the main() method in it and put the code you wish to be executed on another thread there.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><code>let thread1 = Thread {</code></p><p><code>    print("This is running on a background thread.")</code></p><p><code>}</code></p><p><code>let thread2 = Thread(target: self, selector: #selector(doWork), object: nil)</code></p><p><code>thread1.start()</code></p><p><code>thread2.start()</code></p><p><code><br>final class MyThread: Thread {</code></p><p><code>    override func main() {</code></p><p><code>        print("Executing on a custom thread subclass.")</code></p><p><code>    }</code></p><p><code>}</code></p><p><code>let myThread = MyThread()</code></p><p><code>myThread.start()</code></p><p>A Thread instance's lifecycle is managed manually with the following methods:</p><ul><li><p>start(): begins the execution of the thread.</p></li><li><p>cancel(): signals to the thread that it should exit. This is a <strong>cooperative </strong>mechanism (just like in Swift Concurrency); the code running on the thread must periodically check the isCancelled property and handle termination gracefully.</p></li><li><p>sleep(for:): Pauses the current thread for a specified time interval.</p></li></ul><p>These days Thread is considered a legacy, low-level, solution that is most likely not what you want to go for in iOS application development. Instead, you should use more modern solutions that are built on top of the low-level Threads such as Grand Central Dispatch (GCD) and Swift Concurrency. Both of these solutions help you interact with Threads at a higher level of abstraction, where the system will manage the actual threads your work is executed on, rather than you having to manage it yourself manually. These solutions help you avoid many problems you&#8217;d encounter working with threads directly, such as:</p><ul><li><p><strong>Data Races:</strong> Thread has no built-in protection for shared mutable state. You are entirely responsible for implementing synchronization mechanisms like locks (NSLock) or mutexes, which are difficult to use correctly and can lead to data races or deadlocks. Swift's actors solve this at the language level, providing compile-time safety.</p></li><li><p><strong>Thread Explosion:</strong> Creating too many threads can overwhelm the system, leading to excessive memory consumption and context-switching overhead, which harms performance. GCD and Swift Concurrency manage a limited pool of worker threads much more efficiently.</p></li><li><p><strong>Lifecycle Management:</strong> Manually managing the lifecycle of each thread is complex. Forgetting to terminate a thread or handle its cancellation can lead to resource leaks. Structured concurrency provides clear, scoped lifetimes for tasks, which automatically handle cleanup and cancellation propagation.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>Red Flags:</strong></p><ul><li><p>Advocating for the use of Thread for new application-level features.</p></li><li><p>Failing to identify the significant risks associated with manual thread management (, such as data races and deadlocks).</p></li><li><p>Not being aware of modern alternatives like GCD and, more importantly, Swift Concurrency with async/await and actors.</p><p></p></li></ul><div><hr></div><p><em>Help me make this chapter better.</em></p><p><em>What did I miss? Anything to add to make it more clear or helpful?</em></p><p><em>Your feedback directly helps shape this book into a more practical resource for the entire iOS community. Drop a comment or send me an email.</em></p><div><hr></div><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-how-does-thread-class?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">This is the kind of real-world insight that can make a difference in a tough interview. Share this article with a fellow iOS developer who's on the job hunt to help them ace their next interview.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-how-does-thread-class?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/book-update-how-does-thread-class?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Bumping RIBs dependencies and migrating issues from the old repo]]></title><description><![CDATA[Last week, I submitted a PR for RIBs to bump the minimum iOS requirement to iOS 15 and the RxSwift version to 6.9.0. I also started moving all the iOS issues from the old repository to the new one.]]></description><link>https://newsletter.mobileengineer.io/p/bumping-ribs-dependencies-and-migrating</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/bumping-ribs-dependencies-and-migrating</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Tue, 02 Sep 2025 11:00:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I&#8217;ve been laying the groundwork to add SwiftUI and Swift Concurrency support to the RIBs iOS framework. I<a href="https://github.com/uber/RIBs-iOS/pull/26"> submitted a PR</a> bumping the minimum iOS requirement for the library to iOS 15 and RxSwift dependency to 6.x.x (below version 7, 6.9.0 currently).</p><p>The iOS version bump is many versions behind the latest iOS release to help with backwards compatibility but it is necessary to have decent base support for SwiftUI and async/await.</p><p>The RxSwift update is also part of this effort, so that extensions and mappers between Observables and async/await can be used out of the box from the Rx library instead of being rebuilt within RIBs.</p><p>I also moved all of the <a href="https://github.com/uber/RIBs/issues?q=is%3Aissue%20state%3Aopen%20label%3AiOS">iOS issues</a> from the original RIBs repo into <a href="https://github.com/uber/RIBs-iOS/issues">the new one</a>. I will start replying and addressing them as soon as I can. Let me know if I missed anything or if you have any questions!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[[Book Update] What is Actor and Sendable in Swift?]]></title><description><![CDATA[These days Swift Concurrency is all the rage and you'd definitely get asked about it on iOS dev interviews. This is an excerpt from The iOS Interview Guide V2 of a question about Actors and Sendable.]]></description><link>https://newsletter.mobileengineer.io/p/book-update-what-is-actor-and-sendable</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/book-update-what-is-actor-and-sendable</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 25 Aug 2025 11:02:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><p><em>This is an excerpt from the upcoming 2nd edition of my book <a href="https://iosinterviewguide.com/">The iOS Interview Guide</a>. If you&#8217;re interested to hear more book updates like this and to receive draft chapters as I write them feel free to subscribe to my substack, I&#8217;ll post all the updates here.</em></p><div><hr></div><h2>What is Actor and Sendable in Swift?</h2><p>This question will come up as part of discussion around asynchronicity on iOS and as conversation goes deeper into the topic of Swift Concurrency. In Swift Concurrency, async/await is the means of defining and executing async work, while actor is the means of protecting data from race conditions and other async data access/read problems. As an iOS engineer you need to know how to work with actors (and their advantages, limitations, etc.) and you need to know how their features compare to other solutions such as GCD, RxSwift, or alternatives in other languages such as Coroutines in Kotlin.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>Expected Answer:</strong> Swift Concurrency introduced a new type called actor. It behaves and acts much like class type. It&#8217;s a reference type, but its main purpose is to protect access to its mutable state from concurrent access by different threads. This helps protect data access to the actor's state and helps prevent data races by enforcing static checks at compile time. This process is called <em>actor isolation</em>.</p><p>Actor isolation is a &#8220;barrier&#8221;, so to speak, of access to actor&#8217;s properties data. Essentially, in order to access data on an actor externally, read its properties or write to them or call methods that mutate its state, you have to use the await keyword and access them asynchronously. Internally, the system will queue up access to data and mutations, processing them one at a time. This serializes access, preventing race conditions and ensuring data synchronization. This isolation is enforced at compile time giving developers an early warning.</p><p>Internal data access in actors can happen in a &#8220;normal&#8221; synchronous way and doesn&#8217;t require an await call.</p><h3>Sendable</h3><p>To maintain isolation, any data passed into an actor's methods or returned from them must be safe to share across threads. Types that are safe to share have to conform to the Sendable protocol. Marking something as Sendable, you guarantee to the compiler that it&#8217;s safe to transfer that value across threads.<br><br>Sendable protocol is akin to Codable in a sense that in order to have a type conform to it, every property of that type needs to conform to Sendable as well. Primitive types conform to it by default, and structs and enums as well, as long as all of their properties are other primitives or types conforming to Sendable. You can also make normal class types conform to Sendable, but you&#8217;ll have to manually manage access and mutability of their own properties using locks or other thread-safe mechanisms.</p><p>By default, everything is isolated in an actor. If you want to explicitly opt-out of the default isolation for a property or a function, you&#8217;d need to mark it with the non-isolated keyword. You would opt-out these functions or properties in an actor that are not mutating any state - since they don't mutate any state, they can be accessed safely synchronously and do not need to be put on an asynchronous queue with await.</p><h3>Actor Reentrancy</h3><p>One important thing to remember about actors is the actor's reentrancy behavior. When one message is sent to an actor object and is awaited on, it doesn&#8217;t mean that the actor becomes exclusively locked and will only execute other messages after the first request is fully processed. Instead, while one await call is suspended, another message can be processed by the actor and potentially finish sooner than the first one. This solves the problem of deadlocks that could otherwise occur where one actor waits on the execution to finish from another actor, and the other, in turn, waits on the execution of the first one to finish, therefore causing a never-ending deadlock.</p><p>The implication of this, though, is that you can never be sure if the state of the actor hasn&#8217;t changed between before and after you call an await method on it. You will have to check the data after suspension finishes to make sure it&#8217;s still in the correct state.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Prior Solutions</h3><p>Prior to actors, using GCD (Grand Central Dispatch), we had to create a private serial dispatch queue to achieve a synchronization lock that allowed only one operation at a time to access data.</p><p>With RxSwift you&#8217;d use something like BehaviorSubject with SerialDispatchQueueScheduler with a similar setup as GCD - SerialDispatchQueueScheduler will ensure that there is a synchronization lock as long as you perform your operations on that scheduler.</p><p>Either GCD or RxSwift solutions required developer&#8217;s discipline to implement accurately, with Swift Concurrency&#8217;s actors the compiler aids in identifying isolation violation and potential race conditions it could lead to.</p><p>In other languages like Kotlin, you&#8217;d use Mutex and/or StateFlow, but there is no direct compile time safe equivalent to actors.</p><p><strong>Red Flags:</strong></p><ul><li><p><strong>Stating actors are </strong><em><strong>always</strong></em><strong> single-threaded or non-reentrant:</strong> This is a major misunderstanding. While an actor processes messages serially, it <em>is</em> reentrant. An interviewer might ask a follow-up question specifically designed to test for this misunderstanding.</p></li><li><p><strong>Believing nonisolated moves code off the actor:</strong> Misconception that annotating a method with nonisolated will automatically run it on a background thread. nonisolated simply removes actor isolation; it doesn't dictate the execution context. The method will run on the <em>caller's</em> context.</p></li><li><p><strong>Saying Sendable for classes is easy:</strong> If the candidate suggests that making any mutable class Sendable is straightforward, it indicates a lack of understanding of the complexities of thread-safe mutable shared state and the implications of @unchecked Sendable.</p></li><li><p><strong>Confusing async with concurrency:</strong> While async functions <em>enable</em> concurrency, they don't inherently run on a background thread. They only mark potential suspension points. The actual thread hopping for background work is handled by the runtime or by explicitly offloading work (e.g., to an actor or another Task).</p></li><li><p><strong>Inability to explain </strong><em><strong>why</strong></em><strong> actors are safer than GCD/RxSwift for state:</strong> A strong answer shouldn't just list alternatives but articulate <em>how</em> actors offer a compiler-enforced safety mechanism that manual GCD queues or RxSwift schedulers do not for state isolation.</p></li></ul><div><hr></div><p><em>Help me make this chapter better.</em></p><p><em>What did I miss? Anything to add to make it more clear or helpful?</em></p><p><em>Your feedback directly helps shape this book into a more practical resource for the entire iOS community. Drop a comment or send me an email.</em></p><div><hr></div><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-what-is-actor-and-sendable?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">This is the kind of real-world insight that can make a difference in a tough interview. Share this article with a fellow iOS developer who's on the job hunt to help them ace their next interview.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/book-update-what-is-actor-and-sendable?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/book-update-what-is-actor-and-sendable?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[I am officially the primary maintainer of Uber’s RIBs iOS GitHub Repository]]></title><description><![CDATA[I&#8217;m excited to help maintain and support this wonderful architecture!]]></description><link>https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 11 Aug 2025 11:01:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-aP4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-aP4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-aP4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 424w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 848w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 1272w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-aP4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png" width="733" height="320" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:320,&quot;width&quot;:733,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-aP4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 424w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 848w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 1272w, https://substackcdn.com/image/fetch/$s_!-aP4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e309f32-2532-4b8b-b619-7e00834576a3_733x320.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://github.com/uber/RIBs-iOS">RIBs</a> is a cross-platform architecture Uber came up with to power their mobile apps back in 2016.</p><p>I had the pleasure of learning about and working with this architecture during my time at Uber. I was sold on its modular and composable nature, clear separation of layers, and deep Clean Architecture roots.</p><p>Since then, it&#8217;s been my favorite architecture for mobile apps and I&#8217;ve used it for many years in personal and work projects applying it to codebases big and small.</p><p>Lately, I volunteered to help with maintaining the repo and I am now the <a href="https://github.com/uber/RIBs-iOS/issues/20">official primary maintainer</a>!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What is RIBs?</h2><p>RIBs is an acronym that gives the name to the smallest logical piece of this architecture, a RIB, a group of 3 classes: Router, Interactor, Builder.</p><p>In RIBs architecture, your entire app consists of a tree structure of RIBs where each RIB represents a business scope of your app and can have zero or many children.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!maQM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!maQM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 424w, https://substackcdn.com/image/fetch/$s_!maQM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 848w, https://substackcdn.com/image/fetch/$s_!maQM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 1272w, https://substackcdn.com/image/fetch/$s_!maQM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!maQM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png" width="1456" height="1391" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1391,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!maQM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 424w, https://substackcdn.com/image/fetch/$s_!maQM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 848w, https://substackcdn.com/image/fetch/$s_!maQM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 1272w, https://substackcdn.com/image/fetch/$s_!maQM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47249019-f27c-4faa-83c2-921a77adf97f_1600x1529.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As the user interacts with the app and business logic demands, some RIBs will be instantiated and routed to and added to the RIBs tree, and some will be routed away from, deallocated, and detached from the RIBs tree.</p><p>This approach makes it very easy to break down your app into business scopes with single responsibility and logic completely isolated from each other. This way, a team can work on a subset of RIBs within a larger application and have their own &#8220;sandbox&#8221; in them and change whatever it wants without risking breaking other RIBs across the app.</p><p>The uniqueness of this architecture is two-fold:</p><ol><li><p>it&#8217;s a complete end-to-end solution that not only has an architecture and a set of guidelines but also a framework to enforce structure (unlike other architectures such as MVVM, MV*, VIPER, etc. which are all essentially just sets of loose guidelines).</p></li></ol><ol start="2"><li><p>RIBs is business logic driven, not view/UI-driven. Pretty much every other mobile architecture is view-driven where the architecture itself and the application cannot exist without a view and the view is the very first and smallest component of the architecture. In RIBs, each RIB can exist by itself (aka be headless) without a view or can optionally have a view. UI updates and anything else in the view layer is driven by the business logic in the RIB rather than the other way around. It is a very subtle but profound difference that makes this architecture much more flexible and superior to any other client side architecture out there.</p></li></ol><p>That said, of course, most of the RIBs will have an associated view that they will manipulate:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vEyC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vEyC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 424w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 848w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 1272w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vEyC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png" width="1456" height="549" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:549,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vEyC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 424w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 848w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 1272w, https://substackcdn.com/image/fetch/$s_!vEyC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1e9849-e58b-4add-9f1e-7fc5b3fef3e4_1600x603.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There is much more that I can say about this wonderful architecture but I&#8217;ll do it in followup posts in the future. If you want to learn more about it, here&#8217;s a list of other resources that might help:</p><ul><li><p><a href="https://www.youtube.com/watch?v=khnbx-OIiyU">RIBs. The Best iOS Architecture.</a></p></li><li><p><a href="https://insideiosdev.com/episodes/alex-on-ubers-rib-architecture-2d362fd3">https://insideiosdev.com/episodes/alex-on-ubers-rib-architecture-2d362fd3</a></p></li><li><p><a href="https://www.uber.com/blog/new-rider-app-architecture">https://www.uber.com/blog/new-rider-app-architecture</a></p></li><li><p><a href="https://youtube.com/watch?v=FfwZSk6VRVY">RIBs - Uber's new mobile architecture that scales to hundreds of engineers by Tuomas Artman</a></p></li><li><p><a href="https://youtube.com/watch?v=bB9e7ZYVYCo">[Uber Mobility] Architecture Rewrite - Tuomas Artman</a></p></li><li><p><a href="https://youtube.com/watch?v=LWu1HcFIRnQ">[Uber Mobility] Deep Scope Hierarchies - Tony Cosentini</a></p></li><li><p><a href="https://www.youtube.com/watch?v=Q5cTT0M0YXg">[Uber Mobility] RIB (Router Interactor Builder) - Yi Wang</a></p></li><li><p><a href="https://youtube.com/watch?v=vIdsYLXClZs">[Uber Mobility] Plugins - Brian Attwell</a></p></li></ul><p>A few years back, I started to make a video course about it, so if you&#8217;re interested you can check it out <a href="https://alexbush.podia.com/ribs-architecture-on-ios">here</a>, it&#8217;s free!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What are the plans for RIBs?</h2><p>Since RIBs is a cross platform architecture at its core, it originally was implemented and published with Android and iOS combined together, all in <a href="https://github.com/uber/RIBs">one repo</a>. Since there have been more developments happening in the Android implementation version iOS lately, Uber decided to split them out and give the iOS implementation <a href="https://github.com/uber/RIBs-iOS/">its own github repo</a>.</p><p>There are lots of <a href="https://github.com/uber/RIBs/issues?q=is%3Aissue%20state%3Aopen%20label%3AiOS">outstanding issues and questions</a> about the iOS framework in the original repo. I&#8217;ll start slowly moving those to the new repo and will try to help resolve them and answer questions as best as I can.</p><p>There is some modernization that needs to happen to the framework:</p><ol><li><p>Add SwiftUI support</p></li><li><p>Add Swift Concurrency (async/await) support</p></li></ol><p>While RIBs technically can support both out of the box, <a href="https://newsletter.mobileengineer.io/p/ribs-swiftui-uikit-navigation-with">SwiftUI with UIHostingController</a>, and async/await by wrapping it into a RxSwift Single or Observable, it would be nice to have helpers and convenience methods around it built-into the framework itself to make the integration smoother.</p><p>For anyone who&#8217;s transitioning and migrating existing codebases to RIBs, it would be nice to have some convenient APIs to provide that functionality for legacy code integration. I have encountered these issues myself in integrating RIBs into existing codebases - there needs to be some sort of support of attaching RIBs outside of RIBs tree hierarchy, to something like a legacy UIViewController. However, it&#8217;s not applicable to anyone, who, like Uber, starts from scratch and builds their entire application with RIBs from the getgo. For anyone who&#8217;s transitioning and migrating existing codebase to RIBs it is much more practical to start with leaf RIBs rather than the root and work your way up the tree. It is also technically achievable today with the current framework but you&#8217;d need to know the internal mechanics of how a RIB is attached to a RIBs tree and then replicate the same actions adding one to a view controller.</p><p>As I have other commitments like my ongoing work on <a href="https://iosinterviewguide.com/">my book</a>, there is no timeline on these improvements, but I&#8217;ll try to add them as soon as I can and will post on substack about the progress.</p><h2>Conclusion</h2><p>I encourage everyone to try this fantastic architecture. It truly has been the biggest unlock for me for scalable application development!</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/i-am-officially-the-primary-maintainer?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Mobile Architecture at UpKeep Part 1: KMP & Clean Architecture]]></title><description><![CDATA[Mobile app architecture (iOS/Android) at UpKeep - Kotlin Multiplatform for code reusability + RIBs (Router Interactor Builder) for scalability. Part 1 of a 2-part series on iOS architecture at UpKeep.]]></description><link>https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Mon, 21 Jul 2025 11:02:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!c_3Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c_3Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c_3Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 424w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 848w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c_3Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png" width="1456" height="1456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abc985e4-c464-459f-9193-a7af708431a6_1600x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c_3Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 424w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 848w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!c_3Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabc985e4-c464-459f-9193-a7af708431a6_1600x1600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At <a href="https://upkeep.com/">UpKeep</a> we have a small mobile team that supports a rich feature set of our CMMS application. We have native mobile apps for iOS and Android and the main users are technicians in the field performing maintenance work on assets in various facilities such as factories, warehouses, etc.</p><p>Both apps are native apps that utilize native platform capabilities. As the feature set of both apps grew over the years, it became increasingly difficult to avoid subtle (or not so subtle) discrepancies between the two native app implementations. Sometimes some bugs will creep in on one platform or the other or one platform would use slightly different data fetching or permission logic. All of these add up to a degraded user experience and an added cost of &#8220;synchronization&#8221; between two platforms, either at the development stage (where devs from both platforms need to be in sync) or at the later bug fixing stage (where Android and iOS devs would need to dig through the discrepancies and figure out which platform actually behaves correctly and which one does not). Ultimately, we always had to write the implementation twice in Kotlin and Swift (since the two apps have the same features and are supposed to behave the same way).</p><p>All of that led us to exploring cross platform and multiplatform solutions and we zeroed in on Kotlin Multiplatform (KMP) as our technology choice.</p><p>In this article we&#8217;ll cover:</p><ul><li><p>Advantages of adopting KMP as a code-sharing technology over other options</p></li><li><p>A high-level overview of the architecture of UpKeep&#8217;s mobile apps and the KMP integration</p></li><li><p>Our team&#8217;s learnings and path forward</p></li></ul><p>This is part 1 of a two-part series covering UpKeep&#8217;s mobile app architecture. This article will focus on the Kotlin Multiplatform side. In part 2 we&#8217;ll cover iOS application architecture in more detail.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Why KMP?</h2><h3>What is KMP?</h3><p><a href="https://www.jetbrains.com/kotlin-multiplatform/">KMP</a> is a technology that allows Kotlin code to be shared and run as native code on various platforms such as iOS, Android, web, desktop, etc. KMP allows developers to write common business logic once and share it across different platforms while keeping the platform-specific code in separate modules.</p><h3>What are the benefits?</h3><p>Here&#8217;s an excerpt from our internal documentation that we circulated in the mobile team to get buy-in prior to embarking on adopting KMP in our codebases.</p><p>KMP provides several benefits:</p><p><strong>Code Reusability (Unified Codebase):</strong> With KMP, you can write your business logic in a single codebase and share it across different platforms like Android, iOS, web, and desktop. This eliminates the need to duplicate efforts for each platform.</p><p><strong>Maintenance:</strong> Maintaining one set of business logic is easier and less error-prone than maintaining multiple versions for different platforms. Bug fixes and updates can be made in one place and immediately benefit all platforms.</p><p><strong>Consistent Behavior:</strong> Shared business logic ensures that the core functionality of your app behaves the same way on all platforms. This consistency improves the user experience as users get the same features and behavior regardless of the device they use.</p><p><strong>Consistent Testing:</strong> Automated tests can be written once for the shared code, ensuring that all platforms meet the same quality standards. This reduces the risk of platform-specific bugs and discrepancies.</p><p><strong>Reduced Development Time:</strong> Developing features and fixes once and deploying them across multiple platforms speeds up the development process. This allows developers to focus more on platform-specific enhancements and user experience rather than replicating business logic.</p><p><strong>Resource Utilization:</strong> Teams can be more flexible and efficient. Instead of having separate teams for each platform working on the same feature, a single team can develop the shared logic while smaller platform-specific teams integrate and enhance it.</p><p><strong>Maintaining Platform Independence &amp; Flexibility:</strong> While KMP promotes sharing code, it also allows for platform-specific code where necessary. This flexibility ensures that you can optimize and customize the user experience for each platform.</p><p><strong>Separation of Concerns:</strong> Platform-specific code can handle unique platform capabilities, UI components, and performance optimizations, while the shared code focuses on the business logic.</p><p><strong>General Architectural Forcing Function:</strong> In general, using KMP forces developers to think through the business logic as a separate concept, clearly separated from the rest of the code, which in turn makes architecture much more robust and flexible.</p><h2>What was the approach to roll it out?</h2><p>Since most of the team was pretty new to KMP, we decided to take it slow and first build out a demo application as a proof-of-concept (POC) of how KMP works and its benefits. Later on, after the team was happy with the results and talked through the POC codebase extensively, we integrated KMP into our main Android and iOS codebases.</p><p>Since KMP is flexible and can be used adhoc we first refactored an existing feature with KMP and rolled it out behind a feature flag. This way, there was no surprises on the expected behavior, we could clearly validate before and after impact (there would be no change which is good in this case), and we had total control over it at runtime and could shut down the KMP refactored code path at any point in time if it was not working properly.</p><p>After a successful first attempt, we now use KMP for every new feature we develop in our mobile applications.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Mobile Architecture At UpKeep</h2><h3>How much code to share?</h3><p>The introduction of KMP forced us to clearly articulate and define architectural boundaries in our mobile apps. The first discussion and decision that we made was how much code we should share between platforms.</p><p>These days, KMP offers a full end-to-end stack for code sharing that includes all the networking/service layer, storage layer, business logic, and UI code sharing. With KMP, you can choose to share as much or as little code as you want.</p><p>We decided to <strong>leverage the native UI on each platform</strong> to provide the best possible user experience. Therefore, we settled on <strong>sharing only the business logic</strong> code between iOS and Android for now. There is an asterisk to it though, which I&#8217;ll get into more later on.</p><h3>What is Business Logic?</h3><p>In a typical client side application there are two types of business logic:</p><ol><li><p>The business logic of orchestrating network requests and database queries based on user input and general serialization, data mapping and processing, etc.</p></li><li><p>Management of business scopes (and screens), navigational behavior, and overall application state (screen hierarchy) as the user navigates across the app</p></li></ol><p>It doesn&#8217;t matter what kind of architecture (or lack thereof) your app has, the business logic of your app will always encompass those two overall areas, regardless of whether it&#8217;s expressed explicitly or implicitly in the code.</p><p><strong>The first group of business logic</strong> can be depicted like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PwrT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PwrT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 424w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 848w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 1272w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PwrT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png" width="1456" height="956" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:956,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PwrT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 424w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 848w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 1272w, https://substackcdn.com/image/fetch/$s_!PwrT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b925952-8f9a-499f-a43f-2d660d61ed16_1525x1001.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>(Authentication Business Logic Flow is in green)</p></blockquote><p>Here, we are covering all the logic needed to make the &#8220;machinery&#8221; of the app work. After receiving user input to, for example, the login, the app needs to process and validate the input, form a proper network request to fulfill the backend API contract and issue the request. After that, the app needs to process the response, validate that it meets the contract expectations, serialize it, map it to domain models, and then, perhaps in case of a login, persist some of the response, such as session token, to disk.</p><p>All of these things are the &#8220;plumbing&#8221; but also the business logic every app has. These are not UI-related concerns.</p><p>The <strong>second group of business logic</strong> can be depicted like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!buZ-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!buZ-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 424w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 848w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 1272w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!buZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png" width="1456" height="1305" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1305,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!buZ-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 424w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 848w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 1272w, https://substackcdn.com/image/fetch/$s_!buZ-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4a993c0-78ab-427a-83b9-527badf9d471_1600x1434.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>(note grey is currently inactive application scopes/state)</p></blockquote><p>This is where we manage the overall application state, what screens go first and second as the user interacts with the app, where the user should be navigated to after login or tapping on a feature tab/item, etc.</p><p>It also controls when the complex business flows (such as the authentication business logic flow above) should be triggered based on user input or other events.</p><p>Note, when I say &#8220;application state,&#8221; I mean the state that needs to support the above logic, not all the UI state of what strings and images to display in a given screen (we&#8217;re talking overall application flow here).</p><p>This typically ends up being represented as a tree of business scopes or a tree of screens the application has. Everything starts with a root and then business logic decisions are made such as to route to a login screen if the user token is not present. Later, the business logic here decides to route to the main screen upon user login or other types of navigation. Or, after the user inputs some data in the UI, a use case is executed to actually perform a request on the user's behalf. All that decision making on where to route/navigate the user based on the overall application state and when to trigger more complicated login (such as use cases for submitting and retrieving data) is what&#8217;s encompassed in this second group of business logic.</p><h3>What part of business logic to share?</h3><p>Now, as we established what those business logic groups are, we had a hard decision to make - where to draw the line on sharing the code and the business logic. We ended up <strong>picking the first group to share</strong> for the following reasons:</p><ol><li><p>It&#8217;s a more clear-cut slice of mostly &#8220;functional&#8221; behavior with virtually no side effects (except occasional database persistence) that we can extract into use cases that are shared between two platforms. There would be no deviation or platform specific code or logic here except, perhaps, for the low-level specific details of data persistence and networking. But both of those concerns are fairly well established, abstracted out, and solved with current popular open source KMP libraries.</p></li><li><p>It would be ideal to share the second business logic group as well, but given that we have legacy code in our codebase with established and divergent from each other implementations between iOS and Android (iOS has some MVVMs but also a lot of RIBs, and Android is mainly on MVVM design pattern). We decided to not push it yet and not share the second group of business logic between our mobile apps yet. It will require more abstractions and more unification in the architecture of both apps to support this kind of code sharing. We are aiming for it at some point, though.</p></li></ol><h3>KMP Module Architecture: Clean Architecture</h3><p>So, what does the architecture look like for the shared code inside of KMP?</p><p>After establishing what slice of the business logic to share, we quickly narrowed down on using <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">Clean Architecture</a> for it - our shared codebase would be a collection of high level business Use Cases that can be used in and shared between our mobile apps.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4VuN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4VuN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 424w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 848w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 1272w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4VuN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png" width="772" height="567" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:567,&quot;width&quot;:772,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4VuN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 424w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 848w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 1272w, https://substackcdn.com/image/fetch/$s_!4VuN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cf8d255-1b59-4157-9878-c3aa0ef9a6d2_772x567.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s an example list of use cases we have:</p><ul><li><p><code>UpdateNotesUseCase</code></p></li><li><p><code>UploadPhotoUseCase</code></p></li><li><p><code>GetChecklistsAndTasksUseCase</code></p></li><li><p><code>DeleteAllPhotosUseCase</code></p></li><li><p><code>DeleteSignatureUseCase</code></p></li><li><p><code>UpdateTaskValueUseCase</code></p></li><li><p><code>GetWorkOrderActiveTimerUseCase</code></p></li><li><p><code>GetWorkOrderTemplateDetailsUseCase</code></p></li><li><p><code>GetWorkOrderTemplateListUseCase</code></p></li></ul><h4>LoginUseCase Example</h4><p>This is how our Authentication flow would work and look like for Login Screen:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GE56!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GE56!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 424w, https://substackcdn.com/image/fetch/$s_!GE56!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 848w, https://substackcdn.com/image/fetch/$s_!GE56!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 1272w, https://substackcdn.com/image/fetch/$s_!GE56!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GE56!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png" width="1456" height="863" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:863,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GE56!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 424w, https://substackcdn.com/image/fetch/$s_!GE56!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 848w, https://substackcdn.com/image/fetch/$s_!GE56!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 1272w, https://substackcdn.com/image/fetch/$s_!GE56!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3cde80b-205e-4123-b4d2-5eedc3b3433b_1600x948.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here we have an Interactor or a ViewModel start execution of a <code>LoginUseCase</code> to login the user. The <code>LoginUseCase</code> contains all the orchestration business logic.</p><p>The <code>LoginUseCase</code> in turn asks <code>AuthenticationRepository</code> to perform whatever necessary low level logic to actually authenticate and log in the user.</p><p>The <code>AuthenticationRepository</code> first has <code>AuthenticationService</code> make a network request to authenticate the user with the backend API and to receive a session token from it.</p><p>Next the <code>AuthenticationService</code> uses a low level <code>APIClient</code> object to perform an actual HTTP request against the backend API to get the token.</p><p>After executing the HTTP request the API client serializes the response from JSON to Kotlin data class objects and returns it back to the <code>AuthenticationService</code>.</p><p><code>AuthenticationService</code> passes the result back to <code>AuthenticationRepository</code>.</p><p>Then <code>AuthenticationRepository</code> maps the backend response to domain model objects that the rest of the app can work with. After that the repository asks <code>SessionStorage</code> to store the session token.</p><p><code>SessionStorage</code> maps the domain models to DTO and uses <code>DatabaseClient</code> to store those DTOs in the actual database on device.</p><p><code>DatabaseClient</code> is the one that performs low level queries to actually insert data in table/s.</p><p>After database insertion is successful, <code>SessionStorage</code> responds back to the <code>AuthenticationRepository</code> with success and <code>AuthenticationRepository</code> passes the authenticated token to the use case.</p><p>The <code>LoginUseCase</code> finally responds back to the Interactor/ViewModel about successful login so that the UI can navigate to the main user screen.</p><p>This roughly translates to the following responsibilities for each type of object:</p><ul><li><p><strong>Use Cases</strong> are responsible for business logic orchestration - i.e. what repositories to use to retrieve or send data and business logic decision making on when to call them, user permissions, validation, etc.</p></li><li><p><strong>Repositories</strong> are responsible for data retrieval and sending. They abstract out how the data is received or stored - i.e. most of the time data is only sent or received via the backend API but sometimes it needs to be retrieved or stored in the local database, this what&#8217;s abstracted out by repositories, the use cases never need to know from where they got the data provided by repository.</p></li><li><p><strong>Services</strong> are responsible for concrete network requests to concrete endpoints. They serialize data for sending to backend API and after receiving.</p></li><li><p><strong>APIClients</strong> are responsible for actual mechanics of executing HTTP (or GraphQL or Socket) requests. It&#8217;s the low level object that uses URLSession or similar for actual network request sending.</p></li><li><p><strong>Storages</strong> are responsible for storing data to disk, a database, file storage, keyvalue, doesn&#8217;t matter. Just like services for API requests, storages serialize and map data sent and received to and from the database (or other means of storage).</p></li><li><p><strong>DatabaseClients</strong> are just like the APIClients - they are basically a wrapper around Core Data or SQL or UserDefaults or FileManager or similar that perform the actual low level data storing or retrieval function.</p></li></ul><div><hr></div><p>This is a somewhat simpler example where the use case doesn&#8217;t need to coordinate between different repositories to retrieve data but nevertheless it depicts the overall architecture of our use cases and their dependencies well.</p><p>I also omitted a bunch of helper objects that we have, in particular, the mapper objects from one layer onto another. It was a debatable subject on whether we need to have them as separate objects or if we are better off including mapping into each object such as repository, service, etc.</p><h4>Modularization &amp; Code Organization</h4><p>I&#8217;ll cover modularization in detail in the next article of this series but briefly here - we modularize the shared KMP codebase by feature (i.e. Work Orders feature, Authentication feature, etc.) and by layers (database layer, networking layer, etc.)</p><p>We decided to keep shared KMP code in a standalone git repository from the iOS and Android codebases. The iOS and Android codebase import shared code as a native library.</p><p>From the developer experience point of view the shared KMP code is fetched and included in your main iOS or Android codebase as a git submodule folder so that you can change the code as you work on features full stack - both iOS/Android and KMP side at the same time.</p><h4>KMP Shared Code Public API &amp; Dependency Injection</h4><p>We technically expose all of the KMP classes, including the low level ones, to the iOS and Android apps because we decided to leave dependency injection and assembly solutions to each respective application.</p><p>But the intended usage of the shared code is for iOS and Android Interactors or ViewModels to call only Use Cases from KMP and never the other objects (besides using the domain model input/output).</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Architecture in iOS &amp; Android Apps</h3><p>I will cover the architecture of the iOS app (and perhaps the Android app as well) in the second part of this series. But I&#8217;ll briefly explain here how we use shared KMP use cases from the application code.</p><p>As you saw from previous diagrams the second group of business logic objects calls into Use Cases provided by the KMP code to perform useful work for the user. From the perspective of the iOS and Android apps everything is abstracted out for them. The only thing they need to do is to call the right use case at the right time and present things to the UI or route to another accordingly for the user.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2pt3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2pt3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 424w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 848w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 1272w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2pt3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png" width="1456" height="2350" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2350,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:631731,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.mobileengineer.io/i/166573427?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2pt3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 424w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 848w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 1272w, https://substackcdn.com/image/fetch/$s_!2pt3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5519dcf1-0051-4c1a-af43-643c15e909e9_3232x5216.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Now, that is still a lot of business and UI logic that is inside of the iOS and Android apps and is not shared between them.</p><p>As mentioned before, the architectures of the iOS and the Android apps differ from each other. The iOS architecture is <a href="https://github.com/uber/RIBs-iOS">RIBs</a> (Router Interactor Builder) and the Android architecture is MVVM.</p><p>On iOS Interactors are the orchestrators of business logic, they decide what needs to be presented on the screen, what use case needs to be executed and when, what next screen to route to, etc.</p><p>On Android ViewModels play a similar role but it&#8217;s not as well defined - VMs call use cases but other types of orchestration, presentation decisions, and routing happens in the view later.</p><p>We&#8217;re considering eventually moving the Android app to RIBs as well but this is in the distant future as we&#8217;re focusing on getting more things migrated to KMP across both codebases.</p><h2>Learnings So Far &amp; Path Forward</h2><p>This is the third quarter for us using KMP. So far the feedback and experience have been positive. We&#8217;re able to share anywhere between 10% and 25% of the code for new features between two platforms, which is definitely a win for engineering and business. Along with it, we have better consistency and no discrepancies in how we send, retrieve, and massage data between Android and iOS where we use KMP.</p><p>Collaboration between iOS and Android engineers is at an all-time high because they now are literally working on the same codebase (at least part of it) together.</p><p>Another upside of this is that we have a brand new clean codebase with no baggage in it where we could start building things from scratch taking with us all the previous learnings on how to do and not to do things from both the iOS and the Android codebase. We have a clean and clearly defined architecture with single responsibility defined and outlined for each layer and object type.</p><p>The downside, of course, just like with any heavy architecture such as Clean Architecture, is the boilerplate - the sheer amount of files and classes needed to implement a feature is high. But because of clearly defined boundaries and responsibilities, it has not been an issue. Also, <strong>these days a lot of boilerplate code generation can be automated with LLM codegen</strong>. Since the code is so clearly defined and doesn&#8217;t have variability in each implementation, LLMs are highly useful and don&#8217;t get confused too often.</p><p>Moving forward, we&#8217;ll be adopting more and more KMP code throughout the codebases. As the rest of the architecture converges more and more between two platforms (with Android moving to RIBs) we might start considering moving the second group of business logic to the KMP side to share as well.</p><p>The last thing we will ever consider, and likely will never do, is to share UI with KMP between two platforms. UI sharing solutions such as React Native or others never work well and have too many limitations (and the same goes for KMP&#8217;s Compose Multiplatform), so it&#8217;s unlikely for us to go this direction. But even if we don&#8217;t share the UI but share 80% of the rest of the code, it will be a tremendous achievement!</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/mobile-architecture-at-upkeep-part?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Semantic Versioning Dilemma]]></title><description><![CDATA[It is not straightforward, at first glance, which semantic version number of your library you should bump when you're updating its dependencies. Is it major? Or minor? Or patch?]]></description><link>https://newsletter.mobileengineer.io/p/semantic-versioning-dilemma</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/semantic-versioning-dilemma</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Wed, 25 Jun 2025 11:02:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been helping to maintain the <a href="https://github.com/uber/ribs-ios">RIBs-iOS repo</a> in my spare time lately, and there is a bit of a dilemma I'm facing with semantic versioning.</p><p>To give you a refresher, <a href="https://semver.org/">semantic versioning</a> in software is a set of rules that dictates how software version numbers are assigned and incremented. There are 3 numbers in each semantic version: Major, Minor, Patch. This approach to versioning helps manage dependencies by having a strict set of rules on what each number means and when they can be incremented. Short version is this:</p><ol><li><p>The <strong>Major</strong> version number can and should only increase when backwards-incompatible or breaking changes are made to the software</p></li><li><p>The <strong>Minor</strong> version number can and should only increase when backwards-compatible, additive new changes are made. This shouldn't contain breaking changes.</p></li><li><p>The <strong>Patch</strong> version number can and should only increase when backwards-compatible, non-breaking bug fixes are made</p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Simple and sweet, I've been using it for ages. All libraries in SPM, Cocoapods, or Carthage we use in iOS (or in any other software ecosystem for that matter) follow semantic versioning and have version numbers such as <strong>1.2.5</strong> or <strong>2.0.0</strong>, etc., following the <strong>X.Y.Z</strong> format. The first number is the major, the second is the minor, and the last one is the patch.</p><p>Now, here's my dilemma with the library I'm maintaining: what do you do when the change you're introducing is a bump of the dependency version of the dependencies of the library itself?</p><p>Say my library A depends on library B 6.1.0 and library C 5.2.1., and now I need to have it depend on library B 7.0.0 and library C 6.0.0. I really need to bump the versions of my dependencies because I'm going to utilize new features from them in my library soon.</p><p>Should it be a patch change? No, that doesn't sound right - I'm not fixing any bugs after all!</p><p>Should it be a minor change? Nah, I'm not adding any new functionality here&#8230; at least not yet. I need those new features from my dependencies first to do so.</p><p>Should it be a major change? I don&#8217;t think so. I am not breaking any backwards compatibility here since my public API is not changing, nor is the internal behavior of my library.</p><p>Hm&#8230; a dilemma&#8230;</p><p>After thinking about it a bit, I started to analyze what the consequences would be for the consumers of my library if I bumped the version numbers of library B and C, which my own library depends on.</p><p>Well, it turns out that the users of my library are also highly likely to be using my library plus libraries B and C (which mine depends on) in their own project as well.<br>This means that if I bump the version number of my library&#8217;s dependencies it will have a downstream effect on the consumers of my library forcing them to also bump the version of library B and C that they depend on. This is because now the old versions of those libraries, 6.1.0 and 5.2.1 in our example, won&#8217;t be compatible with my library&#8217;s dependencies. A dependency tree is a tree after all.</p><p>So, I conclude that even though it&#8217;s seemingly not a major destructive change, the change in versions of the dependencies of my own library constitutes a major change to the consumer of my library after all because it has downstream forceful/destructive effects.</p><p>Which means I have to bump the major version number of my library!</p><h2>Conclusion</h2><p>This is not pretty obvious at the first glance what semantic versioning number you should be bumping in case when you update dependencies of your own library that others depend on. It was a bit of a head scratcher but as a rule of thumb - as a library maintainer you shouldn&#8217;t be destructive to your users, or at least you need to communicate it via versions. It turns out, your own dependencies are important to consider as they could be used by consumers of your library downstream as well as by your library.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/semantic-versioning-dilemma?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/semantic-versioning-dilemma?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/semantic-versioning-dilemma?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Swift 6.2 Default Concurrency Isolation and @concurrent]]></title><description><![CDATA[Apple is finally listening to feedback and simplifying asynchronous behavior in Swift Concurrency.]]></description><link>https://newsletter.mobileengineer.io/p/swift-62-default-concurrency-isolation</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/swift-62-default-concurrency-isolation</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Fri, 20 Jun 2025 11:05:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TDVz!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I continue working on the <a href="https://newsletter.mobileengineer.io/p/questions-for-async-chapter">Async Chapter</a> of <a href="https://iosinterviewguide.com/">The iOS Interview Guide 2.0</a>, I keep updating it with new additions to the Swift language. With the release of Swift 6.2, announced at WWDC 2025, a significant change was introduced to the default behavior in Swift Concurrency.</p><p>Since Swift 5.5, we&#8217;ve had actors that were designed to prevent data races. Actors ensure that only one piece of code (and only one thread at a time) can access its internal mutable state at any given time. This provides a safe and isolated context for async work execution.</p><p>However, working with actors, isolations, and the varying contexts of thread/queue execution (whether implicit or explicit) sometimes became very complex and challenging to manage.</p><p>In Swift 6.2, Apple changed this, attempting to simplify things by making everything in your app single-threaded by default. Simply put, now, everything runs on the @MainActor implicitly by default if you have the SWIFT_DEFAULT_ACTOR_ISOLATION build setting flag turned on. For brand new projects starting with Xcode 26, it&#8217;s going to be the default.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>What this really means is the return to &#8220;old&#8221; behavior where your code implicitly, by default, runs on the main thread unless you specify otherwise.</p><p>There are basically three ways to run your async code off of the main thread in a background thread:</p><ol><li><p>Use an actor</p></li><li><p>Use @concurrent function.</p></li><li><p>Use Task/Task.detached</p></li></ol><p>Actors&#8217; purpose is to hold a &#8220;safe&#8221; state that can be altered and accessed over multiple async operations from different threads, and @concurrent&#8217;s purpose is to run a one-off async job (for a stateless computation) on a background thread off of the main thread.</p><h2>Conclusion</h2><p>This new default threading behavior is a welcome change to the previously confusing, convoluted implementation of asynchronicity that Apple created in Swift. I&#8217;m glad they are listening this year and are making sensible changes for a change.</p><p>IMO, this is the implementation that they should&#8217;ve aimed for from the get-go as they introduced structured concurrency. It&#8217;s a more natural mental model and a safer assumption for every developer to think that, by default, everything is executed on the main thread. After all, this is how every other client-side platform works, and that&#8217;s how it was prior to Swift Concurrency.</p><p>As for my own projects&#8230; I&#8217;m still sticking with RxSwift :)</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;2017a0b1-eda2-462b-9669-d17e34f89432&quot;,&quot;caption&quot;:&quot;I&#8217;m a bit conflicted when it comes to my opinion and preferences towards using RxSwift vs Async/Await for async operations. I rewrote this article multiple times trying to refine my thinking, hopefully this one captures it, so hear me out.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;RxSwift vs Async/Await&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:27258834,&quot;name&quot;:&quot;Alex Bush&quot;,&quot;bio&quot;:&quot;Author of The iOS Interview Guide book and iOS System Design course. Co-host of Inside iOS Dev podcast. Previously at Uber, Wayfair.&quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/566faa25-1bc3-4c38-bf51-f3299d4c1c67_512x512.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-05-22T14:00:27.601Z&quot;,&quot;cover_image&quot;:null,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://newsletter.mobileengineer.io/p/rxswift-vs-asyncawait&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:164134057,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:10,&quot;comment_count&quot;:3,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;The Mobile Engineer&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7439f56-dbc2-4ce9-9ac0-1c33e03376d9_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/swift-62-default-concurrency-isolation?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/swift-62-default-concurrency-isolation?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/swift-62-default-concurrency-isolation?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[[WWDC 2025] Platforms State of The Union First Impressions.]]></title><description><![CDATA[My hot takes from WWDC 2025 so far.]]></description><link>https://newsletter.mobileengineer.io/p/wwdc-2025-platforms-state-of-the</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/wwdc-2025-platforms-state-of-the</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Tue, 10 Jun 2025 05:20:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xDb0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I just finished watching <a href="https://developer.apple.com/videos/play/wwdc2025/102/">WWDC 2025 Platform State of The Union</a> and here are some of my first raw unfiltered thoughts:</p><h2>New Design. Liquid Glass.</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xDb0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xDb0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xDb0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xDb0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!xDb0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b019a2e-d27d-4003-97ce-55abe5a1ece5_1600x900.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The new design, in my opinion, looks nice and clean, <a href="https://x.com/JonyIveParody/status/1932123749836362119/photo/1">despite the jokes</a> on Twitter <a href="https://x.com/WindowsLatest/status/1932124734772388079">comparing it to Windows Vista's 2006</a> design.</p><p>I think the most important thing here about the new design is unification of it across all Apple platforms and making it even more divergent from Android. This will make it even harder to build cross platform applications with tech such as React Native because mobile platforms will diverge more and more incentivising developers to build native iOS applications more and more.</p><p>New sheets and menu presentations are nice. We&#8217;ll see how easy or difficult they are to customize.</p><p>The new tabBarBottomAccessory looks very nice too. Will be handy for time tracking applications. Hopefully it&#8217;s highly customizable and flexible in its presentation.</p><p>It seems like Xcode 26 is going to be the only release of Xcode where developers will be able to opt-out of the new Liquid Glass UI. Effectively it gives us about a year + some to migrate to the new design.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Icon Composer</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Vw0T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Vw0T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Vw0T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Vw0T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!Vw0T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcc19ee04-409b-4436-919a-b8c1c8186e62_1600x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is great! I know it&#8217;s a small thing but having a nice little app that helps you create app icons with different variations is such a big help trying to publish your own app. Especially if you&#8217;re on a budget haha.</p><p>I&#8217;ll definitely be using it for my own projects.</p><h2>Apple Intelligence</h2><p>There is so much to cover about Apple Intelligence just from the Platform State of The Union video let alone from the other Apple Intelligence specific videos that I don&#8217;t think I&#8217;m going to do it justice. But here are some of the highlights/thoughts of mine:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wKcf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wKcf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wKcf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f54b18b9-e11a-4113-855b-56236967fe12_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wKcf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!wKcf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff54b18b9-e11a-4113-855b-56236967fe12_1600x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Apple's approach to implementing its Foundation Models appears to be very well-thought-out. The way to use LLMs in your code is just by importing it like yet another framework in your app. And it runs on the user's device!</p><p>I especially liked the choice between a single response result from the model vs a stream.</p><p>@Generatable is also a very clever and great way of annotating and structuring the output you want to receive from LLM. I wonder what the accuracy and hallucinations would be??</p><p>Tool calling is also done via interfaces (protocols)! Finally someone at Apple thought through API design and did it well!</p><p>Because Foundation Models runs on device it can be available offline which is a distinct feature from Android.</p><p>VisualIntelligence and Intents integration via deep linking seems interesting too but I need to look into it more closely to figure out how it actually works. (I believe Intents is not a new API but integrating it with LLM things seems to be a great addition)</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Xcode</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L_TF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L_TF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L_TF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!L_TF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!L_TF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F403580c1-a309-40a3-8c1f-d649822ab5c1_1600x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Many new great things are coming to Xcode. ChatGPT and other LLM codegen models are finally coming to Xcode. I think this is the only wish that came true <a href="https://newsletter.mobileengineer.io/i/165320013/ai-code-gen-tools-in-xcode">from my WWDC wishlist</a> this year :)</p><p>The UI seems nice at the first glance but what I couldn&#8217;t see is the approve/reject style of UI to accept or reject the changes that LLM makes in your codebase. Kind of like what Cursor does. The new Xcode UI reminds me more of V0 or Figma Make more than Cursor or Intellij.</p><p>Another new nice addition is #Playground directive to test code inline. It will be very handy to try things out quickly.</p><p>Also, it looks like developers will be able to add their own custom models to be used in Xcode which is great (I personally don&#8217;t think chatgpt&#8217;s models are the best for coding anymore, go google!)</p><h2>Swift</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bfHm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bfHm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bfHm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bfHm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!bfHm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F695d524c-5d6c-4245-abca-b909fc0828ef_1600x900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And the last one that caught my interest was all Swift changes. Unfortunately it doesn't seem like I&#8217;ve got <a href="https://newsletter.mobileengineer.io/i/165320013/protected-keyword-in-swift">my protected keyword wish</a> haha.</p><ul><li><p>InlineArray - meh, great but it doesn&#8217;t seem like it would be very useful since iOS devices are so powerful these days you rarely need this level of optimization</p></li><li><p>Span type - meh, probably is very useful for low level development and interop with C but I don&#8217;t think many app developers will be working with it day to day</p></li><li><p>Interop of Java and Javascript seems to be very interesting! I&#8217;ll be digging into it later.</p></li><li><p>It still puzzles me why Apple keeps introducing more improvements to race condition catching with things like the @concurrent. Concurrency issues are a solved problem if you architect your code properly. Just don&#8217;t use Singletons :)</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Mobile Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Conclusion</h2><p>These were just some of my first impressions, thoughts, and musings on what I saw in the Platform State of The Union video of WWDC 2025. <br><br>What piqued your interest? Leave a comment. And let&#8217;s see what Apple has in store for us tomorrow.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/wwdc-2025-platforms-state-of-the?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading The Mobile Engineer! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/wwdc-2025-platforms-state-of-the?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/wwdc-2025-platforms-state-of-the?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><p></p>]]></content:encoded></item></channel></rss>