<?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>Wed, 08 Apr 2026 19:39:44 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[[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;}" 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">5 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><item><title><![CDATA[Everyone has a wishlist for WWDC 2025. Here’s mine.]]></title><description><![CDATA[My WWDC 2025 wishlist: because apparently, I can't ask Santa for protected keyword in Swift.]]></description><link>https://newsletter.mobileengineer.io/p/everyone-has-a-wishlist-for-wwdc</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/everyone-has-a-wishlist-for-wwdc</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Fri, 06 Jun 2025 11:02:22 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 this time of year. Every iOS developer is talking about their wish lists for this year&#8217;s &#8220;Apple Christmas&#8221; - WWDC. Artjoms Vorona has a <a href="https://curatedios.substack.com/p/34-wwdc25-wishes">good compilation</a> of various developer&#8217;s expectations and wishes.</p><p>I figured I would join the hype train and share mine as well. Here we go:</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>AI Code Gen Tools in Xcode</h2><p>We all are waiting for something better than, not-so-useful, autocomplete that we&#8217;ve currently got in Xcode. Cursor, Intellij, and other similar IDEs are leaps and bounds ahead of Xcode, Apple needs to keep up here.</p><p>Personally, I really like Cursor&#8217;s approach with its powerful codebase indexing and coding rules that kick in based on file pattern match.</p><h2>More SwiftUI integration into UIKit</h2><p>This one has been a long time ask of mine - better integration of SwiftUI into UIKit. I still believe that currently the best way to build iOS apps is not to create a SwiftUI app but instead to have a UIKit based app where you extensively use SwiftUI. As of today the way you do it is by utilizing <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller">UIHostingController</a>, that holds SwiftUI view, by either subclassing from it or embedding as a child view controller as a wrapper around SwiftUI view.</p><p>It works well enough, but what&#8217;s missing is something more lightweight than a view controller wrapper - a simple view holder that accepts a SwiftUI object to render.</p><p>What I&#8217;m thinking about is basically something like the <a href="https://www.swiftbysundell.com/articles/rendering-swiftui-views-within-uitableview-or-uicollectionview/">UIHostingConfiguration</a> that UITableView has but for a UIView.</p><p>This technically already exists for macOS with <a href="https://developer.apple.com/documentation/swiftui/nshostingview">NSHostingView</a>. So, my wish is for Apple to add this to iOS, something like &#8220;UIHostingView.&#8221;</p><h2>Less Singletons and More Protocols in Apple Frameworks</h2><p>It&#8217;s been a pet peeve of mine for many years now. Apple makes great OSes and frameworks for developers to use, but the way their APIs are implemented often is poor, lacking, and outright bad. Specifically the abundance of singletons and lack of interfaces (protocols), for things like UserDefaults, URLSession, FileManager, NotificationCenter, etc., makes it very inconvenient to work with them. You constantly would have to wrap around them with your own custom wrappers that have interfaces (protocols) to actually be able to properly inject and mock them.</p><p>And don&#8217;t even get me started on singletons&#8230; it&#8217;s just an anti-pattern to have those in the first place!</p><p>My wish is for Apple to be better architects of their frameworks&#8217; public APIs and adhere more to SOLID principles.</p><h2>Protected Keyword In Swift</h2><p>This one also has been a long time pet peeve of mine - the lack of protected keyword/concept in Swift. In every other normal OOP language there is a concept of a protected method or property that indicates that it&#8217;s designated for subclasses only and is available to them to call or override. Protected methods or properties are not available publicly otherwise.</p><p>Kotlin has it. Java has it. Ruby has it. Typescript has it. C# has it. PHP has it. What is wrong with you Swift!?!?!?!?</p><p>I know I know what you&#8217;re going to say! &#8220;But Alex, it was <a href="https://developer.apple.com/swift/blog/?id=11">explicitly decided</a> for Swift not to have protected keyword&#8221;. Well, I don&#8217;t care, it was a bad choice and there is no excuse for it.</p><p>Over time all major general purpose languages, as they add features, coalless on the same set of features and become alike. It&#8217;s inevitable as there are only so many good ideas for features in a programming language and they constantly &#8220;borrow&#8221; and copy each other.</p><p>So Swift, get on with the times and add the darn protected keyword, will you!</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>Conclusion</h2><p>These are some of the "wishes" on my list that come to mind. We'll see what Apple has in store for us this WWDC. I am not holding my breath for the additions I listed, but then again...</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/everyone-has-a-wishlist-for-wwdc?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/everyone-has-a-wishlist-for-wwdc?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/everyone-has-a-wishlist-for-wwdc?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[AsyncStream is What Async/Await Should've Been]]></title><description><![CDATA[Apple did something right with AsyncStream. But did they go far enough?]]></description><link>https://newsletter.mobileengineer.io/p/asyncstream-is-what-asyncawait-shouldve</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/asyncstream-is-what-asyncawait-shouldve</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Fri, 30 May 2025 11:02:42 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>Yes, you read it right. I&#8217;ve been researching Swift Concurrency for <a href="https://iosinterviewguide.com/">my book</a> and I think AsyncStream is what async/await should&#8217;ve been because it most closely resembles RxSwift Observables. Let me explain.</p><p>The main way you use and create AsyncStreams is via the Continuation API:</p><pre><code>func generateNumbersWithContinuation() -&gt; AsyncStream&lt;Int&gt; {
    AsyncStream { continuation in
        
        continuation.onTermination = { termination in
            // do the cleanup here
        }
        
        Task {
            for i in 1...5 {
                try await Task.sleep(nanoseconds: 500_000_000)
                continuation.yield(i)
            }
            continuation.finish()
        }
    }
}

// run it with
Task {
    for await number in generateNumbersWithContinuation() {
        print(number)
    }
}</code></pre><p>Here, we create an AsyncStream that emits new values over time asynchronously and calls finish when it&#8217;s done. We also set up the onTermination callback block to do the cleanup of any underlying resources that were producing the values asynchronously to be emitted from the stream.</p><p>Conceptually, pretty much literally, this is an RxSwift Observable:</p><pre><code>private let disposeBag = DisposeBag()


func generateNumbersWithObservable() -&gt; Observable&lt;Int&gt; {
        return Observable.create { observer in
            
            for i in 1...5 {
                Thread.sleep(forTimeInterval: 0.5)
                observer.onNext(i)
            }
            observer.onCompleted()
            
            return Disposables.create {
                // do the cleanup here
            }
        }
        .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated))
}

// run it with
generateNumbersWithObservable().subscribe(onNext: { number in
        print(number)
}).disposed(by: disposeBag)
</code></pre><p>Here, we do literally the same thing, we have an Observable that emits values over time asynchronously and calls onCompleted when it&#8217;s done. We set up a cleanup block with Disposables.create to do the cleanup of any underlying resources that were producing the values asynchronously to be emitted from the observable.</p><p>Congratulations Apple, you reinvented the wheel! Ghmm&#8230; but I digress.</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>This is not unexpected since the concept of reactive streams is very good, so it&#8217;s natural for Apple to <s>steal</s>/get inspired by it.</p><p>&#8230;but where Apple didn&#8217;t go far enough is to make async/await functions mimic, and be similarly implemented to, RxSwift Singles.</p><p>Conceptually, async operations, regardless of what language and framework you use to implement them, are blocks of code/work, jobs that need to be executed asynchronously, that have the main block of work/setup plus a cleanup part to wind down if the operation was cancelled or finished.</p><p>When we look at the AsyncStream and Observable examples above (albeit, very simplified), we see that in action - there is a main chunk of code for the main action and then there is a cleanup block to wind down the operations.</p><p>The difference from async/await is that it assumes that there will be multiple results produced. With async/await, typically, there is one result produced at the end of the operation (or no result but you can mimic it with Void with both AsyncStream and Observable).</p><pre><code>func generateANumbersWithAsyncAwait() async -&gt; Int {
    try? await Task.sleep(nanoseconds: 500_000_000)
    return Int.random(in: 1...5)
}

// run it with
Task {
    let result = await generateANumbersWithAsyncAwait()
    print(result)
}</code></pre><p>Above is an example of an async/await function. It has the main block of code to perform the work, but where is the cleanup to wind down the operation? It&#8217;s not there. The only thing you can do is to check on a procedural flag with Task.isCancelled or Task.checkCancellation(). The problem with it is that most of the time developers will simply forget to do it.</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, if only Apple would implement async/await functions similarly to AsyncStream so that they have an onTermination callback to implement the cleanup&#8230;. Oh wait, if they did that they would essentially reimplement/reinvent the Single from RxSwift!!!</p><p>Singles in RxSwift are Observables made for one purpose, to produce one value asynchronously. Since they are observables in nature they have the same traits such as Disposables.create callback block to properly cleanup after the operation is done or cancelled:</p><pre><code>private let disposeBag = DisposeBag()

func generateNumbersWithSingle() -&gt; Single&lt;Int&gt; {
        return Single.create { observer in
            
            Thread.sleep(forTimeInterval: 0.5)
            observer(.success(Int.random(in: 1...5)))
            
            return Disposables.create {
                // do the cleanup here
            }
        }
        .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated))
}

// run with
generateNumbersWithSingle().subscribe(onSuccess: { number in
            print(number)
}).disposed(by: disposeBag)</code></pre><p>Here, just like with Observable or AsyncStream, you have an explicit dedicated block for cleanup of the asynchronous operation.</p><p>If only async/await was like that! (sigh&#8230;) but unfortunately it remains the &#8220;AsyncStream that never was&#8221;... Apple should&#8217;ve just <s>stole</s>/reimplemented the Rx Single&#8230;</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/asyncstream-is-what-asyncawait-shouldve?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! If you found it interesting please share it, it helps out a lot to grow this newsletter.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/asyncstream-is-what-asyncawait-shouldve?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/asyncstream-is-what-asyncawait-shouldve?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[RxSwift vs Async/Await]]></title><description><![CDATA[Guess this old timer&#8217;s favorite :)]]></description><link>https://newsletter.mobileengineer.io/p/rxswift-vs-asyncawait</link><guid isPermaLink="false">https://newsletter.mobileengineer.io/p/rxswift-vs-asyncawait</guid><dc:creator><![CDATA[Alex Bush]]></dc:creator><pubDate>Thu, 22 May 2025 14:00:27 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 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.</p><p>Over the years I&#8217;ve been exploring and experimenting with different async solutions on iOS. Things like Threads, Grand Central Dispatch (GCD), NSOperations and NSOperationQueues, Futures, Promises, etc. Ultimately, I came to the conclusion that the best way to solve asynchronicity on iOS is by using ReactiveX streams and observables to model it.</p><p>But more recently, with the introduction of Swift 5.5 in 2021, Swift Concurrency promised to solve the asynchronicity problem by providing async/await, actors, and other features.</p><p>By the time it came out, I was all in on Rx, but also learned that it&#8217;s not a silver bullet for every problem and that it shouldn&#8217;t be used everywhere in every layer of your application. To achieve the best results for the effort you put in and to reduce the number of potential errors you <strong>should NOT use Rx</strong> Observables and Streams <strong>in the UI layer</strong> of your application. Everywhere else it&#8217;s fair game - business logic, networking layer, service layer, etc.</p><p>So effectively, the asynchronicity problem was solved for me. Rx has a steep learning curve but if you apply it and use it with discipline you won&#8217;t have any problems.</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>And then comes async/await that&#8217;s claimed to be the best at asynchronicity. Well, I examined it more thoroughly as I conducted my research on the Async chapter of The iOS Interview Guide 2.0 and it has its pros and cons, but still Rx would solve all of these problems even before for me. This is how it stacks up:</p><ul><li><p>async functions to model asynchronous operations - already had that with Single and Observable in Rx</p></li><li><p>Sequential execution with await and parallel execution of async operations with async let - yep, already had that with higher order functions such as flatMap, concat, zip, combineLatest, merge, etc.</p></li><li><p>State management with actor - yea, had that, with Subject, BehaviorRelay, etc. There are more advanced compile time controls and safety that comes with Swift Concurrency though, more on that later.</p></li><li><p>Error handling with do/try/catch/async throw - we had that with onError callback, catchError, retry, etc. That said, error handling might be a bit easier in some scenarios with Swift Concurrency vs RxSwift</p></li><li><p>Operation cancellation and cleanup with Task/isCancelled - RxSwift has a more robust clean way of doing it with subscribe, Disposable, and DisposeBag. More on that later.</p></li><li><p>UI data binding with ObservableObject - Rx has it via Driver but as I mentioned above this is a mute point for me since I prefer not to have any data bindings and reactivity in my UI layer.</p></li><li><p>Completely absent feature in Swift Concurrency - Scheduler. It&#8217;s a powerful feature of RxSwfit that allows you to control the execution context of observables with the async operation block&#8217;s or its callback. More on that below.</p></li></ul><p>As you can already see from this short list - almost everything Swift Concurrency offers RxSwift provides plus more. Let&#8217;s take a look at those exceptions or specific things that need clarification.</p><h2>State Management with Actor vs Subject/BehaviorReplay</h2><p>Overall, there is nothing that you can do with Actor that you can&#8217;t do with RxSwift&#8217;s Subject using observeOn, subscribeOn, and serialize. But there is one clear advantage that Swift Concurrency has here - built-in compiler time checks for data isolation. Swift Concurrency also has Sendable and other tools for data isolation and thread safety.</p><p>In reality though, for over 15 years of me building iOS applications, I have never ever encountered a situation where data access over different threads was a problem. Usually, if you&#8217;d get to that point and start having issues with thread data access, race conditions, etc., it&#8217;s a symptom of a larger architectural problem rather than an &#8220;async problem to solve&#8221;.</p><p>Ask yourself - why do you have threading data access issues in the first place? Is there an improperly architected singleton in your app that everyone is trying to access and read/write data from/to? Most likely there is. You shouldn&#8217;t be using singletons in the first place, they are an antipattern. Just purge them and re-architect your data access and this will solve your problem, not an actor bandaid from Apple.</p><p>RxSwift wins here in my books because it gives you way more flexibility but I have to admit - it loses on the compile time checks, it&#8217;s better to have a compiler help you than not.</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>Error Handling in Swift Concurrency vs RxSwift</h2><p>Here it might be closer to a tie. On one hand, RxSwift&#8217;s errors are thrown for the entire chain/stream in just one callback block at the tail end of the stream when you call it which makes it hard to debug. On the other hand, RxSwift has a lot of different catch and retry methods that could help you make the error behavior more sophisticated.</p><p>Swift Concurrency though has a more straightforward approach by using try/do/catch blocks. That&#8217;s a win for Swift Concurrency here.</p><h2>Operation Cancellation and Cleanup</h2><p>Swift Concurrency is just weird if not outright lazy here. After you call cancel() on a Task, which is similar to Disposable&#8217;s cancel() method, you are then supposed to constantly check Task.isCancelled or try Task.checkCancellation() to make sure your async operation doesn&#8217;t perform wasteful work after it was cancelled.</p><p>Ummmm&#8230; do what now? What is this primitive stateful procedure programming? Are we back in college, hacking together a god-user-object where all the state lives and we need to flip all sorts of states and flags on it to achieve everything in our codebase and ultimately drown in a sea of state related bugs?</p><p>This is just lazy and unprofessional on Apple&#8217;s part - this is not how modern software should be built. Most developers will forget to check the flag and won&#8217;t clean up resources causing subtle bugs and wasteful performance.</p><p>This is where RxSwift wins over Apple&#8217;s stuff hands down - every Observable has a dispose callback block that will be executed whenever the async operation is cancelled. This is where you would do proper cleanup of resources scoped to just your observable specifically. Nice, efficient, clean and tidy.</p><h2>RxSwift&#8217;s Scheduler</h2><p>This is another area where Rx wins hands down. The level of control and sophistication of it using Scheduler is untouched by Swift Concurrency - it doesn&#8217;t even have such a concept.</p><p>With the Scheduler, you can schedule operations to perform on the main thread, background thread, and schedule the call back when it emits values to perform on various threads as well. Even more, you can model the time itself in tests by using TestScheduler which allows you to run synchronous unit-tests that mock time ticks mimicking asynchronicity. Hands down a winner there!</p><p>Yes yes, I know what you&#8217;d say - &#8220;but Alex, you can use @MainActor and the await MainActor.run and other things like that!&#8221;. Yes, you can&#8230; but as I said above, they have nowhere near the sophistication you can achieve with the Scheduler.</p><h2>Conclusion</h2><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>At this point, I&#8217;m not ready to completely switch to Swift Concurrency and use async/await everywhere in my code, because RxSwift just provides so much more power and sophistication to solve async tasks. But at the same time, async/await has its merits too, for simpler linear operations such as network requests, for example. Plus, it has an easier learning curve than RxSwift.</p><p>RxSwift requires you to exercise discipline because it quickly can get out of control, but it gives you a lot of power. With Swift Concurrency you can be a bit more lax.</p><p>At the end of the day I use both, luckily RxSwift has several niceties and extensions to interoperate with Swift Concurrency and map async operations or streams to observables and singles and vice versa.</p><p><strong>P.S.</strong> If you liked this content please share it, it helps spread the word about this substack! Also, I&#8217;m working on the 2nd edition of The iOS Interview Guide, topics like this will be covered in the Async Work chapter of the book. Subscribe if you want to hear more updates about my book&#8217;s progress.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://newsletter.mobileengineer.io/p/rxswift-vs-asyncawait?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://newsletter.mobileengineer.io/p/rxswift-vs-asyncawait?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p>]]></content:encoded></item></channel></rss>