Welcome to the Swift Data Group Lab, our final group lab of WWDC26. I'm Kurt, I'm part of the Worldwide Developer Relations team here at Apple, and I'm joined by engineers and leaders from the Swift Data team: Rishi, David, Thomas, and Ben. In addition to those on screen, there's a team behind the scenes helping with the triage of all your inbound questions. We're excited to answer your questions today about all things Swift data, and we might even dip into core data depending on your questions. If you have code-specific questions or we somehow can't get to your question today, please come to the developer forums at developer.apple.com slash forums, where we'll continue the discussion. If you have a bug or a feature request, go to feedbackassistant.apple.com, as we'll focus on questions today that will help the broadest possible audience. Rishi is going to start us off with a quick overview of Swift data and some additions in our 2027 releases. Thanks, Kurt. And thanks to all the developers out there watching. We're all delighted to be here and cannot wait to answer your questions. But first, let me tell you a bit about Swift data. Swift data is a Swift native persistence framework that allows you to model and persist your app's data across all of Apple's platforms. Swift data provides a broad range of features and lets you write code that is fast, efficient, and safe, with the latest Swift has to offer. And getting started with Swift data is easy. It takes a few steps. First, start with adopting the model macro and decorating the classes you wish to persist. Next, set up your model container with your schema. And then add the model container view modifier, And now you set up your UI with the query macro, and you're ready to go with Swift data. Check out our colleague Matthew Turk's latest Swift data talk, Code Along: Add Persistence with Swift data. The 2027 releases bring an amazing new set of features. The new results observer provides developers an observable fetch experience that can be used with sectioning as well. For types that were previously un-persistible but were codable, now they can be decorated with the attribute macro to be explicitly marked to be treated as codable. And there are so many more new features. To learn all about the latest additions in depth, check out Thomas' talk, What's New in Swift Data. Now let's hear from the developers and take some questions. THOMAS MORTON: All right. Thanks for that, Rishi. And we're looking forward to all your questions. Let's jump right in. And I think this one, Ben, I'll start off with you, and then we can pass it around as we go. But the question is, what's the proper way to use Swift data with widgets and app intents, shortcuts, for example? Is there any sample code, Apple sample code, showing that setup? I don't remember about sample code specifically, but for the multiprocess case where you have a widget and an app or an app intents plug-in, You are going to want to be careful about having one particular app process own the database and do the migration and coordinate that. So if the user starts firing up the widget before they've ever launched the app for a new version of the app, probably want UI to tell them to run the app first. Just like the multi-process coordination case is filled with sharp edges just in general. So we kind of recommend folks have one process as the first among equals, if you will. and then the other processes can read and write to the same database file without too much trouble after that. I think that's in the SampleTrips group app. We have a widget there. Is that right? Yes, there is a widget in the SampleTrips app, and it utilizes the group container so that I can share the data between the app and the widget. Same thing is true for extensions as well. The app intent portion of it is still this year. I know last year I used App Intents to build up a timeline on the widget. So that was an example that we can be used. And to build on Ben's point, for the widgets and extensions, we recommend that you don't include your schema migration plan for those. Okay. And that allows them not to migrate the data. And then you can utilize the error path to bend a UI to the developer or the client and have them open the app to do the migration of the data. And that's where your schema migration plan is leveraged. Makes sense. So the next question, maybe we'll start with you on this, Thomas, and you can pass it around if you'd like. And this sort of gets at the core of what Swift data is great at, but how should developers approach large data sets in Swift data? And maybe we should talk a little bit about what we think of as large in this context, too. Yeah, I think it depends a lot on what dimension it's large in. And if it's like a lot of rows that you have, or if like the rows itself are very large, if you store like large bits of data in each model, or if you just have a lot of models, like if you have like hundreds of models kind of things. So we can kind of go through them. Like the first one is if you have a lot of rows, like you want to make sure you index them correctly to let the database fetch them fast. And the second thing would be that when you fetch them, you don't fetch them all at once. If you put limits on your fetch requests, or otherwise limit how much is being pulled into memory, because otherwise your app might run out of memory. And then the other thing to think about would be if you store very large things, we have an attribute called external storage for data, which actually lets the database not store the bytes of that data block in the database, but in a file kind of next to the database. I don't know if anyone else wants to add on those. I think when you have a large data set that you're ingesting, trying to do them in smaller chunks, and batch insertion would be a good approach. Keep a short-lived model context for that as well, so that you reuse a new model context every single batch that you do. And in that way, as Thomas talked about, you can then leverage the fetch to be efficient in the regular model context that's driving the usage of the data. If I'm using a query in SwiftUI against a data set with a lot of rows is like what are some things I need to think about there is that a matter of having the right predicates I'm pulling the rows that I need or how should I approach that I think you want to make sure at first you limit it like that you put a max that you're gonna fetch out of it and then that you put right bracket that you don't or everything you want to fetch is actually expressed by the predicate and you're not later on kind of have an if clause in your view body for example and then last out it's covered by an index so So, for example, if you-- you can start with instruments. I would first start with a Hitches instrument. If you load everything in there, that's still load nicely. And if it doesn't, there is a persistence instrument that actually tells you what Swift data is doing under the hood and how many objects is being fetched into memory. Thanks. Excellent. I hope that was a useful answer. Oh, this is an interesting one. So Rishi, maybe we'll start with you on this one. What are the best practices around creating sample data? Should we insert it via the preview traits parameter, or should we have an in-memory model, or how should we go about doing this? That's a great question. In the Sample Trips app that we've published, we use the preview traits to seed those with preview data. And that's a great way to kind of make sure that your preview data is expressive and covers a lot of the UI that you want to cover. So you want to have long trip names. You want to have a lot of them. You want to have a diverse group so that you can see how your UI will reflect all of those. And that's a great thing about using the previews and Swift data. And also another trick that you can do in those previews is that you can actually do a query inside the preview and use the result of that query to drive the view as well in the preview. So that's how I would drive it with sample data there. That way you can also use that same preview trait throughout your views in your app and on various ones. and see how they reflect that same data. I think there's another piece about sample data beyond previews that we've talked to before is you also need sample data to test your migrations. And I don't know if maybe, David, do you have anything to add about migration testing and how you do that? Yeah, you really want to have a corpus of data built up, previous stores, to make sure that every one of your models that you've shipped previously also is able to migrate through your new migration. It's not sufficient just to test one model. For example, someone may have had a device that was in a drawer for a year, and they pull that out and update the app. You want to make sure that they're able to migrate as well as the person who's stayed up to date as well. One of the things I really like using agentic programming for is generating sample data. They're pretty good at giving you sort of creating a bunch of instances of models that you can then save off. And they can also be kind of funny sometimes. I like that. Anytime code can make me laugh, that's a good thing. So this is another interesting question. And Thomas, you talked about this in the What's New talk. So maybe we'll start with you on this. and I'm sure Rishi has opinions as one of the authors of this. But when should a developer choose Results Observer over at Query? Are there trade-offs in performance, granularity, control over your architecture? Like, what's the contrast there? Yeah, so Results Observer is really meant for kind of an equivalent to Query outside of a Swift UI view. So if you have some kind of a view model or something where you want to use something like Query, That's where results observer comes in. In terms of performance, they're pretty much the same under the hood. It's just kind of how they're being exposed to you and if they hook into the SwiftUI view lifecycle. And then results observer's history observer is kind of observing a result from a query. So from a predicate, from a fetch description with all the different filtering options you can set on there. as history observers kind of to catch up with a store to kind of catch up with everything that happened from a specific point in time to another one if you want to like replicate it to a different store or keep something else up to date maybe rishi has more to say about this yeah i mean to build on what thomas said he did a great job the results observer really was to give folks a way to have a fetch that was observable outside the view. And currently you can do model context.fetch, but you have to re-fetch when you know that something's changed in the database. And then you have to listen for notifications and observe those. And that takes a pain point away from the developer with the results observer. Now you don't have to worry about catching these notifications and then re-fetching for those. The data refreshes, you can observe the results observer and know when it will update. and then you can react accordingly. And the nice thing about the History Observer is that it's kind of eliminates that same developer pain point of having to listen for notifications and knowing which ones to be notified about. And in both the Results Observer and the History Observer, you can give a predicate, you can give some options to it so that you can be precise about what you want to be notified about and refresh for. I thought there was a really neat example in your talk, Thomas, about using History Observer but limiting it on which-- we say author, but what part of the code is doing the writing. And the example you shared, which I thought was neat, is if the remote server is the thing doing the writing, you don't need to tell the remote server that you just received those changes. Like, you want to watch for the changes that happen locally and communicate those. And I thought that was a neat thing. The other thing that excites me about Result Observer, and this is a chance to throw to one of the SwiftUI talks this year on building SwiftUI into an AppKit talk. In that talk, David Nadova talked about how AppKit and UIKit both support observable objects. And so that combined with Results Observer means you can feed observable data and update your UIKit views or your AppKit views just as efficiently as you were updating SwiftUI views with Query. And so I think it's a really neat way to use the same persistence model across the different UI frameworks. All right. This is a good question. Ben, I'm going to start with you on this one and probably end with you on it. So in my iOS app, I use core data with NS expressions to calculate sums, averages, minimum max, et cetera, directly on the SQL store. With Swift data, there's no equivalent, and the data needs to be loaded into RAM. What's your recommendation on how I handle this? Yeah, so we're trying to move away from the NS predicate elements, and the Swift expression stuff is a little bit newer. So we're aware that this is something we need to add, and a feedback report would be appreciated. For min and max, you can do that with a fetch limit of one and a sort descriptor. Some of the others don't map quite so cleanly. And this is one of those things where feedback reports that have specific use cases. So not I used this in the past and I want to use it in the future, but I use this to accomplish something. This is the thing I'm trying to accomplish. Because those use cases drive the API design. We don't want to be constrained by the API that we've shipped previously. We want to understand what problems people are solving for their customers. Right. You want to know your use cases, which functions are you using, and how you're utilizing them. One of the things to build on Ben's answer is that you can use coexistence. You can have a core data stack and a Swift data stack point to the same data store. So where you don't have that Swift data feature, you can still reach into the core data approach and utilize it from the same data stack. Nice. All right. Digesting this next question. So Rishi, I'm going to start with you on this one. So the question is, when using SwiftUI with Swift data, what's the best way to construct the views state in the Swift data model to allow people to populate non-optional attributes when adding a new model object. I'm not sure I followed all the pieces of that. But I guess it's a question about sort of – I guess the piece I'm not quite following, maybe we have to answer this two ways, is what is non-optional here? Is it non-optional state that has to have a pre-built model, or is it a Swift data model that has non-optional properties? If your Swift data model has non-optional properties, as long as you set them in the initializer, we should be able to persist them and present them to the Swift UI view. Now, if your state is relying on a model to be updated for a non-optional attribute, That seems like it would have to have happened before it was passed to the state in order for it to notice that it's been changed. Or it would have to be updated post-addition to the state so that the observable aspect of it is triggering the updates to the state. Right. Yeah. And I, um, another interpretation of the question, um, uh, sometimes I've had cases where I, um, I needed a single Swift data object that a view was rendering. So it wasn't a list of things, but it was a single object. Um, and, um, I wanted to make sure that object existed before I tried to show something about it. Right. Um, and in those cases, I've done a couple of things and you can tell me if I'm wrong here. I use Swift data, but I'm not the expert. The experts are sitting here. So one thing I've done is fetch that data or find that data by persistent ID or whatever at a parent view, at a super view, and then only instantiated the subview if I already had the instance, right? And so I would make the choice there. It's like, okay, create the view or go with a content unavailable view or some other view that shows I don't have it yet. The other thing I've done, and this is where I'm really interested whether I'm not doing the right thing, but I've added a query with the fetch limit of one, and then if that query's going to return an array, and now I'm deciding what to show in the view based on whether there are zero or one objects. And that seems like a plausible approach. Is there anything wrong with that? There's nothing wrong with it. It's a plausible approach. But you really probably just want to drive your view from the root model that you fetched the first time, right? And if it's not there, then you know to create it and then give something for the viewer to start building off of. Okay. And that's where using a model context and fetching with it instead of using query would give you a cleaner experience because then you could fetch for that one model, have the fetch limit set, and if it doesn't exist, you know to create it then. Okay. Whereas in the SwiftUI query case, you'd have to show an empty list first. And then how does the person create something? But your body would then have to understand that logic rather than moving that logic outside the view and have it presented before. That's helpful. Now I have to go edit some of my code. All right. So this question is about Swift Data and Cloud Kit. So Ben, maybe we'll start with you and we can pass it around. So for Swift Data Plus Cloud Kit, if you publish an app to the App Store without using an app group or a version schema, is there a way to introduce an app group or a version schema without losing your existing data? Does anybody quite follow that? This is not something I've done. So you should be able to add a version schema. Yes, you can add a version schema at any time. If you are going to move it to an app group container, then you're probably going to need to copy the data out of the old container. Because that group container is going to be a different directory. And the way the entitlements work, there's no way to align them. So you're going to have a new container if you use that group container. They'll copy the data over there and start from there. You should just make sure that the model configuration that you've set up before the app container, you have that URL, and you can copy it to the new app container. Now, as far as where the version schema comes in, what you want to do is start up your version schemas with the one that you have now, and you'll start progressively building them up. You can take your current schema and version it now, and that'll be what the database is. And if it happens to be version 3, and you had one, two previously, You can add those later, and then you can pick those stores up as well in the migration schemes. So if someone is moving the data from a non-app group to an app group, is it up to the app to read the data from the previous location and then write it to the app group? Or is there an automatic? That's a great question. So if you use the default model configuration experience, it will move it for you. Oh, wow. If you had specified a URL previously, then you'll need to move it yourself. Okay. Interesting. I don't think we have sample code for that migration path. Am I right about that? Okay. Feedback about an example for that would be welcome, too. I think this is a similar question, but Rishi, I'll let you decide whether it is. So the question is, how does using an app group to define the SwiftData local store affect the CloudKit development and production environment? How can I safely evolve? Yeah. So the one thing you need to make sure is that the CloudKit entitlement that's used is appropriate for all of those reading from that app group. Right? So there's going to be syncing to that CloudKit container on behalf of those apps that can go into that app group. So they all need to have that same entitlement and have that same restriction as well. If you want to share data like that, that's the proper way to make sure that they all have the correct entitlements. Now, if you don't want to sync that data, then you could have that as a separate model configuration and a separate data store that handles sync, and then you have another one that is in the app group container for local sharing of the data. So you could have the app that's doing sync, if I'm understanding right, but then widgets and other things locally would be used in that local app group? Right, and they would use the local version of the data rather than being required to have the entitlement. You don't want to give your widget or extension the entitlement to have for the CloudKit entitlement, which would require them to sync then. So that's where it's a little bit tricky about putting your CloudKit database in the app group. Wow, interesting. And that also would have perf implications because the widget doesn't have time to go sync with the cloud. Right. Widget extensions aren't going to have the same ability to sync like your app does. Sure. And you want that verbose sync experience for your clients. So that's why you have to be careful about where you're putting that sync data and that you put it in the appropriate place and that the right clients are driving the sync, just like migration in the earlier questions. Right. All right. Makes sense. Thank you. I think this is mostly the same question the second half of it I think is interesting though so we'll stick with you Rishi since you've been covering the first half of this but the question here is if you're running in Xcode in a test version and you're evolving the schema, do I need to do anything to avoid creating duplicates in the local store? Between two apps writing to the same CloudKit? Yeah, I think this is a shared CloudKit store. So yes, you'll need to be concerned about such things. but when you're driving through development that's the time to learn and you'll learn about what your apps are going to be using and what your data actually is going to be and then as you progress through that make sure that the schema is aligned in each of the apps so you don't want one app to have a different schema than the other and one's adding a column while the other one's removing you don't want competing migrations so that's one of the things to be mindful of and then the other part is as you're working in development and eventually you'll have to push your schema to production and test it there as well. So you'll want to flip your environments to production on all of the apps so that they all push their schemas up. And that app will then, then they'll be able to sync and see that they actually sync in production as well. Okay. Anybody have anything to add to that? Sync is hard. Yes. Oh, this is a good one and a fast one, but maybe we'll see if we have more about it to dive in. So, Ben, I'll start with you on this, and then maybe you can throw it around. What's the most performant way to count Swift data objects without querying them in the views? You can fetch the count. So there's fetch count, and is that on the model context? Yeah. Okay. So then the follow-up question on this, I guess, is in performance-sensitive parts of my app, I've sometimes found myself manually fetching Swift data models into a cached array and managing updates myself rather than using at query. How do you all sort of think about that tradeoff? Thomas, do you have anything to talk to in this case? - Yeah, sure. So I think, so Corey just does a normal fetch under the hood. The thing that can be a bit tricky in SwiftUI is that the SwiftUI view cycle might fetch more often than you have to. This might be because it's observing something else and the view gets reloaded and that causes a fetch. And if you kind of move it out into your code, you have a bit more control over that. So I think the first thing would be use the SwiftUI instrument to understand when views get re-initialized and when fetches are happening. And you might be able to break it down to smaller views to kind of reduce the number of queries that get executed when views get invalidated. And there's nothing wrong with doing it yourself. Just cache invalidation is another thing you have to worry about. You can also use results observer if you want to use the observable experience and kind of make choices there. But in terms of, like, query doing, like, is too aggressive in refetching. I think instruments, and specifically the SwiftUI instrument, combined with the persistence instrument, gives you a good overview of why SwiftUI decided it's time to refetch your data. And if you disagree with that choice, that's the time to, like, figure out a way to convince SwiftUI that it's not time to refit your data. Yeah, that's fascinating and not something I thought of before. Like, if you've got a SwiftUI view that is invalidating frequently, but you're just measuring where the time is spent, if that invalidation is triggering I.O., it's going to look like the problem is the I.O. But the problem is actually that the view is invalidating too often. It can also be once you bring Swift data into your SwiftUI application, I/O is way more expensive than just reading state objects from memory. So it might have done this before, and you had renderings of views that were unnecessary. But now you kind of amplified this problem, because we actually go out to the disk and fetch your data. And this came up in several of the SwiftUI group labs this week. But if you can break the views down into smaller views that have smaller dependencies, maybe all the query is doing is passing the resulting model objects down to subviews. And then they're doing the work. And now the query doesn't have to rerun, because the objects are consistent. SPEAKER 1: Another one for this one is that they could harness the history observer to know when to do these manual fetches now. And in part, to build on the fetching stuff is that there is also-- not to build on fetch count, there's also a fetch identifier so that you don't have to fetch the full model. You can just fetch the identifiers that have interest for your current batch. And then with the history observer, you can harness the history to know what's actually changed and whether the properties you actually care about have actually changed, and then drive whether that should update and refresh, and then you can update your view of the actor. Nice. I'm learning so much. I think I would say you don't have to use SwiftUI for these queries, right? Particularly if you're outside the view hierarchy, you're on a background actor, you're doing a detached task or something, just do a fetch. It's OK. You don't have to route through a query if it's inconvenient. Right. And the other thing that I was thinking of here, and where I've reached for this lever before myself that our developer is asking about, is when I needed to do sectioning. And now we've got sectioning. So Thomas' talk has a great example of using that. And check out the code snippets along that talk, too, because then you can grab the syntax to make that happen. All right. So Ben, let's start with you on this question. So it's another SwiftDataWithCloudKit question. How can I ensure that synchronization happens as quickly as possible? I'm seeing long legs in my app for data showing up on different platforms. Are there any best practices that I should think about for making sure that synchronization is happening quickly between devices? Well, beyond just testing, which is good, some of these things, there's a lot of logging that we route through. So some of the stuff that the default store is using through core data is pretty verbose if you want to take a look at scraping through the logs. I would say something to be mindful of is the different platforms definitely have different system policies for how aggressive they are. Like, for instance, watches are very concerned about their battery management. So one of those things is they might throttle using the network more unless they're on Wi-Fi. So you have like a cell-based watch. It might not actually do much syncing for a while um and they also are much more aggressive about syncing when they're on puck so there's some stuff like that um and similarly i think um internally we see like if the the phones are really busy you're testing them really hard uh and they start to heat up then they throttle everything um so you'll see in the logs like these thermal warnings right because you have a test rack and they're not quite um quite the same um so there's stuff like that to be mindful of is like you might have missed a push notification for some reason, or the system might have decided that it needed to throttle you for a policy reason. And you'll have to kind of look at the logs to suss that out. But in general, your apps should sync more aggressively if you background them and then bring them back to the foreground in terms of your test workflow. Obviously, that's not something we would expect a customer to have to do. And then if you are in the middle of your testing and like one of your devices isn't getting them, then I think historically that's been like there's something with push notifications that we'd want to take a look at. Like why it's not getting those pushes. The entitlements I think are probably the first thing I would check. It's super easy to have the wrong entitlements, have like different entitlements set up between like your debug and release configurations. A lot of different like little workflows. So I think that's probably the first thing that I would check. Yeah, and when you do run into an issue, grabbing sysdiagnose from all your devices and sending us a feedback report would be beneficial so that we can tell you what happened in that scenario and give you some feedback. Another thing I've run into there when I'm going back and forth between my-- my main Swift data app is a personal app for grocery shopping. And I need to sync to my watch before I go to the grocery store because that's my workflow. I put my list in my phone. And one of the things that I find when I'm testing it is I use autosave and it takes a few seconds before autosave kicks in. And that helps us batch changes and so on. It's like a nice way to do it. But when I'm in a testing workflow and I'm going back and forth really quickly, sometimes I can get ahead of the autosave. The other thing with autosave is if you force quit the app in Xcode by hitting the stop button, the autosave never actually happens. And so you need to make sure that the save is actually happening. Otherwise, the data is not going to get anywhere because it isn't there anymore. You can actually background the apps in the simulator while you're testing, which I think Very few people actually do, but that would be the more customer sort of flow. Yeah. You would get the same notifications. Yep. And in the new device hub, there's a nice home screen button at the bottom Chrome, so it's easier to background. Excellent. All right. Thanks. Oh, this is a great question. I'm sorry. I'm sorry Matthew's not out here to talk about it, but let's start with you, Rishi. What's the best way to store enums, including those that conform to codable, with associated values in Swift data? Well, as long as your values are codable, we should be able to persist those for you. And new this year is being able to build up predicates with the raw representable inumerations. And so Matthew Turk shows us in his new talk with the Code Along and Add Persistence. So that's new this year. And previously, in the years before, you could always encode these because they're codable. If you've run into associated values that are not particularly easy to code-- now they've got custom codings or complex NS types underneath-- then consider decorating it with a new dot codable flag for the attribute macro. This will then use the coding conformance that it's already got and will allow the developer to use their custom coding to how we save this as data. And then it should allow you to store a whole slew of new types that we haven't been able to persist for you before. I know when we were working on Thomas' talk, we had a lot of conversations around when not to use the codable attribute. Thomas, you want to talk about that a little bit? Yeah, sure. So one of the disadvantages of, like, so first, when you have associated values, this is something Swift data can't, like, map into a schema for you. It's kind of not in, like, it doesn't know how to break it down. But you could. Like, you could still make a model that stores these different cases with some kind of relationship to another thing, and then have an accessor that returns a enum of some kind, where you kind of package it back up and kind of bend that to your views as the API, but under the hood you are kind of decomposing it into Swift data models, and that is a bit more work than Codable, but it does allow you to express it in predicates or in sorting and those kind of things. - Yeah, that's the downside of Codable. It's like, it's raw data as far as we're concerned. - And it depends a lot. Like if you are, like if you don't have access to the Codable type itself, then to like implementation that you can't kind of like reason about it how it should be stored in a database then codable is the right choice but otherwise if you want to query by it you might want to consider actually modeling it in a swift data model and then get the full power of like predicate and sorting and those kind of things thomas's talk has a really cool example of um storing map uh kit data um in in the database and we can you know sort of he demonstrates how when the data changes we can detect that and update the the map, but the map kit type isn't a type that, as developers, we own. We're at Apple, so we can go peek at it, but we can't change it. Right. And I think measurement is one that came often up in the past, that is, in the foundation framework, that we can't store natively right now, so it's codable to kind of store it. I want to pivot away from our incoming questions a little bit and do a little bit of color and some background on what you all work on. So maybe we'll start with Rishi and sort of go around the horn. What pieces of Swift data and core data and persistence frameworks do you work on now? What have you worked on in the past? Okay. So I work on Swift data in general. I work on a lot of the aspects of it. It all started way back with the attribute and relationship macro. It's like where this project started way back when. It was kind of like... I mean, they weren't even macros. They weren't even macros. They were, you know, explorations. Pre-macro? Yeah. Pre-macros and using property wrappers to kind of build up an NSManageObject model from your code. And that kind of was the first step in this whole process. Outside of that, I've just been on the coordinator team for many years before that. did a lot of things in migration and persistent history and then just a lot of other many things before that at Apple, but yeah, it's been a great time. Nice. How about you, David? So I've been on Core Data and Swift Data for about six years now. I actually ended up taking over a lot of the migration-related things and history-related things from Rishi. but an additional I guess I also took over the spotlight portion too so you just handed me all your things so you could go work on Swift Data so yeah I get to look at your code all the time and shake your fist Rishi why did you do this to me how about you Thomas I'm also an engineer on the Swift Data and Core Data team and I also maintain CQlite for all Apple platforms. This year I've been mostly working on performance optimizations for both Core Data and Swift Data, but I'm kind of all over the stack wherever I'm currently needed. Ben? Yeah, so I'm the engineering manager for these projects. I've been working on various ORMs for a long time now, back when the dinosaurs roamed the earth and all with EOF and Core Data and Swift Data. EOF Enterprise Object Framework. We all know it so well. There's a song about it actually. There is. It's a great song. I'm not sure if that's on so the album is by James Dempsey and the Breakpoints and lots of great and funny kind of Apple nerd humor. So check that out. This is not a paid endorsement. So thanks for sharing that. And that gives you some sense of sort of the breadth of the kind of things we could cover if folks have additional questions in those areas. But Ben, I'm going to throw this next one at you. Excellent. What are the best practices for using multiple model contexts when does it make sense to split work across separate contexts or when should I keep everything in a single context how much time do we have 19 minutes perfect all right so the last question of the afternoon so this is gonna kind of be dependent on your objective you know how if this is interacting with the view, then that's going to be a consideration. But if we sort of pause it for a second, like you just have a work set you're doing in the background, so there are no view implications, and like how do you divvy that up? A lot of that is going to be trying to keep things coherent within a transaction, right? So the model context itself is sort of a working set, and all the changes you accumulate in that model context are going to go into a single transaction together when you save. So to the database and you don't want to have too much cross coordination if you want to geek out we could talk about like Amdell's law which is like the more the more interaction you have between these actors that's all friction right and the communication between them is in some ways some serialization and so you're gonna be like losing performance every time you have to hop between all these different things or every time any of these these contexts have to communicate with each other or coordinate their work there should be some kind of synchronization or semaphore or something actor and so that's all like a little bit of friction and so you kind of want to split up your work in a way that they can be just the right amount of independent from each other that they can all be you know go on full bore and and get stuff done and then they minimize the amount of time they have to talk to each other to coordinate the work that they're each doing so That's sort of like the super high-level aspect of that. I think on most of the platforms we ship today, like on phones and iPads and watches and stuff, there probably aren't going to be too many contexts that you want to use at the same time, simply because it's very easy to write a program that is way more concurrent than the NAND system can handle. So typically, if you start, and you should, start measuring your performance and benchmarking what you're doing, the NAND system is going to be like your primary bottleneck. And no matter what you do with tasks or dispatch cues or swift concurrency or async, right, like you can only do so much with the NAND hardware on each device. And some of them are substantially more powerful than others, right? You talk about some of the SSDs you can get onto a Mac costs more than an entire phone. It costs 10 times as much as a HomePod. So there's a pretty vast disparity of capabilities there. So that's the important thing, and that drives a lot of the decisions in our own API design of how do we end up giving you this object graph that you're modeling and it feels like you're working with a nice Swift code, but underneath the covers, we're peddling as fast as we can to cheat and kind of avoid doing I.O. unless we really, really have to. And so there's a lot of injecting features out from underneath you in ways to try to give you this illusion of a nice, clean app design while at the same time being sensitive to the fact that you might want to run it on a watch or a Mac Studio or something in between. Yeah, so you can use as many model contexts as you want The escalate database itself will handle one writer at a time. We're using the wall journaling mode For a lot of the work Here that means like multiple writers can be going at the same time Sorry multiple readers can be going is the same time as a writer Which I think people tend not to quite follow because it's not that's not like a reader writer lock It's multiple readers and a writer. And, yeah, like a lot of performance-driven testing and measurements to kind of do that. I think compared to what I see folks talk about in, like, literature and just sort of, like, dev forms and stuff, folks tend to be surprised that they can actually get faster performance with less concurrency. It's not like super popular, but, right. So if you're scaling up on a Mac Studio, that's one thing. But for a lot of phones and iPads and other things, you can add a lot of complexity to make things a little bit faster. But they use more memory. They're using more I.O. There's definitely like diminishing returns to adding a lot more concurrency. And I think we kind of like nudge to allowing some of the connection pulling work we do, like two or three things going at a time and start throttling after that just because nothing underneath the app can really keep up. Thomas, is there anything on the SQLite layer there that you want to add about single writer? Single writer. So first I would say on what Ben said, like when you were benchmarking, Benchmarking is surprisingly hard. There's kind of caches all the way down. Like CQLite has a page cache. The operating system has a cache for file system pages. Maybe the NAND controller even does some caching. So just make sure you have a reasonably sized data set. It's surprisingly hard to do good benchmarking on, OK, this time it's faster, but oh, I just hit a cache. Like I just got served out of the page cache from CQLite versus actually doing I/O. So having the I/O instrument can help you to kind of see what was actually going on. And then, yeah, pretty much what Ben said, like concurrency runs into limits pretty quickly, specifically on I/O bound tasks. And there is significant cost when you have to jump over concurrency bound race and have to synchronize things. Does this, Rishi, to you, we talked a little bit earlier about small, short-lived contexts. How does that enter into this question? I mean, they keep your memory footprint small, right? So you're not going to keep too much accumulating in the model context. But to build on Ben's point is you don't want to have a model context doing a ton of work and staying around and keeping that work in memory. You want to digest it and save it and let other contexts take a small piece of that and continue to do the work. You don't want to take a large object graph and hydrate it all into memory. We just want to reduce your memory footprint and kind of put what we don't need away and have that stored on file and then pull it up when we actually need it. Matthew had an analogy in his talk about this that I just love. It's like fetch the data you need to work with. And his analogy is like, you wouldn't go to the reference desk at the library and say, bring me every book, and then I'll go through and find the ones by my favorite author. You would say, bring me the books by my favorite author. It's like, work with a subset of the data. And then when you're done with it, put it back or let it go. Right. All right. The next question, I think we'll, whoops, I checked it off. Actually, I'll go back to you on this, Rishi. How do I know if I need to paginate my queries instead of fetching all records of a given type? I see. So to kind of build on what Thomas has already talked about, using instruments here is really going to help. You want to see if you hit the hitches in your UI. You want to see if you're pulling in a lot of objects you're not actually displaying. So you want to, like, that's kind of where, you know, setting the fetch limit to kind of, like, I know I only need the top 1,000 here or top 100 because I'm only going to show so many in this view. So restricting that really helps and keeps the performance down and will keep you from having to patient your queries. Another interesting throw to another SwiftUI talk this year is Renz did a talk on lazy stacks. And where this enters in is he has a neat example there of a view that only loads in a few of the items, just a few more than it needs to show, but then it adds a last view at the bottom of the stack that is just a progress spinner with an on appear that updates the paginating. And so you scroll, that one appears, and then it automatically fires and goes and gets more data. And so I think it's a neat pattern to see how you can approach that sort of thing. Cool trick. Yeah, very cool trick. So the next question, maybe Thomas, I'll start with you on this one. Is it a good idea to combine Swift data with a cloud backend for sync, or is that an anti-pattern? My app is using a Firestore today, plus a local JSON mirror for the local storage. And is that a place where we could apply Swift data? Yeah, definitely. So if you already have it in a bunch of JSON files, you can just start by implementing a store for that. And kind of, we have, I think, one of the example apps use the JSON store. I think Lovina did a talk a couple years ago about implementing custom data stores and a JSON store was an example. And kind of keep what you have and kind of make Swift data work with your existing storage mechanism. Another approach would be to move everything into Swift data and use something like History Observer to know what to send to your cloud backend if you're not using Cloud Kit, which works out of the box. So kind of what pieces of Swift data you want to use. Obviously, when you build a JSON store, it doesn't handle indexing for you. You can build something clever yourself, but Swift data is not going to help you out of the box for that. So those are kind of the two things I can think about that. Maybe Richie has other thoughts on it. I mean, one thing we've seen a lot of clients do is leverage Swift data for their local cache. and that would be the local JSON mirror here in this case. And then from there, leveraging history and history observer to know what data has changed and what to sync back to your backends. And that's a very common client experience that we've seen. But leveraging what Swift data can do for you for that local cache really gets you pretty far, so you don't have to reimplement a lot of that portion of it. But the custom data store experience also lets you harness a lot of the Swift data framework, and you can implement your own custom data store and store that data in any format and in any particular security model you may need to follow. Is that sort of maybe a step on a pathway also? Like I could use a JSON store, start adopting more of Swift data, and then once I've made that migration in my app, then I could think about using a SQLite store or other kind of store. Is that going to be a path I can follow as an engineer, or should I jump to what I want? Definitely. You should start out with where your data lives now and start leveraging the UI experience that sort of data lets you harness, but then also the model context as a scratch space for working on your models and saving and kind of giving you that ability to remove the technical debt that you've built in your app for trying to build your own persistence layer. Right. Yeah. Always, when I was an indie dev, we always looked forward to DubDub because we would get to see what of our own code we could delete because there was now an API to support it. And so that was always a good thing. File those feedback requests. Yeah, that's right. My use case is I want to delete this code. Please implement it for me. So maybe we'll start with you on this question, David. It's about database migration. what's the proper way to know if a database migration is required or not? So a use case is showing a loading screen while migration is running or preventing widgets of the app from accessing the data store while the migration is in process. How should people think about that? Yeah, so if you were to have your widget essentially load an unversion schema, the loading would actually error out, and then you can use that to know whether or not you need to migrate the database. Now, as far as how to update the user or let the user know in UI, you'd say, you know, you need to go back to the main app to essentially, you know, migrate the data. And then you can write the version that the app has migrated to into some sort of user default. And then when the widget loads again, you would use that as the version that it should open the persistent container with. And again, you shouldn't try to do any sort of migrations from your widget because, like we mentioned before, it's not going to get the runtime resources that an app would. But as far as the ability to show a loading screen while the app is doing migration, it would be possible to essentially calculate how many stages you have and then override the did migrate handler for a custom migration stage. And then you just do the math between which stage you're on and how many are left. It's not like a wall time, but it at least would give you, I have this many more stages left to migrate. But I would say that if you're looking for something a little bit easier to use or anything like that, you should file a feedback report so we can consider that and see what we can do. - Yeah, it'd be super fun if the Foundation Progress API could hook in, for example. I guess I have to go file a feedback report when we're done. The other thing I think that wrapped up in that question is kind of a human interface design question is like, what is the experience you want the people using your app to have while a migration is happening? Like how much of the functionality can you give them while the local store is being migrated? And so that's something to think about and look what different apps do, because just getting a loading spinner is not a great way to drive engagement. People are going to bounce off that. So if you can make that useful in other ways while that migration happens, that's also a great thing to be able to do. Yeah, you can utilize what David was saying and tell them what you're actually doing in each of those migrations and what you're adding and what features that you're enabling for them with these subsequent migrations. Yeah, that's a great point. Like if you look at the first time you install a macOS update and launch mail, you get a rundown of the new features in mail while it updates. Exactly. Nice. All right. Keep an eye on the clock. We're getting near the end of the hour, but let's take a couple more before we need to wrap. So, Rishi, I'm going to send this one your way to start. What's the best way to do grouped queries, like grouping results by one property of a model and then display the results in sections. Okay. Well, the new results observer and query now support section by. So now you can provide a key path for one of the persistent properties and section your data by that, which is a great way to kind of leverage this group by experience. So in Thomas' app, he shows us in his talk in sample trips, and it's a great way to now build up your UI and show some nice details for those sections and then have the elements that are part of that section already calculated for you. You don't have to go and redo those in memory yourself. Right. Yeah. I need to migrate my grocery shopping app to use that because I'm grouping by aisle in the store. And so I'm taking a couple of weeks vacation after DubDub, and one of the things I do is work on my grocery app. You've got to go up to your personal apps, yeah. All right. I think we can take one more question, and then we'll need to wrap. Maybe two, because I like the one after this one, too. But let's see how we do. So I'm going to throw this one to you, Ben, first. What's the recommended way to handle concurrency when a model actor needs to return model data? Yeah. So the models themselves aren't sendable, and their interaction as a graph in the model context, they're reference-based classes, so they can't really be made sendable in any sane way. So you're going to want to pull out the data that you want to send over to your other actors, and that could include the persistent identifier so that they know where this chunk of data came from. You could pull out deltas in a dictionary or a struct or something like that. But, yeah, that's going to be like, you know, you pass basically a copy over to. I think that actually, to me, echoes back to your earlier point about how much concurrency you're trying to leverage and the cost of communicating across those concurrency domains. It does. The challenge here is the sendable boundary is really quite strict. And so, yeah, you're going to need to pass something over that's gentle with that. and I think one of the challenges people get into is they want to start passing relationships or other things across and You get in this position where if you only pass parts of the graph Across and then now like the receiver doesn't have the full set Of whatever your first model actor was looking at and can be Foyablesome yeah, and and foiblesome And and you know then there's things like maybe the object wasn't fully hydrated and it gets hydrated on one side and yeah, so there be dragons. So I'm afraid that is the end of our time today. So this is the end of the group lab. We're so thankful that you all joined us today. I hope you found this really helpful. Big thanks to our panelists and to the folks working behind the scenes to make this happen. If we didn't get to your questions, please visit the developer forums at developer.apple.com slash forums and visit feedbackassistant.apple.com to file any bugs or feature requests. And again, we love use cases, how you're using the tools to build apps for your customers. Speaking of feedback, you should receive an email with a survey link to let us know about your experience with WWDC. We'd love to incorporate your feedback into future events. Again, thanks for joining us. I hope you've had a great WWDC.