This is one of the most exciting changes with Core Data 2016. Through it, you can avoid unfulfilled fault errors by pinning your context to the point you originally retrieved the data. From this code walkthrough, you can become more comfortable with the nuances of working with Query Generations.
- [Narrator] One of the coolest things that came out of WWDC this year is the concept of query generations. It basically allows you to query the database and then pin your results so that if any other thread or context deletes those objects, your app won't crash, it won't get any warnings because in that pinned context those objects still exist. This is a really exciting concept and I hope to be able to use this more in my apps. One of the biggest problems currently, is there isn't a lot of documentation.
In fact, the only place that I was able to find any documentation was the What's New document from Apple. They have a small example here that ours is kind of patterned after. But there really isn't a lot of other documentation, and the videos aren't quite right in the description. Let's go ahead and jump into this. With this app, we're basing it off of our simple example that we've worked with before. When you click on the row, we get a list of people, when you click on the plus sign we're loading new people in.
Currently we're just loading an empty array. So let's start implementing it. The first thing we would want to do is pin that context. So I'll put a Pin ViewContext and we'll say try! persistentContainer.viewContext .setQueryGenerationFrom and we can just say .current and that will pin to the latest state. Go ahead and load some pinnedPeople and we'll say we're just using our loadPeople method.
And all the loadPeople method is is a fetch request. In fact we can go in here and I'll show you that. We have a SortDescriptor, we have our FetchRequest, it's paged and then we have fire it on the main context. We'll go ahead and close that. And then we're just going to print out this result. Usually I use emoji because it stands out in the output panel a little bit better. And I'll just use the pigeon for the main thread. And we'll say pinnedPeople sub zero, we're just printing out the name or just expecting I think like person, person four since we've loaded the first three pages.
Next, we're going to make a background context. Set a new background context and we're going to just tell this to automatically merge changes from parent, we don't have to do this but I just wanted you to see that this doesn't affect how things perform. And the reason why I did a background context, instead of just telling the persistent store coordinator to run it, is that I want to do a performAndWait so that we can have this run sequentially. And in here, I'm going to just make change set for us. We're going to say newName = NSExpression and we'll say for, doesn't come up, forConstantValue okay and I'll just say Different Generation.
And all this is doing is it's going to change all of the names, put update all names, and we'll use a updateRequest and it's going to be a NSBatchUpdateRequest I think it's entity, we'll say here's our magic string I wish this was strongly typed. UpdateRequest.propertiesToUpdate I think, yeah I didn't spell this right. Oh, for constant that would be why.
PropertiesToUpdate is going to be we'll do the newName we'll say it's for name and what is the complaint here? Oh, yes entity name. Okay, and we're just going to fire this result again, you should be doing doCatches around all these trys. So we'll say backgroundContext.execute(updateRequest) since it's an update request we can't have it execute itself and you'll see what I mean in just a moment.
We're going to get the latest on this thread, so we'll say fetchRequest = NSFetchRequest<Person> = Person.fetchRequest and let people, again no error catching. In this case we can say fetchRequest.execute because it's not a batch update request. And then let's go ahead and print out the results, it should be updated name, and since we're on the background thread, we're going to go ahead and use our trusty unicorn and we'll just say people.name, I'm expecting this now to have this new value of Different Generation.
Expect, what do we call it Different Generation. This is the latest generation to the database really. And then I'll just show you a couple of things, we're going to talk about how to refresh all objects. But this shouldn't effect pinnedResults. Normally if these results weren't pinned, this would refresh them and everything would have that new name, the Different Generation name. Say refreshAllObjects, and again I said this won't make a difference.
We'll go ahead and refetch that data. Refetch people, we'll say pinnedPeople is now = loadPeople. Again, that's just a fetch request. And that will give us the results. If it weren't pinned, we would have the latest in the database, but since it is pinned, it should be the old generation's because of the pin and I'll say print and we're on the main thread again so we'll use our trusty penguin, person and we'll say the pinnedPeople .name and we're expecting this to be the old name.
And now, we're going to move the pin to the latest generation. And then I'll just not catch any errors, so I'll say persistentContainer.viewContext .setQueryGenerationFrom(.current). This doesn't update any of the values yet, of objects that are registered. In fact, I have a whole comment block that I think we want to talk about. In the WWDC video, at 11:55 we're told that registered objects won't be updated unless you refresh the objects or you refetch the data.
And to clarify, refetching doesn't get the latest generation, even after the pin but the refresh will. So that's a little contrary to the video but you'll see that the behavior actually ends up being that way. As of this recording, this is the behavior that I'm seeing, of course things might change at some point and then this block of comment will not be correct. But for now let's document the behavior that I'm seeing, and we can at least make some sense with how things are working within these generations.
So the next thing we're going to do, I'll just put a comment here of some code that we'll run in a moment. PersistentContainer.viewContext.refreshAllObject. We're not going to run that so that I can prove what I just said. Makes no difference, and I'll say pinnedPeople, this is us refetching the data because we're using the fetch request in there. And it should equal Different Generation.
And we'll go ahead and use our trusty penguin again. Person sub zero, let's see pinnedPeople  .name I'll return that. We'll put some break points after all of these print statements. So we can kind of see the results. And verify that what I said is true. Okay we're here, we go into the example. I click the plus button and now we're within this method.
We've pinned the context, I've printed it out. This is the original value, I would expect that. Now we're going to go into the background context and print out this value. And this is the new value that I expected. And then we're going to return to the main thread, which context is pinned so this shouldn't affect it. And we should get Person 4 at this line. Which we did get Person 4. And now to verify what I said about the video not being quite right, we're first refetching so this won't have the latest generation's name.
And you can see that printed out here, it's the old generation's name. Now what we want to do instead is refresh all the objects. And this effectively goes to the database and gets all the data from whatever generation you're pinned to. And since we reset the pin to the latest generation, and now that we are refreshing all the objects, this last printout should now say Different Generation. So let's run that again. Click on simple, click on the plus. We're in here, Person 4 is our first printout.
A new name, the Different Generation is our next printout. We come back down here, this goes to the old one because we haven't moved the pin yet. We're about to move the pin and then we will print out this last name, and since we're refreshing the objects it will now say the latest generation's name. In this case Different Generation. So if you want to refresh your objects, make sure that you're using refreshAllObjects. And don't try and just refetch the data.
This course is meant for the enterprise developer who wants to get up to speed with the latest methods with Core Data. Instructor Jon Bott starts with a review of the basics, explaining the different architectural data models currently in use, the issues that can arise from these different models, and how the latest changes in Core Data 2016 simplify these models. He wraps up with hands-on migration to the new APIs and further tips on leveraging them in iOS 10 and macOS 10.12 apps.
- Fetching data
- Working with objects, queues, and threads
- Understanding the architectural models
- Managing local and server-side data
- Understanding iOS 10 and macOS 10.12 updates to Core Data
- Migrating to the new core data