{"id":49969,"date":"2025-11-07T17:41:21","date_gmt":"2025-11-07T22:41:21","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=49969"},"modified":"2025-11-07T17:41:21","modified_gmt":"2025-11-07T22:41:21","slug":"mainactor-assumeisolated-preconcurrency-and-isolated-conformances","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2025\/11\/07\/mainactor-assumeisolated-preconcurrency-and-isolated-conformances\/","title":{"rendered":"MainActor.assumeIsolated, Preconcurrency, and Isolated Conformances"},"content":{"rendered":"<p><a href=\"https:\/\/fatbobman.com\/en\/posts\/mainactor-assumeisolated\/\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/fatbobman.com\/en\/posts\/mainactor-assumeisolated\/\">\n<p>As Swift 6 gradually gains adoption, this problem becomes increasingly prominent: developers want to benefit from the concurrency safety guarantees provided by the Swift compiler, while struggling with how to make their code meet compilation requirements. This article will demonstrate the clever use of <code>MainActor.assumeIsolated<\/code> in specific scenarios through an implementation case with <code>NSTextAttachmentViewProvider<\/code>.<\/p>\n<p>[&#8230;]<\/p>\n<p>We seem to be caught in a dilemma: we need to construct <code>UIHostingController<\/code> in <code>MainActor<\/code>, yet we cannot assign the constructed view (<code>UIView<\/code>) to <code>self.view<\/code> within <code>MainActor<\/code>.<\/p>\n<p>[&#8230;]<\/p>\n<p>Looking at <code>MainActor.assumeIsolated<\/code>&rsquo;s signature, we can see that this API provides a <code>MainActor<\/code> context for its trailing closure. This means <strong>we can &ldquo;synchronously&rdquo; run code that can only execute in a <code>MainActor<\/code> context within a non-<code>MainActor<\/code> synchronous context, without creating an async environment, and return a <code>Sendable<\/code> result<\/strong>.<\/p>\n<p>[&#8230;]<\/p>\n<p>I still hope we can move past this somewhat &ldquo;chaotic&rdquo; transition period soon. Perhaps in a few years, when numerous official and third-party frameworks have completed their Swift 6 migration, we&rsquo;ll finally enjoy a more relaxed safe concurrent programming experience.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/www.massicotte.org\/preconcurrency\">Matt Massicotte<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.massicotte.org\/preconcurrency\"><p>I consistently find the <code>@preconcurrency<\/code> attribute to be confusing. But, I&rsquo;m tired of that. Let&rsquo;s just, once and for all, get a better handle how to use this thing.<\/p>\n<p>[&#8230;]<\/p>\n<p>It has <strong>three<\/strong> distinct uses. And while they all apply to definitions, the details are quite different.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/www.jessesquires.com\/blog\/2024\/12\/19\/diffable-data-source-main-actor-inconsistency\/\">Jesse Squires<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.jessesquires.com\/blog\/2024\/12\/19\/diffable-data-source-main-actor-inconsistency\/\"><p>UIKit provides two diffable data source APIs, one for <a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uicollectionviewdiffabledatasource-9tqpa\">collections<\/a> and one for <a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uitableviewdiffabledatasource-2euir\">tables<\/a>.\nRecently, while working on <a href=\"https:\/\/www.jessesquires.com\/blog\/2024\/10\/18\/introducing-reactivecollectionskit\/\">ReactiveCollectionKit<\/a>, I noticed that the APIs were updated for Swift Concurrency in the iOS 18 SDK, but the annotations were inconsistent with the documentation.<\/p><p>[&#8230;]<\/p><p>I reached out to <a href=\"https:\/\/mas.to\/@smileyborg\/\">Tyler Fox<\/a> from the UIKit team on Mastodon to ask if this was a mistake. As it turns out, it is not a mistake and his reply was incredibly helpful and insightful. For posterity and documentation purposes (and because social media is ephemeral and unreliable), I&rsquo;m going to reproduce <a href=\"https:\/\/mas.to\/@smileyborg\/113427085770601417\">his entire response here<\/a>[&#8230;]<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/wadetregaskis.com\/beware-of-specifying-isolation-requirements-for-whole-protocols\/\">Wade Tregaskis<\/a>:<\/p>\n<blockquote cite=\"https:\/\/wadetregaskis.com\/beware-of-specifying-isolation-requirements-for-whole-protocols\/\">\n<p>These might seem pretty similar &#x2013; you&rsquo;d be forgiven for assuming it&rsquo;s just a convenience to put <code>@MainActor<\/code> on the protocol overall rather than having to repeat it for every member of the protocol.  Less error-prone, too.<\/p>\n<p>But, you generally shouldn&rsquo;t do that.  They are <em>not<\/em> equivalent.<\/p>\n<p>The first form is not merely saying that all the members of the protocol require a certain isolation, but that the <em>type<\/em> that conforms to the protocol must have that isolation.  The <em>whole<\/em> type.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/www.massicotte.org\/step-by-step-conforming-to-protocols\">Matt Massicotte<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.massicotte.org\/step-by-step-conforming-to-protocols\"><p>Further, as far as the compiler is concerned, there is an actor boundary both going into and returning from <code>assumeIsolated<\/code>. This means you cannot work with non-<code>Sendable<\/code> data here and that can be an enormous pain.<\/p><p>[&#8230;]<\/p><p>Before Swift 6.0, dynamic isolation was the only option. And before Swift 6.2, I think that preconcurrency conformances were the best tool for handling protocol isolation mismatches. They address pretty much all of the weakness of the <code>nonisolated<\/code>-<code>assumeIsolated<\/code> thing. But they just feel funny.<\/p><p>[&#8230;]<\/p><p>Swift 6.2 allows us to express this idea directly, by constraining a conformance to be valid only for a particular global actor.<\/p><p>What we now have is exactly what we want. A <code>MainActor<\/code> type that is <code>Equatable<\/code> in that context <strong>only<\/strong>. This is not the same as a true, unconstrainted <code>Equatable<\/code>, because those work everywhere. It&rsquo;s a little like defining a new, special variant of that protocol right in line at the conformance declaration site.<\/p><p>[&#8230;]<\/p><p>But remember, not all protocols are compatible. And making this entire thing implicit makes the problems even more surprising. Don&rsquo;t get me wrong, I <strong>really<\/strong> like isolated conformances and am very happy to see them come to the language. But they are not a magic bullet (and neither is <code>MainActor<\/code>-by-default).<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/slideslive.com\/39043374\/sending-preconcurrency-nonisolated-migrating-project-to-swift-6-compilation-mode?ref=folder-185936\">Lukas Valenta<\/a> at mDevCamp (<a href=\"https:\/\/mastodon.social\/@lvalenta\/115487813401304260\">Mastodon<\/a>):<\/p>\n<blockquote cite=\"https:\/\/slideslive.com\/39043374\/sending-preconcurrency-nonisolated-migrating-project-to-swift-6-compilation-mode?ref=folder-185936\"><p>The talk focuses - as the name suggests - to strategy to migrate the project from Swift 5 compilation mode to Swift 6. We will discuss several issues anyone will encounter to have project that compiles under Swift 6 mode, such as issues with Combine and Async publishers, DispatchQueue.main precondition queue checks, working with older APIs that predate the concurrency, as well as a debate whether it is all worth it. I will also mention the transition of not only the project itself but also a story of external dependencies, some of which written by me, and how did the migration in the libraries took place.<\/p><\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2025\/11\/03\/swift-6-2-approachable-concurrency\/\">Swift 6.2: Approachable Concurrency<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2025\/08\/26\/swiftdatas-modelactor-is-just-weird\/\">SwiftData&rsquo;s ModelActor Is Just Weird<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/12\/20\/swift-concurrency-in-real-apps\/\">Swift Concurrency in Real Apps<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/11\/26\/watch-out-for-counterintuitive-implicit-actor-isolation\/\">Watch Out for Counterintuitive Implicit Actor-Isolation<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/11\/07\/problematic-swift-concurrency-patterns\/\">Problematic Swift Concurrency Patterns<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/09\/20\/unwanted-swift-concurrency-checking\/\">Unwanted Swift Concurrency Checking<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/03\/01\/where-view-task-gets-its-main-actor-isolation-from\/\">Where View.task Gets Its Main-actor Isolation From<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/03\/01\/how-the-swift-compiler-knows-that-dispatchqueue-main-implies-mainactor\/\">How the Swift Compiler Knows That DispatchQueue.main Implies @MainActor<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/11\/08\/swift-concurrency-tips\/\">Swift Concurrency Tips<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/05\/12\/mainactor-not-guaranteed\/\">@MainActor Not Guaranteed<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Fatbobman: As Swift 6 gradually gains adoption, this problem becomes increasingly prominent: developers want to benefit from the concurrency safety guarantees provided by the Swift compiler, while struggling with how to make their code meet compilation requirements. This article will demonstrate the clever use of MainActor.assumeIsolated in specific scenarios through an implementation case with NSTextAttachmentViewProvider. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2025-11-07T22:41:25Z","apple_news_api_id":"19cac26d-818d-43f4-aeb4-ff984188c2c8","apple_news_api_modified_at":"2025-11-07T22:41:25Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/AGcrCbYGNQ_SutP-YQYjCyA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2741,30,2742,71,2200,901],"class_list":["post-49969","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-26","tag-mac","tag-macos-tahoe-26","tag-programming","tag-swift-concurrency","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/49969","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=49969"}],"version-history":[{"count":1,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/49969\/revisions"}],"predecessor-version":[{"id":49970,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/49969\/revisions\/49970"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=49969"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=49969"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=49969"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}