App Services • 1:01:30
Join us online for a deep dive into WWDC26 with Apple engineers and designers to ask questions, get advice, and follow the discussion about the week’s biggest SwiftData announcements. Conducted in English.
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it may have transcription errors.
Welcome to the SwiftData Group lab. Our final group lab of WDDC26. 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 SwiftData 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 SwiftData. 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/forums where we’ll continue the discussion.
If you have a bug or a feature request, go to Feedback assistant.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 SwiftData and some additions in our 2026 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 SwiftData.
SwiftData is a Swift native persistence framework that allows you to model and persist your app’s data across all of Apple’s platforms. SwiftData 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 SwiftData is easy. It takes a few steps. First, start with adopting the model Mac Pro and decorating the classes you wish to persist. Persist. Next, set up your model container with your schema and then add the model container view modifier.
And now you’re. Set up your view UI with the query macro and you’re ready to go. With SwiftData, check out our colleague Matthew Turk’s latest SwiftData talk on ad persistence with SwiftData. The 2026 releases bring an amazing new set of features. The new Results Observer provides developers an observable, observable fetch experience that can be used with sectioning as well for types that were previously unpredictable but were codable.
Now they can be decorated with the Mac Pro, the attribute Mac Pro 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’s talk What’s New in SwiftData? Now let’s hear from the developers and take some questions.
All right. Thanks for that, Rishi. And and we’re looking forward to all your questions. Let’s jump right in. And I think this one, Ben, I’ll start off off with you. And then we can we can pass it around as we go. But the question is What’s the proper way to use SwiftData 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 Multi-process case where you have a widget and an app or an app intents plugin, 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 cases filled with sharp edges.
Just in general. So we kind of recommend folks have one process as the 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. And I think that’s in the Sample Trips group app. We have a widget there. Is that right? Yes, there is a widget in the Sample Trips app and it utilizes the group container, right? 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 it used app Intents to build up a timeline on the widget. So that was an example that 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 vend 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 if you’d like. So, and this sort of gets at the core of what what SwiftData is great at. But how should developers approach large data sets in SwiftData? 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 like what dimension it’s large in. 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, 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, that you put limits on your fetch requests or otherwise, like 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 blob in the database, but in a file kind of next to the database.
I don’t know if anyone else wants to kind of 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 the new model context every single batch that you do.
And then that way, as you 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 predicate? So I’m pulling the rows that I need or how should I approach that?
I think you want to make sure that first you limit it like that. You put a max that you’re going to fetch out of it, and then that you put the right predicate 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 body, for example. And then lastly, it’s covered by an index. So for example, if you you can start with instruments.
I would first start with like a instrument. Like if you load everything in there, does it still load nicely? And if it doesn’t, there is a persistent instrument that actually tells you what Swift data is doing under the hood and what like how many objects are 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 on this one. What 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 it 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 SwiftData. 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 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. Nice. I think there’s another piece about sample data, like beyond previews that we’ve talked to before is, is you also need sample data to test your migrations and and I don’t know if maybe, David, you have anything to add about migration testing and how you do that. Yeah. The 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, you know, someone may have had a device that was in a drawer for a year and they pull that out and update the the app. You want to make sure that they’re able to migrate, as well as the person who’s stayed up to up to date as well.
One of the things I really like using genetic programing for is generating sample data. Like they’re pretty good at giving you sort of creating a bunch of instances of models that you can then save off. And, And they can also be kind of funny sometimes. I like that any time code can make me laugh. That’s a good thing.
So this is another interesting question. And Thomas, you talked about this in the in the what’s new talk. So maybe we’ll start with you on this. And I’m sure Rishi has opinions as 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 the contrast there?
Yeah. So results observer. Three meant for kind of an equivalent to query outside of a SwiftUI 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 observers. History Observer is results. 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 versus history observer is 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 refresh when you know that something has 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 where the 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 notify be notified about.
And in both the results observer and the 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, in your talk, Thomas, about using history observer, but limiting it on which we say author.
But what part of the code is, is doing the writing and the example you shared, which I thought was, was neat is if the, 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. 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 talked about how AppKit, excuse me, and UIKit both support observable objects. And so that combined with results observer means you can feed observable data and update your UI kit 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.
Yeah. All right. This is a 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 NSE expressions to calculate sums, averages, min and max, etc. directly on the SQL store.
With SwiftData, there’s no equivalent in 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 nspredicate elements, and the Swift expression stuff is a little bit newer. So we’re aware that this is something we need to need to add in a feedback report would would be appreciated for min and max. You can do that with the fetch limit of one and a sort descriptor. Some of the others don’t don’t map quite so cleanly. Yeah.
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 like 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, for their customers, right?
We 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 SwiftData stack point to the same data store. So where you don’t have that SwiftData feature, you can still reach into the core data approach and utilize it from the same data stack. Nice.
All right. So the digesting this next question. So Rishi I’m going to start with with you on this one. So the question is when using SwiftUI with SwiftData what’s the best way to construct the view’s state and the SwiftData 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 SwiftData model? So if. You’re Non-optional property. If your SwiftData 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 SwiftUI view.
Now, if your state is relying on a model to be updated for a non arbitrary attribute, that seems like it would have to have happened before it was passed to the state, right 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 another interpretation of the question. Sometimes I’ve had cases where I, I needed a single SwiftData object that a view was rendering. So it wasn’t a list of things, but it was a single object.
And I wanted to make sure that object existed before I tried to show something about it. Right. And in those cases, I’ve done a couple of things. You can tell me if I’m wrong here. Yeah, I use SwiftData, but I’m not the expert. The experts are sitting here.
So one thing I’ve done is, is fetch that data or find that data by precision 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 the 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 the query is going to return an array and I’m deciding what to show in the view based on whether there are 0 or 1 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 view to start building off of.
Okay. And that’s where the 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 then 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 or. And then how does the person create something? Right. 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 SwiftData and CloudKit. So Ben, maybe we’ll start with with you and we can pass it around. Okay. So for SwiftData plus CloudKit, 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?
Any body quite follow that. This is not something I’ve done. Yeah. So you should be able to add a schema. Yes. You can add a schema anytime. 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, like there’s, there’s no, yeah, there’s no way to like align them. Right. So you’re going to have a new container.
If you use that group container, you’ll copy the data over there and start 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 group container. Okay.
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 and that’ll be.
And if it happens to be version three 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 if someone is moving the data from a non-ap 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 would be welcome to.
I think this is a similar a similar question, but Rishi I’ll let I’ll let you decide whether it is. So the the question is How is using an app group to define the SwiftData local? How does using an app group to define the SwiftData local store affect the CloudKit development and production environment?
Can I. How can I safely evolve. 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 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. Okay. So the data. So you could have the app that’s doing sync if I’m understanding it. Right. But then like widgets and other things locally would be using that local app. 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.
Oh, interesting. And that that also would have, you know, perf implications because the, 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. Right. 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? Right. Makes sense. Thank you.
I think this is mostly the same question. The second half of it, I think is interesting though, and. So we’ll stick with you, Rishi, since since you’ve been covering the first half of this. But the question here is, if you’re running in Xcode and a test version and you’re evolving the schema, like, 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 this is a shared CloudKit store. So yes, you’ll, you’ll, 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, right? 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, that’s one of the things to be mindful of. And then the other part is as you’re working in development, eventually you’ll have to push your schema to production and test it there as well. So you 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 any anything to add to that? Sync is hard. Yes.
Oh this is this is a good one. And a fast one, but maybe we’ll see if we have 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 SwiftData objects without querying them in the views?
You can fetch the count. So so there’s fetch count. And is that. On the model. On the model context. 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 SwiftData models into a cached array and managing updates myself rather than using at query.
How do you all sort of think about that trade off? Thomas, do you have anything to to talk to in this case? Like, yeah, sure. So I think so. Query 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 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 doing like is too aggressive in prefetching, I think instruments and specifically SwiftUI instrument, combined with the persistence instrument, gives you a good overview of why SwiftUI decided it’s time to refresh 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 refresh your data. Yeah, that’s 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. And it can also be once you bring SwiftData into your SwiftUI application, I O is way more expensive than just reading like state objects from memory, right? So like it might have done this before and you had renderings of views that were kind of unnecessary, but now you kind of amplified this problem because we actually go out to the disk and, and fetch your data.
Yeah. And so 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, you know, maybe, maybe all the query is doing is passing the resulting model objects down to sub views, and then they’re doing the work. And now the query doesn’t have to rerun because the objects are consistent.
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’s also not to build on fetch account. There’s also fetch identifiers so that you don’t have to fetch the full model. You can just fetch the identifiers that have of interest for your current fetch.
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 thereafter. Nice. I’m learning so much. I think I would say you don’t have to use SwiftUI for these queries, right? Like particularly if you’re outside the view hierarchy, you’re on a background actor, you’re doing a detached task or something like just do a fetch. It’s okay. Like you don’t have to route through query if it’s inconvenient, right?
And the, the other thing that I was thinking of here and sort of where I’ve sort of reached for this lever before my myself that our developer is asking about is when I needed to do sectioning. And now we’ve got sectioning. So Thomas’s talk has a great example of, of using that. And so and check out the code snippets along that talk too, because then you can, you can grab the syntax to make that happen.
All right. So, Ben, let’s start with you on on this question. So let’s say another SwiftData with CloudKit question. How can I ensure that synchronization happens as quickly as possible? I’m seeing long lags in my app for data showing up on different platforms. Are there any best practices that 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. And they also are much more aggressive about syncing when they’re on puck. So there’s some stuff like that.
And similarly, I think internally we see. Like if the the phones are really busy, you’re testing them really hard and they start to heat up. Then they throttle everything. So you’ll see in the logs like these thermal warnings, right? Because you have a test rack and they’re not quite quite the same.
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 just decided that it needed to throttle you for a policy reason. So, and you’ll have to kind of look at the logs to, to suss that out.
But in general, your app should sync more aggressively if you background them and then bring them back to the foreground in terms of like your test workflow, obviously that’s not something we would expect a customer to have to do, right? 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 entitlement 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 cyst diagnosed 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, right? Another thing I’ve run into there when I’m, you know, going back and forth between my, like my, so I, my, my main SwiftData app is a personal app for grocery shopping.
And I, you know, I needed to stick to my Watch before I go to the grocery store because that’s my workflow. I put my list in my phone. And, 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, 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. The other thing with autosave is if you force quit the app in Xcode by hitting the stop button, 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 there 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 and you get the same notifications. 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. This is 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 SwiftData? Well, as long as your values are codable, you should be able to persist those for you and new. This year is being able to build up predicates with the raw representable enumerations.
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 Ms. types underneath.
Then consider decorating it with a new Dock codable flag for the attribute Mac Pro. 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 data types that we haven’t been able to persist for you before.
I know when we were working on Thomas’s talk, we had a lot of conversations around sort of when not to use the Codable attribute. Thomas, do 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 SwiftData can’t map into a schema for you.
It’s kind of not in like it doesn’t know how to break that 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 access to that returns a enum of some kind where you kind of package it back up and kind of vent that to your views as the API.
But under the hood, you are kind of decomposing it into SwiftData 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. Yes. 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 the 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 SwiftData model and then get the full power of like predicate and sorting and those kind of things. But Thomas’s talk has a really cool example of storing MapKit data in the database. And we can sort of he demonstrates how when the data changes, we can detect that and update the map.
But the the MapKit 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? 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 like natively right now. So the generic. Code able to, to kind of store it. Yeah.
I want to pivot away from our incoming questions a little bit and, and do a little bit of, of color and some background on what you all work on. So maybe we’ll, we’ll start with Rishi and sort of go around the horn. What what pieces of 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 SwiftData 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, exploration macro. Yeah. Pre macros and using property wrappers to kind of build up an nsmanagedobject model from your code. And that’s kind of was the first step in this whole process.
Outside of that, I’ve just been on the core 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, and history related things from, 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, you could, you could go work on SwiftData. So yeah, I get to look at your, your code all the time.
And shake your fist. Yeah. Rishi, why did you do this to me? How about you, Thomas? Yeah, I’m also an engineer on the SwiftData and Core Data team, and I also maintain SQLite for all Apple platforms. This year, I’ve been mostly working on performance optimizations for both core data and SwiftData, 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 with EOS and Chordata and SwiftData. iOS enterprise object framework. Yeah. We all know it so well. There’s a song about it, actually. There’s 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, for sharing that. And that gives you some sense of sort of the breadth of the kind of things we could cover if, 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? Oh my God. How much time do we have? 19 minutes. Perfect. All right. So the last question of the afternoon. So.
This is going to kind of be dependent on your objective. You know if this is interacting with a 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’s 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 you want each of those chunks to make sense to the database.
And you don’t want to have too much cross coordination. If we want to geek out, we could talk about like Amdahl’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 going to 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 I’m most of the platforms we ship today are 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 Xsan system can handle.
Right. So typically, if you start and you should start measuring your performance and benchmarking what you’re doing, the Nan system is going to be like your primary bottleneck. And no matter what you do with tasks or dispatch queues or Swift concurrency or async, right. Like you can only do so much with the Nan hardware on each device, and some of them are substantially more powerful than others.
Right? You talk about like some of the SSDs, you can get onto a Mac costs more than an entire phone. You know, like costs ten times as much as a HomePod, right? Like, you know, so there’s, there’s a pretty vast disparity of capabilities there. So, so that’s the important thing.
And that drives a lot of the decisions in our own API design of like, how do we end up giving you this, this object graph that you’re modeling and it feels like you’re working with nice Swift code, but underneath the covers, we’re pedaling as fast as we can. To. To cheat and kind of avoid doing IO unless we really, really have to.
And so there’s a lot of like 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 SQLite database itself will handle one writer at a time. We’re using the WAL 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 at the same time as a writer.
Which I think people tend not to quite follow because 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 forums and stuff. Folks tend to be surprised that they can actually get faster performance with less concurrency. It’s not like super popular, but, 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 pooling work. We do like 2 or 3 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 SQL layer there that you want to add about single writer or single writer? So first I would say on what Ben said, like when you are benchmarking, benchmarking is surprisingly hard. There’s like kind of caches all the way down. Like SQLite has a page cache. The operating system has a cache for file system pages. Maybe the Nand controller even does some some caching. So.
just make sure you have a reasonably sized data set. It’s surprisingly hard to do good benchmarking on. Okay, this time it’s faster, but oh, I just hit a cache, right? Like I just got served out of the page cache from SQLite versus actually doing I o right. So having the I o instrument can help you to kind of see what was actually going on. And then, yeah, pretty much has been 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 boundaries 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 to this question is. 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 to work with. And as analogies, 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. Well 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 know, 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, right. 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 1000 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 paginate your queries. Right. 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. 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, 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 SwiftData 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 SwiftData. 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 uses Json store.
I think I did a talk a couple years ago about implementing custom data stores and adjacent store was an example and kind of keep what you have and kind of make SwiftData work with your existing storage mechanism. Another approach would be to move everything into SwiftData and use something like History Observer to know what to send to your cloud backend. If you’re not using CloudKit, which works out of the box. So kind of what pieces of SwiftData you want to use.
Obviously, when you build Json store, like it doesn’t handle indexing for you. You can build something clever yourself, but SwiftData 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 she has other thoughts on it.
I mean, one thing we’ve seen a lot of clients do is leverage SwiftData for their local cash, right? And that would be the local Jason Mira 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, your back ends.
And that’s, that’s a very common client experience that we’ve seen. But, you know, leveraging what SwiftData can do for you for that local cash 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 SwiftData 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 a is that sort of maybe a step on a pathway? Also, like I could use a Json store, start adopting more of SwiftData. And then once I’ve made that migration in my app, then I could think about using, you know, 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 SwiftData lets you harness. But then also the model context is 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, trying to build your own persistence layer, right?
Yeah, always. When I was an indie dev, we always look forward to dub dub 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, that was always. 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 migrations and process. How should people think about that?
Yeah. So if you were to have your widget essentially load and unversion schema. The 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 or let the user know in UI, you’d say, you know, we, you need to go back to the main app to essentially, you know, migrate the, the data, right? And then you can write the version that the app has migrated to into some sort of user default.
And then when the app when the widget loads again, you would use that as the 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.
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 what stage you’re on and then how many are left. It’s not like a wall time, but at least we give you, I have this many more stages left to migrate, but I, I would say that.
If you’re looking for something a little bit easier to use or, or anything like that, you should file a feedback report so we can consider that and, and see what we can do. Yeah, it’d be, it’d be super fun if the, the foundation progress API could hook in. For example, I guess I have to go file a feedback report when we’re done.
The, the other thing I think that wrapped up in that question is, is kind of a human interface design question is like, what is the experience you want the people using your app to have? Well, a migration is, 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, to think about and look what different apps do, because just getting a loading spinner is not, it’s not a great way to drive engagement. You know, 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.
You can utilize what David was saying and tell them whether 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. And that’s, that’s a great point. Like if you, 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 while it updates. So nice.
All right, keep an eye on the clock. We’re getting near the end of the hour, but let’s let’s take a couple more before we need to wrap up. 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, you know, if in Thomas’s app, he shows us in the his talk in sample trips, and it’s a great way to now build up your UI and show like 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, I need to migrate my grocery shopping app to use that because I’m grouping by aisle in the store. And, and so I taken a couple of weeks vacation after dub dub. And one of the things I do is Workouts, grocery shopping, and you. Gotta. Go cooking. Gotta update all your personal apps. Yeah.
Yep. All right. I think we can take one more question and then we’ll, 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 they’re reference based classes. So they can’t they can’t really be made sendable in any sane way. Right? 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 right over to.
I think that actually, to me echoes back to your, 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 sensible boundary is, 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 into 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 is looking at and can be awesome. Yeah. And, and that’s. Yeah, I. Know. And 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/forums and visit feedback.apple.com to file any bugs or feature requests.
And again, we’d 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 w w Dock.