{"id":52815,"date":"2026-08-03T16:35:40","date_gmt":"2026-08-03T20:35:40","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=52815"},"modified":"2026-08-03T16:37:15","modified_gmt":"2026-08-03T20:37:15","slug":"nspasteboard-crashes-when-handling-file-promises","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2026\/08\/03\/nspasteboard-crashes-when-handling-file-promises\/","title":{"rendered":"NSPasteboard Crashes When Handling File Promises"},"content":{"rendered":"<p><a href=\"https:\/\/wadetregaskis.com\/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises\/\">Wade Tregaskis<\/a>:<\/p>\n<blockquote cite=\"https:\/\/wadetregaskis.com\/nspasteboard-crashes-due-to-unsafe-internal-concurrent-memory-mutation-when-handling-file-promises\/\">\n<p><code><a href=\"https:\/\/developer.apple.com\/documentation\/appkit\/nspasteboard\">NSPasteboard<\/a><\/code> mutates itself simultaneously from the main thread and the global concurrent <a href=\"https:\/\/developer.apple.com\/documentation\/DISPATCH\">Dispatch<\/a> pool, w.r.t. to its internal type cache. This is surprisingly trivial to reproduce (sample code below) by just dropping, e.g. a file promise (such as by opening a PNG in Preview, revealing the thumbnails sidebar, and then dragging the thumbnail onto the sample project&rsquo;s window).<\/p>\n<p>[&#8230;]<\/p>\n<p>Since this bug causes semi-random memory corruption, it manifests in a large number of ways &#x2013; not all of which are all that helpful.<\/p>\n<p>[&#8230;]<\/p>\n<p>SwiftUI&rsquo;s <code><a href=\"https:\/\/developer.apple.com\/documentation\/swiftui\/view\/ondrop(of:istargeted:perform:)-f15m\">onDrop(of:isTargeted:perform:)<\/a><\/code> method makes no claims or promises as to what thread \/ queue it executes the closure on, and in fact according to the anonymous Apple engineer it <em>never<\/em> executes the closure on the main thread.<\/p>\n<p>Now, while that may be the intent, the reality of that is wrong &#x2013; in my experience it <em>always<\/em> executes the closure on the main thread (which makes a lot of sense to me as drag-and-drop event handling in AppKit has always been on the main thread in practice).<\/p>\n<\/blockquote><p>Apple also told him that <code>NSPasteboard<\/code> is not safe to use outside the main thread, even though that&rsquo;s not documented, it&rsquo;s not <code>@MainActor<\/code>, and Apple&rsquo;s own code does do that.<\/p>","protected":false},"excerpt":{"rendered":"<p>Wade Tregaskis: NSPasteboard mutates itself simultaneously from the main thread and the global concurrent Dispatch pool, w.r.t. to its internal type cache. This is surprisingly trivial to reproduce (sample code below) by just dropping, e.g. a file promise (such as by opening a PNG in Preview, revealing the thumbnails sidebar, and then dragging the thumbnail [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2026-08-03T20:35:44Z","apple_news_api_id":"5c2515ba-5b4b-44a5-829f-79f0a1aefc4e","apple_news_api_modified_at":"2026-08-03T20:35:44Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/AXCUVultLRKWCn3nwoa78Tg","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":[131,69,800,30,2385,2742,1670,71,2200,1812],"class_list":["post-52815","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-bug","tag-cocoa","tag-concurrency","tag-mac","tag-macos-14-sonoma","tag-macos-tahoe-26","tag-pasteboard","tag-programming","tag-swift-concurrency","tag-swiftui"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/52815","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=52815"}],"version-history":[{"count":1,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/52815\/revisions"}],"predecessor-version":[{"id":52816,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/52815\/revisions\/52816"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=52815"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=52815"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=52815"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}