{"id":45180,"date":"2024-10-01T14:36:12","date_gmt":"2024-10-01T18:36:12","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=45180"},"modified":"2024-11-11T11:30:40","modified_gmt":"2024-11-11T16:30:40","slug":"swift-concurrency-and-objective-c","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2024\/10\/01\/swift-concurrency-and-objective-c\/","title":{"rendered":"Swift Concurrency and Objective-C"},"content":{"rendered":"<p><a href=\"https:\/\/tapbots.social\/@paul\/113221607934713424\">Paul Haddad<\/a>:<\/p>\n<blockquote cite=\"https:\/\/tapbots.social\/@paul\/113221607934713424\">\n<p>Anyone know why calling the following in a <code>MainActor<\/code> class\/func<\/p>\n<pre>MyTest.increment(1) { result in\n    NSLog(\"result=\\(result)\")\n}<\/pre>\n<p>crashes (asserts) when building with Swift 6?<\/p>\n<p>I get that its not happy that the completion is coming in on another <code>dispatch_queue<\/code> but it should complain about it at compile time, or ignore it at run time.<\/p>\n<\/blockquote>\n<p>Unfortunately, it seems to be designed this way.<\/p>\n<p><a href=\"https:\/\/mastodon.gamedev.place\/@OneSadCookie\/113222493804971281\">OneSadCookie<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.gamedev.place\/@OneSadCookie\/113222493804971281\">\n<p><a href=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/0423-dynamic-actor-isolation.md\">SE-0423<\/a> &ldquo;Dynamic actor isolation enforcement from non-strict-concurrency contexts&rdquo; adds the crash (otherwise your &ldquo;safe&rdquo; Swift 6 code is unsafe).<\/p>\n<\/blockquote>\n<p>It&rsquo;s not that the code <em>is unsafe<\/em> but that neither the Swift compiler nor the Swift runtime can <em>prove that it&rsquo;s safe<\/em> because the <code>MyTest<\/code> class is written in Objective-C. You are supposed to <a href=\"https:\/\/clang.llvm.org\/docs\/AttributeReference.html#customizing-swift-import\">annotate your Objective-C code<\/a> so that Swift Concurrency can understand it, though this is <a href=\"https:\/\/mastodon.gamedev.place\/@OneSadCookie\/113222731221301279\">not really documented<\/a>.<\/p>\n\n<p><a href=\"https:\/\/sfba.social\/@dgregor79\/113230153261136390\">Doug Gregor<\/a>:<\/p>\n<blockquote cite=\"https:\/\/sfba.social\/@dgregor79\/113230153261136390\">\n<p>In Swift 5 mode, this code silently introduces a data race into the program.<\/p>\n<p>In Swift 6 mode, the data race is caught by the dynamic isolation check. That&rsquo;s the first point at which the data race can be detected, and the check is there to prevent this race from becoming weird runtime behavior.<\/p>\n<p>This is all as designed. If that Objective-C code were Swift 6 code, we&rsquo;d catch the error at compile time. As Objective-C, runtime is the earliest it&rsquo;s possible to detect the race. Enabling Swift 6 language mode means turning previously-unobserved or undiagnosed data races into ones that fail predictably to flush out any contract violations outside of the Swift 6 code. As more code enables Swift 6, the runtime checks get replaced with compile-time.<\/p>\n<\/blockquote>\n\n<p>Swift 6 mode is being &ldquo;helpful&rdquo; by proactively crashing the app even though there&rsquo;s not necessarily a problem. It may be that it just doesn&rsquo;t understand that <a href=\"https:\/\/tapbots.social\/@paul\/113230656114923534\">GCD is being used<\/a> to call everything on the right thread.<\/p>\n\n<p>I get why Swift 6 is designed this way, but I don&rsquo;t understand how you&rsquo;re supposed to make the transition. Swift 5 mode gives no errors at compile time and doesn&rsquo;t even log any errors at runtime. Swift 6 mode gives no errors at compile time and crashes at runtime. To get from one to the other you&rsquo;re supposed to go through the code line-by-line and not make any mistakes.<\/p>\n\n<p>Personally, I&rsquo;m skeptical of the benefit of switching to Swift 6 mode with a hybrid codebase. If you started off with good code, it seems more likely that you&rsquo;ll get some annotation wrong and have Swift 6 trigger an unnecessary crash than that you actually discover a latent concurrency bug that matters. I think it makes more sense to migrate the code to Swift before flipping the switch. Then you can get errors at compile time instead of at runtime.<\/p>\n\n<p><a href=\"https:\/\/hachyderm.io\/@auramagi\/113230496244527810\">Mike Apurin<\/a>:<\/p>\n<blockquote cite=\"https:\/\/hachyderm.io\/@auramagi\/113230496244527810\"><p>I think that Swift 5 mode doing nothing is part of the problem here.\nI&rsquo;ve <a href=\"https:\/\/hachyderm.io\/@auramagi\/113154706505329960\">run in similar issues in Combine<\/a> and was very blindsided by it. There is no way to progressively discover and deal with such isolation violations, just enabling 6 mode and praying.<\/p><\/blockquote>\n\n<p>But this illustrates that porting <em>your<\/em> code to Swift doesn&rsquo;t fully solve the problem, either. It seems that you still have to <a href=\"https:\/\/hachyderm.io\/@auramagi\/113154706505329960\">annotate your closures<\/a> because of Apple&rsquo;s code.<\/p>\n\n<p><a href=\"https:\/\/mastodon.social\/@mattiem\/113154843486127399\">Matt Massicotte<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@mattiem\/113154843486127399\">\n<p>The core problem, in my opinion, was Combine was not updated and that&rsquo;s bananas.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\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\/09\/19\/swift-6\/\">Swift 6<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/07\/22\/swift-6-announced\/\">Swift 6 Announced<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/03\/28\/swift-async-algorithms-package\/\">Swift &ldquo;Async Algorithms&rdquo; Package<\/a><\/li>\n<\/ul>\n\n<p id=\"swift-concurrency-and-objective-c-update-2024-10-02\">Update (2024-10-02): See also: <a href=\"https:\/\/mastodon.social\/@woolie\/113235375787660011\">further discussion on Mastodon<\/a>.<\/p>\n\n<p id=\"swift-concurrency-and-objective-c-update-2024-11-11\">Update (2024-11-11): <a href=\"https:\/\/aplus.rs\/2024\/objective-c-callback-crashes-swift6\/\">Aleksandar Vaci&#x107;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/aplus.rs\/2024\/objective-c-callback-crashes-swift6\/\"><p>After upgrading all my active projects to Swift 6 language mode, I was bound to encounter some head-scratching edge cases. One of the weirdest ones was this runtime crash[&#8230;]<\/p><p>It does not happen when app is compiled and ran in Swift 5 language mode. It is not caught by Swift 6 compiler at build time. But it regularly crashed on app start (in AppDelegate) when a <em>method in Objective-C framework<\/em> that <em>has a callback closure<\/em> is executed.<\/p><\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Paul Haddad: Anyone know why calling the following in a MainActor class\/func MyTest.increment(1) { result in NSLog(\"result=\\(result)\") } crashes (asserts) when building with Swift 6? I get that its not happy that the completion is coming in on another dispatch_queue but it should complain about it at compile time, or ignore it at run time. [&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":"2024-10-01T18:36:15Z","apple_news_api_id":"97ecd0c1-7fe8-411a-9c74-b9be23af5243","apple_news_api_modified_at":"2024-11-11T16:30:43Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAw==","apple_news_api_share_url":"https:\/\/apple.news\/Al-zQwX_oQRqcdLm-I69SQw","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":[1813,880,31,2586,46,30,2598,54,71,2200,901],"class_list":["post-45180","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-combine-framework","tag-grand-central-dispatch-gcd","tag-ios","tag-ios-18","tag-languagedesign","tag-mac","tag-macos-15-sequoia","tag-objective-c","tag-programming","tag-swift-concurrency","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45180","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=45180"}],"version-history":[{"count":5,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45180\/revisions"}],"predecessor-version":[{"id":45734,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45180\/revisions\/45734"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=45180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=45180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=45180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}