{"id":44227,"date":"2024-07-26T14:10:39","date_gmt":"2024-07-26T18:10:39","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=44227"},"modified":"2024-09-30T09:53:28","modified_gmt":"2024-09-30T13:53:28","slug":"swiftdata-and-core-data-at-wwdc24","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2024\/07\/26\/swiftdata-and-core-data-at-wwdc24\/","title":{"rendered":"SwiftData and Core Data at WWDC24"},"content":{"rendered":"<p><a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10137\/\">What&rsquo;s new in SwiftData<\/a>:<\/p>\n<blockquote cite=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10137\/\">\n<p>SwiftData makes it easy to add persistence to your app with its expressive, declarative API. Learn about refinements to SwiftData, including compound uniqueness constraints, faster queries with <code>#Index<\/code>, queries in Xcode previews, and rich predicate expressions. Join us to explore how you can use all of these features to express richer models and improve performance in your app.<\/p>\n<p>[&#8230;]<\/p>\n<p>You can use the new <code>#Unique<\/code> macro to tell SwiftData which combinations of your model&rsquo;s properties must always remain unique in the model data. When two model instances share the same unique values, SwiftData will perform an upsert on collision with an existing model!<\/p>\n<\/blockquote>\n<p>It seems there is still no way to control the merge policy.<\/p>\n\n<blockquote cite=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10137\/\">\n<p>New in iOS 18 is the ability to use Foundation&rsquo;s new <code>#Expression<\/code> macro to build complex predicates easily! Expressions allow for reference values that do not produce true or false but instead allow for arbitrary types.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10138\/\">Create a custom data store with SwiftData<\/a>:<\/p>\n<blockquote cite=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10138\/\"><p>Combine the power of SwiftData&rsquo;s expressive, declarative modeling API with your own persistence backend. Learn how to build a custom data store and explore how to progressively add persistence features in your app.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10075\/\">Track model changes with SwiftData history<\/a>:<\/p>\n<blockquote cite=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2024\/10075\/\"><p>Reveal the history of your model&rsquo;s changes with SwiftData! Use the history API to understand when data store changes occurred, and learn how to use this information to build features like remote server sync and out-of-process change handing in your app. We&rsquo;ll also cover how you can build support for the history API into a custom data store.<\/p><\/blockquote>\n\n<p>See also: the <a href=\"https:\/\/developer.apple.com\/documentation\/swiftdata\">SwiftData documentation<\/a> and <a href=\"https:\/\/developer.apple.com\/documentation\/updates\/swiftdata\">updates<\/a>.<\/p>\n\n<p>I was not impressed with the introduction of SwiftData <a href=\"https:\/\/mjtsai.com\/blog\/2023\/06\/12\/swiftdata\/\">last year<\/a>, and this year was also a disappointment. It seems like they are working on the wrong things and not communicating their vision for the future of SwiftData and how it will work with (or supplant) Core Data. It&rsquo;s not clear whether this is a strategy and execution problem or primarily a communication problem, but it is leaving me unsettled.<\/p>\n\n<p>Last year, SwiftData was very buggy and incomplete. This year, they didn&rsquo;t talk about fixing the basics but rather about advanced and somewhat niche features like custom stores and history tracking. Last year, there was no integration between SwiftData and Core Data <a href=\"https:\/\/fatbobman.com\/en\/posts\/use-core-data-features-in-swiftdata-by-swiftdatakit\/\">identifiers<\/a> and predicates. There still seems to be none. The only mention of Core Data was that if you&rsquo;re using <a href=\"https:\/\/mjtsai.com\/blog\/2019\/08\/21\/persistent-history-tracking-in-core-data\/\">Core Data&rsquo;s persistent history<\/a> you can now migrate to SwiftData history. There were no Core Data sessions at WWDC 2024.<\/p>\n\n<p>So it remains unclear whether it&rsquo;s even <em>planned<\/em> for SwiftData to be able to do the things that Core Data can. Is this going to be <a href=\"https:\/\/mjtsai.com\/blog\/2024\/07\/24\/swiftui-at-wwdc24\/\">like SwiftUI<\/a> where five years later they are still reimplementing features the previous framework has had for decades? It&rsquo;s also unclear whether Core Data even has a future. Last year was encouraging in that both frameworks got equivalent new features at the same time. This year, Core Data got nothing, even though there&rsquo;s a long list of outstanding feature requests, limitations, and bugs. Is this year an aberration? Or is Core Data in maintenance mode while they focus on SwiftData? It really does not feel good to have multiple apps and 20 years of code built on a framework that Apple isn&rsquo;t talking about.<\/p>\n\n<p>The point of building on system frameworks is that they&rsquo;re in theory better supported than random open source projects. But now the future is uncertain, and it&rsquo;s closed-source so that no one else can make the fixes or improvements that Apple won&rsquo;t. I <em>can&rsquo;t<\/em> rewrite my Core Data code in SwiftData because it&rsquo;s so limited. Even if there are major improvements next year, they will be locked to macOS 16 and later, so it will be a long time before I can deploy code that uses them. At least right now, it&rsquo;s not <em>worth<\/em> rewriting my code for a third-party framework. Core Data isn&rsquo;t announced dead yet, and many other apps use it, so hopefully it&rsquo;s safe to assume that Apple won&rsquo;t let it break too badly. But if I were starting a new app today I would seriously consider whether it makes sense to be on the <a href=\"https:\/\/mister.computer\/@kyle\/112848233233485624\">Apple train<\/a>. There are some interesting alternatives such as <a href=\"https:\/\/github.com\/groue\/GRDB.swift\">GRDB<\/a>, <a href=\"https:\/\/github.com\/Lighter-swift\">Lighter<\/a>, and <a href=\"https:\/\/github.com\/marcoarment\/Blackbird\">Blackbird<\/a>.<\/p>\n\n<p><a href=\"https:\/\/chaos.social\/@donnywals\/112599091238897294\">Donny Wals<\/a>:<\/p>\n<blockquote cite=\"https:\/\/chaos.social\/@donnywals\/112599091238897294\"><p>It feels like Apple wanted to do more with SwiftData but they ran out of time. Surely being able to write your own persistence layer wasn&rsquo;t the one thing they really wanted to ship, right?<\/p><p>There&rsquo;s so much that developers were hoping for this year but we didn&rsquo;t get any of it.<\/p><p>Makes me wonder whether the plan was to replace Core Data entirely and go from there but they couldn&rsquo;t get it done so they gave us this instead?<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/hachyderm.io\/@groue\/112599442185632839\">Gwendal Rou&eacute;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/hachyderm.io\/@groue\/112599442185632839\"><p>I mostly see people hitting walls with SwiftData (limited support for non-optional relationships and ordered relationships, predicates that won&rsquo;t compile, no dynamic predicate, change reactivity that does not work, etc.) And I&rsquo;m all but charmed by its mutable model classes anyway. Maybe it would be interesting to have a GRDB store, so that one can workaround SwiftData limitations with a robust database API. But then, why not use the robust API in the first place?<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/www.donnywals.com\/newsletters\/j2fNQNRpmyk54diFO9Rldg\/\">Donny Wals<\/a>:<\/p>\n<blockquote cite=\"https:\/\/www.donnywals.com\/newsletters\/j2fNQNRpmyk54diFO9Rldg\/\">\n<p>Sadly, none of these limitations are the result of Core Data not supporting them. They&rsquo;re a result of SwiftData&rsquo;s interface not exposing these features. So even if you ship a custom persistence layer you&rsquo;re limited by what SwiftData supports. And that&rsquo;s not much.<\/p>\n<p>For now, SwiftData remains a framework that&rsquo;s only useful for small, simple apps that don&rsquo;t need much in terms of features.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-036\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-036\"><p>The latest version of SwiftData was surprisingly impactful. Although it might seem that few features were added on the surface, the substantial underlying adjustments were revolutionary. Considering that SwiftData was only founded a year ago, such changes were particularly unexpected. Stability remains a challenge, as seen in the first test version, which may disappoint many developers who had high expectations for SwiftData. However, after in-depth analysis, I believe there is sound reasoning behind these significant adjustments. The new version of SwiftData has almost achieved decoupling from the Apple ecosystem, and once it adds an independent default storage implementation, it will have all the makings of a cross-platform open-source framework. While this is just my personal hope, given Apple&rsquo;s increasingly open approach to the community, the potential for this transformation is significant. If realized, the current waiting and effort will undoubtedly be worthwhile.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@helge\/112642579242918976\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/112642579242918976\">\n<p>What they apparently didn&rsquo;t do is fix the actual issues it had, like missing Observation refreshes &#x1F643;<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/chaos.social\/@dasdom\/112642139387539587\">dasdom<\/a>:<\/p>\n<blockquote cite=\"https:\/\/chaos.social\/@dasdom\/112642139387539587\">\n<p>Is SwiftData dead already?<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@SENTINELITE@moth.social\/112604791432634373\">SENTINELITE<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@SENTINELITE@moth.social\/112604791432634373\">\n<p>I picked up SwiftData last year for my app, &amp; it&rsquo;s been an excruciatingly painful experience. Cloud syncing doesn&rsquo;t always work, duplicate entries, etc.<\/p>\n<p>Things didn&rsquo;t really improve this year, although some internal things did, because some bugs have been cleaned up.<\/p>\n<p>But&#8230; I just feel lost. I need the foundation of the app to be working, so I can dive into other APIs (Widgets, shortcuts, etc), but it&rsquo;s been an uphill battle.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@helge\/112677126918818254\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/112677126918818254\"><p>Something disappointing in SwiftData is that it doesn&rsquo;t make use of the static nature of the macro(s). The macro can&rsquo;t see the full schema like Lighter does, but it could still statically generate a ton, e.g. a static snapshot struct for the backing data. Or predefined indices for quickly binding the snapshot to the SQLite API (or really any).<\/p><p>Instead we get custom backends. Is anyone going to use this, for anything? Maybe they&rsquo;ll eventually release it as FOSS for server side?<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@helge\/112677770059572062\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/112677770059572062\"><p>IMO the ability to replace the backend is exactly the wrong direction to go &#x1F937;&#x200D;&#x2640;&#xFE0F; (for an embedded persistence framework). Specific backends should have specific implementations of the SwiftData API, not yet another layer in between that does abstractions which likely won&rsquo;t fly. Remember the LDAP EOAdapter&#8230;<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/moth.social\/@SENTINELITE\/112815400249546703\">SENTINELITE<\/a>:<\/p>\n<blockquote cite=\"https:\/\/moth.social\/@SENTINELITE\/112815400249546703\"><p>Adopting SwiftData&rsquo;s new History stuff, causes my model(s) to crash, as you can&rsquo;t query embedded structs. [&#8230;] I&rsquo;m THIS close to rewriting the back-end. Gah.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/moth.social\/@jonduenas@mastodon.world\/112818929201852007\">Jon Duenas<\/a>:<\/p>\n<blockquote cite=\"https:\/\/moth.social\/@jonduenas@mastodon.world\/112818929201852007\"><p>Yeah, it&rsquo;s just, credit where credit is due, adding CloudKit to SwiftData &ldquo;just worked&rdquo;. With bugs and crashes, sure. But barely any extra effort on the developer&rsquo;s end to do the actual syncing.<\/p><p>I&rsquo;m actually considering whether I should just move rolling my own backend with something like Firebase or Supabase and make local storage more of a cache than the source of truth.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/iosdev.space\/@babbage\/112605538816290770\">Duncan Babbage<\/a>:<\/p>\n<blockquote cite=\"https:\/\/iosdev.space\/@babbage\/112605538816290770\">\n<p>Would love any leads on this: setting [Core Data&rsquo;s] <code>.fetchBatchSize<\/code> causes entire collection to be immediately traversed [by SwiftUI].<\/p>\n<\/blockquote>\n\n<p>With Core Data and AppKit there are several possible solutions. If you fetch whole objects, the batching &ldquo;just works,&rdquo; so long as you <a href=\"https:\/\/mjtsai.com\/blog\/2021\/03\/31\/making-nsfetchrequest-fetchbatchsize-work-with-swift\/\">stick to one store and avoid converting<\/a> the <code>NSArray<\/code> to a Swift <code>Array<\/code>. With multiple stores, or to save memory, it&rsquo;s straightforward to just fetch the sorted IDs and bring the objects into memory as needed.<\/p>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/06\/24\/swiftdata-vs-realm-performance-comparison\/\">SwiftData vs. Realm Performance Comparison<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/06\/13\/catalyst-not-at-wwdc24\/\">Catalyst (Not) at WWDC24<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/06\/04\/swiftdata-issues-in-macos-14-and-ios-17\/\">SwiftData Issues in macOS 14 and iOS 17<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/05\/28\/dynamic-swift-predicates-in-macos-14-and-ios-17\/\">Dynamic Swift Predicates in macOS 14 and iOS 17<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/01\/09\/swiftdata-fetching-pending-changes\/\">SwiftData Fetching Pending Changes<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2023\/06\/12\/swiftdata\/\">SwiftData<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/08\/26\/lighter-swift\/\">Lighter.swift<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/07\/07\/model-view-controller-store-reinventing-mvc-for-swiftui-with-boutique\/\">Model View Controller Store: Reinventing MVC for SwiftUI With Boutique<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2019\/08\/21\/persistent-history-tracking-in-core-data\/\">Persistent History Tracking in Core Data<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2016\/07\/28\/grdb-swift-sqlite-orm\/\">GRDB Swift SQLite ORM<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2014\/10\/13\/sqlite-swift\/\">SQLite.swift<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2014\/01\/31\/yapdatabase\/\">YapDatabase<\/a><\/li>\n<\/ul>\n\n<p id=\"swiftdata-and-core-data-at-wwdc24-update-2024-07-29\">Update (2024-07-29): <a href=\"https:\/\/mastodon.social\/@helge\/112854498435796193\">Helge He&szlig;<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@helge\/112854498435796193\">\n<p>SQLite.swift is really slow. If you want a popular good one, rather get GRDB.\nNeither provides what SwiftData has though, i.e. an EditingContext.<\/p>\n<\/blockquote>\n<p>I do think the context is a useful feature&mdash;another reason I&rsquo;ve liked Core Data.<\/p>\n<p>When I need direct SQLite access, I&rsquo;ve been using <a href=\"https:\/\/github.com\/stephencelis\/SQLite.swift\">SQLite.swift<\/a> because it seemed simpler and smaller than GRDB. It like it, but it is indeed very slow due to Swift and GCD overhead within the framework. I was able to 3x the query performance by making some minor changes to reduce overhead and by manually unpacking the result rows. Now nearly all the time is spent in SQLite itself.<\/p>\n\n<p id=\"swiftdata-and-core-data-at-wwdc24-update-2024-07-31\">Update (2024-07-31): <a href=\"https:\/\/useyourloaf.com\/blog\/swiftdata-indexes\">Keith Harrison<\/a>:<\/p>\n<blockquote cite=\"https:\/\/useyourloaf.com\/blog\/swiftdata-indexes\">\n<p>First the bad news, this [SwiftData Indexes are] an iOS 18 feature that does not back deploy to iOS 17. <\/p>\n<p>[&#8230;]<\/p>\n<p>Note the subtle difference between adding an index for the individual properties or a compound index (I can see myself getting this wrong)[&#8230;]<\/p>\n<\/blockquote>\n<p>I don&rsquo;t see how to index the entity column.<\/p>\n\n<p id=\"swiftdata-and-core-data-at-wwdc24-update-2024-09-25\">Update (2024-09-25): <a href=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-050\">Fatbobman<\/a>:<\/p>\n<blockquote cite=\"https:\/\/weekly.fatbobman.com\/p\/fatbobmans-swift-weekly-050\">\n<p>Compared to minor UI-level glitches, deeper crash issues have caught some developers off guard. Apps using SwiftData have seen a noticeable increase in crash frequency, undoubtedly disappointing many developers who had high hopes for the new framework. Even more surprising is that the <code>fetchedProperties<\/code> feature in Core Data on iOS 18 can also cause crashes&mdash;quite unexpected for a framework that&rsquo;s known for its stability.<\/p>\n<\/blockquote>\n\n<p id=\"swiftdata-and-core-data-at-wwdc24-update-2024-09-30\">Update (2024-09-30): <a href=\"https:\/\/x.com\/jazzychad\/status\/1815890026083017033\">Chad Etzel<\/a>:<\/p>\n<blockquote cite=\"https:\/\/x.com\/jazzychad\/status\/1815890026083017033\"><p>well.. i&rsquo;m trying to use SwiftData but the \n<code>@Query<\/code>\n wrapper spins the CPU 100%+ on the main thread&#8230; lots of <a href=\"https:\/\/forums.developer.apple.com\/forums\/thread\/747858\">other ppl reporting this<\/a> .. seems very bad<\/p><\/blockquote>","protected":false},"excerpt":{"rendered":"<p>What&rsquo;s new in SwiftData: SwiftData makes it easy to add persistence to your app with its expressive, declarative API. Learn about refinements to SwiftData, including compound uniqueness constraints, faster queries with #Index, queries in Xcode previews, and rich predicate expressions. Join us to explore how you can use all of these features to express richer [&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-07-26T18:10:44Z","apple_news_api_id":"a2574997-b417-4eed-b8f7-8a2b0946f305","apple_news_api_modified_at":"2024-09-30T13:53:32Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAABg==","apple_news_api_share_url":"https:\/\/apple.news\/AoldJl7QXTu2494orCUbzBQ","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":[109,143,1412,31,2586,30,2598,74,991,71,2404],"class_list":["post-44227","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-coredata","tag-database","tag-grdb","tag-ios","tag-ios-18","tag-mac","tag-macos-15-sequoia","tag-opensource","tag-open-source-software","tag-programming","tag-swiftdata"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/44227","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=44227"}],"version-history":[{"count":8,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/44227\/revisions"}],"predecessor-version":[{"id":45149,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/44227\/revisions\/45149"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=44227"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=44227"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=44227"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}