Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: tech-talks-2009-7
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2009-7
$eventShortId
Shortened ID of event: tech-talks
$year
Year of session: 2009
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2009] [Tech Talk World Tour] Wo...

iPhone Tech Talk World Tour #7

Working with Core Data

2009 • 51:07

Core Data is a powerful, efficient framework for data management and persistence on Mac and iPhone. Learn how to structure your data efficiently, use Core Data across multiple threads, and discover best practices for working with Core Data and UIKit. Explore the key components of the Core Data architecture and discover how Core Data can accelerate your iPhone application development.

Speaker: Michael Jurewitz

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi, I'm Michael Jurewitz, the Application Frameworks and Developer Tools Evangelist at Apple, and welcome to Working with Core Data. Today, we're going to focus on showing you how you can integrate this powerful technology into your application to help simplify your development and optimize how you work with data. We're going to focus on the best practices around structuring your data, fetching that data efficiently, and show you how Core Data integrates directly with UIKit. So let's get started.

[Transcript missing]

So now, why would you use Core Data in the first place? Well, again, it comes all back to efficiency. And it's efficiency in terms of memory usage, being able to reduce the amount of memory that you consume in your application so that you're not having to load in your entire data set from the moment you start using your application. It's efficiency in terms of writing less code. Core Data is really going to help you get some great savings on that front, and we'll take a look at that in a moment.

And it's also efficiency in terms of integrating directly with UIKit. So let's go ahead and take a look at an example here. In this case, we created a sample application that did things with SQLite in sort of the usual way that we see third parties working with SQLite directly.

And we pitted this against the exact same application using Core Data. So let's go ahead and take a look first at memory usage. So memory usage on iPhone, we're loading in 10,000 records from this sample application. So now our basic SQLite application took around 1,800 kilobytes to load in all this data. So fairly sizable.

If we take a look at the Core Data application, it also took 1800 bytes. Now in this case, this makes sense because you're still loading the exact same amount of data. So we see these things are on equal footing. But by simply turning on one small feature in Core Data, a simple thing we call batching, we're able to shave this down to just 900 kilobytes on a fresh launch. That's a lot of savings.

Now let's take a look at the actual speed of this application, in this case, the launch time. So if we take a look at the SQLite application, we see that it takes roughly five seconds to go grab all our data, query it from the database, populate it, and then present it.

By using Core Data, we're able to do this in three and a half seconds, roughly 30% faster than the SQLite implementation. And again, by using batching, we're able to shave this down to one second, or roughly 80% faster. Those are huge savings. Those are exactly the kinds of things that you want to see in your application so that you can get started ultra-fast for your user.

Now let's take a look at the lines of code, right? Because ultimately, it's all about maintainability in your project. And you want to make sure that you're able to just maintain the code that you care about. So SQLite, doing this basic kind of work, took us roughly 570 lines of code. That's a lot to work with. Now for Core Data, it was 320 lines of code, almost half. And all those great benefits we saw from using batching in Core Data, well, that took 321 lines of code. So as you can see, really straightforward, lots of great savings.

So let's go ahead and dive into the Core Data architecture and make sure that you understand the basic building blocks of working with Core Data. So the whole point as we go through and do this work is that we want you to be able to think about objects and avoid database grunt work. We want you to think about these objects and how they're connected to each other and the different behaviors that they have as they interact with each other.

Now, these individual data objects that you find in your application are represented by a class in Core Data called NSManagedObject. And so we refer to your data objects as managed objects. Now, each instance of one of these objects represents a row of data in your database. And it encapsulates everything about that particular object.

So it's your data, the relationships, all the different things it might be connected to. And these also provide really powerful capabilities like validation and error checking. So that you can make sure that as a user actually inputs data into your application, that you've always got a consistent view of your data and that it's valid.

Now, all of these managed objects live inside what's called a managed object context. This is represented by the NS managed object context in Core Data. And you can basically think of this as Core Data's scratch pad for your objects. Michael Jurewitz Now, here we track all the changes that get made to the individual model properties in your objects. And the way to think about the managed object context is that it handles all of the verbs about your data. So, the inserts, the fetches, saves, and deletes.

So, basically creating data, querying data, etc. The managed object context is going to be the central point that you work with when you're doing all of these things. Michael Jurewitz So, now let's take a look at data storage. Michael Jurewitz Now, Core Data stores its data using a class called the NS Persistent Store and a couple other friends that help out along the way.

Michael Jurewitz Now, all of this data is stored in SQLite. So, fundamentally, the storage layer is the same between SQLite and Core Data. Core Data sits on top of this layer and handles all of these interactions for you, though, so you never actually have to worry about talking to the database directly.

Michael Jurewitz Now, the schema and the data that we actually store in this file is considered private. Michael Jurewitz Core Data is stored in the database. So, you don't have to worry about talking to the database directly. Michael Jurewitz Now, the schema and the data that we actually store in this file is considered private. Core Data is stored in the database. So, you don't have to worry about talking to the database directly. data is storing this in a special format that it expects to be able to read. So don't go in there and try to manually manipulate this data.

Now, as we take a look at actually modeling your data, Core Data does this through a class called the NSManagedObjectModel. Now, this is something that you'll actually graphically design in Xcode, and so you can lay out all of your different objects and how they're connected to each other.

And the ManagedObjectModel basically describes all of these objects, or what we call entities, and how they're related to each other. You also get a chance to define basic behaviors with this object graph. So, how should things be deleted, for example? Should you cascade a delete? Should you nullify back references, etc.? And we actually use all this information in your ManagedObjectModel to create that store file.

Now, these entities I talked about are represented by NSEntityDescription. And in this case, they're basically just the blueprints for your managed objects. They describe the different data that you want to store. So is it a string? Is it a data? Is it a date, et cetera? And also the different relationships that these objects have to each other.

Okay, so let's bring all this information together. So we've got our managed object model. We've got our managed object context. And of course, living in that, we have our different managed objects. And then we have our actual store file. And now sitting at the center of all of this is the NS Persistence Store Coordinator. And its job is to mediate the interaction between all of these different layers.

Michael Jurewitz Now, if you're coming to Core Data for the first time and you've been doing database programming in the past, let me do a little mapping for you that might have this make a little more sense. So in this case, the managed object model, you can think of as basically your database schema.

The NS Persistence Store Coordinator you can think of as basically your database connection. It's how you're going to get at all your individual data. The Managed Object Context with all of the individual managed objects is basically just your raw data as objects. Now finally, there's one more class that gets used in this mix, and that's the NSFetchRequest. And we're going to talk a whole lot about this a little bit later. But the basic way to think about the fetch request is that it's your SELECT FROM clause.

Okay, so we've laid the groundwork for how all these things work together. Now let's jump into the individual sections that I mentioned to you earlier. So let's start off here and take a look at structuring your data. Now first, a simple word of warning. This is the most important step in your development, is figuring out exactly how you want all of this data to be structured, how it should be related to each other, and the different behaviors and ways that you're going to be querying that data.

So if anything, this is the stage where you want to take the time to iterate. Design that model for the first time, see how it works, but make changes as you find different performance bottlenecks. The last thing you want to do is start off with a rocky foundation that's going to set you up for problems on into the future. Now as always, Core Data is going to make it easy for you to correct some of these issues, but again, spend the time here to do that iteration.

So we're going to cover a couple rules of thumb about designing your data. And ultimately, they come down to these four things. So first, you need to find a balance between speed and space. Next, you need to make sure that you're always designing with the UI in mind.

Now, this might seem a little strange if you've come from sort of a pure database background where you've got this idea of a perfect organization of data in your head. And while, yes, that's true, ultimately, the way that you choose to present this data is going to have some effect on how you store it, mostly because of performance.

Next, a simple statement to avoid entity inheritance. And I'll tell you about that in just a moment. And finally, don't put blobs in the database. These are binary large objects. There are better ways to work with these. So let's go ahead and jump in and talk about this speed versus space.

So in this case, this is all about structuring your data. And ultimately, it's a push and pull between two different ends of the spectrum, between normalization and denormalization. Now, these are database terms that basically have to do with how you structure your data. So normalization is the idea of taking the different data that you want to work with and finding a perfectly factored representation of it so that you completely eliminate any repetition of data.

Now, this is space efficient because you've eliminated that repetition. So instead of having some table where you repeat the artist name or the album name on a song multiple times, you simply have one canonical reference to that artist name or that album name. Now, denormalization, in contrast, is what you do after you've normalized your model. And it's the willful reintroduction of some repetition of data. And usually, this is the idea that you want to take something.

You want to take some data and make it more local, basically make it easier for you to get at, possibly because you display it often in your UI. So the idea here is to help give you faster access to this data. But again, if you denormalize too much, then it's going to be very space inefficient. You're going to have lots of repetition of data to deal with.

So ultimately, you're always going to be in this push and pull, in this balance between these two poles. And that's OK. So if we take a look at what normalization is in the first place, we're going to see that it's going to be a lot more efficient. So if we take a look at what normalization is in the first place, let's take a look at a song.

So as you sit down to design a database for storing songs, you begin to write down all the different things you want to store. So an artist, a title, an album, genre, et cetera. Now, when you go and you take this and you want to actually normalize this model, you might end up with something that looks a little more like this. In this case, you see we still have a song entity. But now we have distinct artist, album, lyrics, and even songs.

So we've got song data stored off here. Now, the song still has a title. In this case, it also still has a genre. We could actually probably take that genre and factor that out, too. And we see now that all these different objects have relationships to each other. So for example, an artist has a too-many relationship to a song. So an artist can have many songs. But a song has got a one-to-one relationship with, say, an album, for example. Usually, it's only on a single album.

Now after you've looked at this and you've factored out all of your data and you begin writing your application, you might find different bottlenecks that you want to try to overcome by changing your model slightly. So say, for example, we had an application where the whole focus was on showing the duration of songs and also showing the lyrics for songs, or at least showing some indication that a song has lyric text that we might want to look at. In this case, if we introduce some denormalization, we might end up with something like this.

In this case, you see we've added a has lyrics tag back to our song, and we've also moved this length from the song data back into our song entity. In this case, that way we can actually take a look at what the total length of our song was and be able to quickly display that in our object, in our UI.

Okay, so when it comes to speed versus space, it's all about finding that balance. It's all about normalizing our model first and finding the best organization of data that's going to fit our needs. And then it's all about actually using that model and discovering those bottlenecks. From there, you want to make sure that you're carefully denormalizing. Because remember, any duplication of information that you've got in that model is going to require extra maintenance. It's now another place, you know, three different places that you're going to have to potentially change this information, depending on how many times you duplicate it.

All right, so let's look at the idea of remembering the UI here or designing with the UI in mind. So if we started with an application called Core Data Recipes in this case, then we might have something that looks like this. We've got a table view where we're listing out all the different recipes.

We see we've got a title and a description for them, roughly how long they take to prepare. As we select an individual table row, you'll notice that we get the detail on a recipe. In this case, we get a thumbnail, which is kind of a picture of the recipe.

We see the different categories that it's in. So in this case, it's a dessert, and we see the different ingredients that we might use. And clicking on that little thumbnail picture, we actually get a nice, detailed picture of our individual recipe. Now, if we were designing this application, we might end up with a data model that looks something like this.

In this case, you see we've got a recipe, and a recipe has got a title and a description, and it's also got relationships to all these other different objects. So we see, for example, a relationship to a recipe picture. In this case, we call it a large picture. We've got a relationship to ingredients, to categories, to the different steps that we want to use in our recipe to actually create it.

Now, this is interesting and this would work for this application, but let's sort of turn this on its head. Now, what if instead you wanted to create an application that was all about the pictures of food, it was all about the photos. And so, you were really focused on how that food was presented, what the chef had actually put on that plate. And you wanted to categorize these things based on how the pictures were styled, for example.

Michael Jurewitz Well, you might end up with a data model that looks a little more like this. Notice that now we have this thing called a recipe picture that's sort of the central hub of what we're doing here. And we're displaying the picture name and maybe the date that we actually took the picture. And maybe the actual name of the recipe because we want to display that as well.

But you'll notice now that this recipe picture is what's related to the different category. And we still have, ultimately, a recipe with its different ingredients and its different steps. But it's interesting to see how, depending on the whole goal of the application and how we want to present that information, our data model can change. And that's okay. You should feel just fine if you find yourself making changes that reflect the UI.

So really the questions that you want to ask yourself are really what's the focus of this application? How is the user going to actually flow through your application? So at each stage, say as they go through that navigation style interface, what's the data that you need at each point? The other thing to think about is what additional information might interest the user, because those are things that you're likely to want to display in your UI in the future. And so you might want to make sure that that data is easy to get at in how you design it in your model.

And again, it's okay for your model to evolve at this point. This is exactly what you want to do. You want to iterate and you want to work with things. Now, if you go ahead and you end up shipping something and you want to make some changes in the end, Core Data does have some powerful migration support to help make that easier.

[Transcript missing]

So if you've got small objects, things that are about a kilobyte in size or a couple kilobytes, say for example, a archived UI color, this is totally okay to store in the database. It's very small. It shouldn't have that big of an impact when you go to actually query for the things that it might be related to. So this is okay. Feel free to put this in there.

Now, once you start getting up to about 100 kilobytes, and certainly any more than that, you start hitting a gray area where it's probably still okay for you to store these things in the database, but you need to adopt some very specific behaviors to make it efficient. Let's say, for example, this was, again, a picture of one of our recipes.

Now, if for whatever reason, as we go querying for all of our recipes, we ever had a situation where we didn't need this picture, and if we had stored that picture in our actual recipe, then we'd end up in a situation where we were eating hundreds of kilobytes of memory simply as part of that query for data that we ultimately would never actually need. And so, you want to make sure that you model these at the opposite end of a one-to-one relationship, so that your recipe, for example, can go ask for its thumbnail data, as opposed to always having to carry it around with it.

Now, once you're up into the megabytes in terms of size, these need to be stored on disk, and there's just no two ways about it. Store these on disk, put them in your documents directory, for example, and simply store a URL or a file path to where they are, and then load them in. Now, the good part about doing it this way is that then you can actually go and use UI images in it with contents of file, for example, if you've got an image that you're trying to load.

Now, this is good because UI image has got a bunch of smarts that'll basically help it decide how it wants to load in that image. If it's large enough, it'll try to memory map it in. This will help a lot in terms of speed and also in terms of memory savings.

So if we take a look at an example, again, here's our recipe, and we've got this small picture. We still have a relationship to a large picture, but again, we're carrying around that thumbnail with us every time, and that's just really inefficient. So what we want to do is take this data and factor it off like this.

So you see we've got our small picture now is stored out on that recipe picture entity. So we're storing our thumbnail out there. And when we have to get to the large picture, the actual detail, we've got this data file path, and we'll use that to load the image off disk.

So again, remember, file systems provide fast, efficient access to large files. It's ultimately what they were designed for. Now, by doing this, you avoid all sorts of unneeded faulting and help reduce the duplication of data and also the wasting of data as you pull in your objects as part of your fetches.

Now, it's not necessarily the most efficient thing for you to store lots of smaller resources off on disk. So again, this is why those are a little more safe for you to actually store in the database. But again, use a one-to-one relationship and feel free to benchmark either case to find the one that's going to work best for you.

All right, so that was structuring your data. Now let's hop into fetching efficiently. So fetching your data, it's all about using the NSFetchRequest. This is going to be how you access all that saved data. Now, NSFetchRequest is really flexible and really powerful, and it's really simple to use. All you have to do is tell us what to fetch, where you want it fetched from, and how you want it to be brought back to you, basically.

Michael Jurewitz So in this case, if we wanted to fetch all the albums beginning with B and sort them alphabetically, we'd tell our FetchRequest, "Hey, we want you to go talk to the album entity." We'd set a predicate that said, "Hey, find them where the title is like B*," or we could say, "starts with B." And for the sorting, we simply wanted to come back with it being alphabetical by name.

Now again, if you've come from a database background, the way to think about this is that this is sort of your select from clause. And the predicate is your where clause. This is where you're specifying all the different conditions that have to be met to bring back this data.

and the sorting here is basically just your order by clause. So creating a fetch request is as easy as one, two, three. The first thing you do is to set the actual entity that you want to fetch. In this case we create an NSEntity description, we specify the name that we want to use and also the managed object context that we want to work with. Next, we simply alloc and init a fetch request. And then we set the album in as the entity to be fetched with that fetch request.

Now, if we've got a predicate that we want to set, in this case, these are conditions that need to be met in order to bring back any objects, we can do that. In this case, we're seeing if the release year matches the current year. And finally, you just need to do the actual fetch.

Now, the first thing that you always need to do is declare an NSError and simply set that to nil. Now, you do that because you're later going to pass this error into the execute fetch request method. Now, if for whatever reason anything goes wrong during this fetch, Core Data is going to take care of populating this error information for you.

And so this is going to be the object that you can use to figure out what might have gone wrong. Michael Jurewitz But normally, you would just send execute fetch request to your managed object context. You'd pass in the actual fetch that you want to have done, and you'd get back an array of results.

So there's a bunch of different ways that you can do fetching. And the most important thing in your development when you're getting started is to know your tools. So we're going to take a look at five different topics here. We're going to take a look at faulting, prefetching, batching, partial faulting, and aggregates. So let's just jump right in. Now, understanding faulting is one of the most important things as you're getting started with Core Data.

So as you go and decide to fetch against your database, perhaps you might bring back albums that we talked about earlier. And when you do that fetch, Core Data is going to take care of bringing those objects back and making sure that their row information is populated for you. So that when you go to access one of these albums, we've got all the information there for you to be able to use.

Now, it's typical, though, for something like albums to have a bunch of relationships off to, say, a bunch of song objects. Now, it wouldn't be very efficient if we also went out and fetched all these song objects in, because if you had a lot of these things, you could really quickly exhaust your memory, for example. So what Core Data does is it maintains an understanding that these things exist, but it waits to actually pull this information in until you ask for it.

So if, for example, you were to ask Album 3 for Song 3, we'd go ahead and notice that, we'd go to the database, and we'd look at the data. And we'd load that song in for you. So that's a basic idea for how faulting works in Core Data.

Now let's take a look at prefetching. Prefetching is all about getting more information back. And this is exactly for the cases when you know exactly what data you need. And say if there's any data off in relationships that you know you're going to access and so you want easy access to it.

Now prefetching works for relationships, but it also works for the attributes on those relationships. So let's take a look. Michael Jurewitz So how do you use this? Well, you set up your prefetch request like you always would. In this case, we've specified our entity, we've specified the managed object context, and we've set up everything else.

Now, the next thing you do is use this set relationship key pass for prefetching. And you pass in an array of key pass that basically you want to be prefetched. Now, these key pass represent basically the names of relationships and potentially the different properties that you want to have preloaded. So in this case, we're fetching against our albums, but we want these songs to go ahead and also get fetched along with this.

Now, when you do this, you want to just go ahead and execute your fetch request like you always would, and you'll get back a bunch of albums. Now, it's important to note, though, that along with these albums, you'll also have any associated songs. But herein lies the problem, because you might have gotten back a song, or more than likely, you probably got back some songs, or perhaps you got back songs, or you might even have gotten back a song.

So, you want to make sure that you use this functionality judiciously. Be careful about how much data you might actually be pulling back. This is, again, an area where something like batching, for example, might be able to help you out, but also just understanding your data and how it's related can help.

So speaking of batching, let's take a look at that. Now, batching lets you work with chunks of data. And when you think about it, there's ultimately only so much that you can actually fit on the screen at one time. Something like the iPhone, I mean, you're going to be lucky if you can get more than, say, six, seven, eight rows in a table view without really squishing down information.

Michael Jurewitz Now, batching is going to let you save memory because what you're going to do is only load the data you need to display on screen. So you're only dealing with what's necessary. And the best part about batching is that it's really easy to adopt. Remember that one line of code that I mentioned earlier? So, to use batching, again, you set up your fetch request like you always would.

And then it's this single line, this set fetch batch size. And then you simply pass in how many objects you want to be fetched as part of your initial request. In this case, we're just asking for 10. Now, when you actually go and execute this fetch request, you're going to get back an array of results. Now, this array is only going to have 10 results in this case.

But there's something really interesting going on here. This NSArray, this results array, still responds to the count method. And the count that it'll give you will be the total number of objects that really came back from this query. So, for example, 5,280. But it's only going to have 10 objects actually in it.

Now, to access the rest of these objects, all you need to do is start indexing into the array or start iterating through it. Whatever you need to do. Core Data will notice this and behind the scenes will take care of loading this data into the array for you to be able to use. So, really tricky, really powerful, and just really seamless for you to use. It's fantastic.

So, let's take a look at partial faulting now. Now, partial faulting lets you just get a slice of the data. And this is for when you know the data you need, and you know that that data is only a subset, it's only a few things. Now this is heavily UI influenced, so this is exactly for the situation where you're showing a listing of albums and you know that you only need to show, say, the name of the album and maybe how many songs it has or the name and the release date. So let's see how you actually work with partial faulting. So again, you set up your fetch request like you always would.

And then you've got this single method to call, the set properties to fetch. And you pass in an NSArray of strings for the different properties that you want to be fetched. Now, if you've read the documentation carefully, you've noticed that it actually calls for an array of NS property descriptions.

Well, as it turns out, you can also simply pass in an array of strings. So do feel free to do that. Just make sure that your string names you pass in exactly match what you've got in your model. So in this case, we see for the albums, I'm prefetching their title and also their release date.

After that, we simply execute the fetch request and you'll get back an array of results that matches just this information. All right, so now let's take a look at aggregates. So what are aggregates? Well, they're basically just data summaries. Now, Core Data supports two different kinds of aggregates. You've got in-database aggregates and custom aggregates. So now, in-database aggregates really lets you sort of calculate to your heart's content.

Now, natively supported in Core Data are functions like sum, count, min, max, and average. And these are really pretty straightforward to use. Now, count, among all of them, is the most straightforward to use. So how do you actually do a count? Well, you set up your fetch request like you always would. And then, instead of calling executeFetchRequest, you call count for fetch request. You pass that to your managed object context.

And in there, of course, you also pass in the fetch request and the error parameter. And now, the managed object context will return to you the count of objects that would have been returned by your query. Now, you could do a normal fetch and then ask the array of results for its count. That's certainly a way to do it.

But it's going to be much less efficient if all you need is a count. If you plan on then accessing the different attributes on these objects, well, then doing a fetch request might make sense. But if all you need is the count, use this API. So let's take a look at the other in-database aggregates. In this case, taking a sum.

So the first thing we need to do is create an NS expression. And this is going to define the actual function that we want to be evaluated against our data. So we create this NS expression with expression for function arguments. And in this case, we pass in a array. And we pass in a array of NS expressions.

Now, we've got here this expression for key path, duration. In this case, this duration key path is going to be the attribute on our object that we want to sum up. The next thing we do is create an NS expression description, and we simply alloc init and auto-release it.

Next, we want to set up some information about that description. So first, we give it a name, in this case, total time. Next, we want to give it the actual expression that we want to be evaluated, so it's that NS expression that we created up top. Finally, we want to tell the expression description how we want the data returned. In this case, we want the NSDecimal attribute type because we want to get back basically a floating point number.

Next, we want to tell our object exactly what we want to have fetched. So we set up the properties that we want to fetch, in this case, just an array that contains our expression description. And we pass that into our fetch request through the set properties to fetch.

Finally, we'll set the result type on our fetch request to the NSDictionary result type. This will make sure that we just get back a single dictionary as part of our fetch request that will contain the name total time and the value, which is basically the result of running the summation. So as we execute that and then get back a result, we'll get just that, a single dictionary with a single entry that says total time and the amount of time that those things took.

Now, you can also do your own custom aggregates in Core Data. Those are a little bit easier to work with, so let's show you how you can do that. So, for example, if you wanted to fetch against all the albums again and find something to go ahead and calculate, you would set up your fetch request like you always would.

We'd go ahead and set the result type from this fetch to the NSDictionaryResultType, and then set the properties that we actually want to be fetched, whatever that might be. We'd then execute our fetch request, and we'd get back an array of dictionaries that we could simply use key value coding to get the values from, just value for key, for example.

And in this case, we'd be able to then iterate through all those results and do whatever fancy logic we had in calculating our aggregate. Now, this is one way to do it, but if, for example, you were also planning on later using these objects for display or pulling out some other information about them, you might just feel free to do a normal fetch request and then just iterate through those results. So you wouldn't necessarily have to set that NSDictionaryResultType.

All right, so we looked at a bunch of different options for fetching and understanding key parts about how fetching works in the first place. So we looked at faulting, we talked about prefetching, batching, partial faulting, and aggregates. So that's fetching efficiently. Now, let's take a look at integrating with UIKit.

So now Core Data is a technology designed to primarily help with the back end. You know, we're providing fast, efficient access to your data. We're helping you validate user input, and we're also helping you reduce your memory usage overall. But Core Data goes beyond that and can even help you when it comes out to displaying your data. Because remember, now you've got all this data that's come back from the database, but you've got real objects that you can work with. And remember, managed objects are just objects. Sure, they're subclasses of NSManagedObject, but that's just a subclass of NSObject.

And Core Data makes it easy for you to define your own custom classes to use for these managed objects. So if, for example, you wanted to override accessors to add some custom behavior, or if you wanted to add some other sort of business logic to your model layer, you can do that.

Now, the other thing that you can do is actually leverage things like change tracking. So, for example, your different managed objects can observe each other using KVO, or you might have a controller layer that decides that it wants to observe some of your individual managed objects. But you can also register for graph level changes, and this is knowing when all of your objects at a high level have changed. So, for example, NSManagedObjectContext has a didSave notification, so you can know when data has been pushed down to the database, and you can find out exactly what's changed.

There's also a didUpdate notification, so you can simply find out when any object that the context knows about has changed. Now, if you go and you use that notification, do be aware that it's something that you'll want to turn off, say, as you're batch importing a whole bunch of data, for example.

Now, when it comes to displaying data, Core Data can also help you out as well. So, if we look at something like UI Table View, it's usually pretty straightforward to work with, right? You've just got a list of data, ultimately, that you want to display. Now, that data might be divided into sections, depending on what you want to show, but that's sort of where things begin to get complicated, right? So, how many sections do you have? How many objects are in each one of those sections? And what are the different sections in the first place that you can do something like construct that index on the right-hand side of the Table View? Well, this is exactly something that Core Data can help you out with. And it does that through a class called the NSFetchResultsController. Now, the NSFetchResultsController fetches, orders, and sections your data for you, and it really integrates seamlessly with UI Table View.

Now, it also caches section information for you for ultra-fast. So, what is this caching that I'm talking about? Well, in order to build that sectioning information, basically that index that you have on the side of the Table View, that takes a lot of time. I worked with someone earlier in our tech talks who had a SQLite database that had 10,000 different rows in it, and they wanted to display these in a Table View.

Now, aside from the fact that that's a lot of data to display in a Table View, it also takes a lot of time to iterate through all the different objects there, figure out, say, what the function is, what the first letter is of the attribute that you want to actually display, and then figure out then what are the different sections that exist.

Now, you could write all that code, but it's kind of tedious, and it's not something that probably you want to maintain. So, this is something that the FetchResultsController does for you. Now, working with the FetchResultsController is really pretty simple. You simply need to create a FetchResultsController. Usually, you do that in something like your ViewController, for example, the same thing that we'll be talking to the Table View. In that FetchResultsController, you want to set the fetch request to use, the predicate, and then any sort descriptors, so anything that controls how you want that information sorted.

Next, you want to set yourself as the delegate for that fetch results controller. And then you simply need to implement the table view methods as normal. And now, whenever you're asked for data, you'll simply consult the fetch results controller for the individual data that you need. And it'll handle all of the loading for you.

Michael Jurewitz Now, the best part about all of this is that it's all handled for you in the templates. So if you go into Xcode and you select the navigation style template and check that use Core Data checkbox, all of this gets set up for you. And you can customize it right away to your needs.

Now when you want to break this data up into different sections, the Fetch Results Controller also helps you out with that. And you do that by simply specifying the key path on the object you're fetching that you want to be used as the section name. Now the Fetch Results Controller will go ahead and compute and persistently store all of the cache information. It'll update this any time it sees any of your objects change. It does this by observing the managed object context. And as you edit your objects, make saves, etc., it'll update its cache so that you never actually have to worry about it.

Now it's important to note that in order to do this, the Fetch Results Controller will load all of your data on first start in order to compute that section cache. Once it's done that, it'll never have to load all of it ever again. In this case, that's when it can simply start observing your objects. So on first launch, be prepared to have a little bit higher memory usage depending on how much data you're initially shipping with.

Now it's important to note when you're building this sectioning information that the fetch request and the predicate on the fetch results controller should be treated as immutable. So you shouldn't be trying to alter these two different values really at any time as you're using the fetch results controller.

If you do that and you have the caching turned on, which it is by default, the fetch results controller is going to think that it needs to refetch all of your data and rebuild that entire section cache. Now the way to avoid that is to simply set the cache name to nil. That'll prevent it from trying to write this information out to disk. It'll also prevent it from trying to do the calculation in the first case.

Now, if you want to do something like support a search-style interface in your application, generally the way to do that is to create a second fetch results controller. And if you set that cache name to nil, then at that point, you can alter that fetch request or that predicate and continue to refetch your data without any sort of performance impact.

Okay, so that was working with table views. Now let's talk about threading. And first, we're going to start off with some of the rules of the road for threading. So at a very basic and high level, the rules are simply practicing safe object ownership. This is the case for any kind of threading that you might try to do. Now Core Data accomplishes this by using a policy of isolation. And it isolates based on the managed object context. And so the idea is that you want to have one managed object context per thread.

Now, this is very key because a managed object context is going to be talking to your database, it's going to be managing a whole bunch of objects, and you don't want to end up in race conditions between threads. Now, once you have one of these managed object contexts commit its data to the database, you'll want to do something like post a notification to the main thread that simply says, hey, changes have been made, go re-sync your view of the world.

So if we look at isolation in practice, really for any kind of threading, it's all about avoiding race conditions, times where you've got multiple threads trying to write to the same addresses in memory. So a couple rules here. First, never try to pass managed objects between threads. Just don't do it. There are much better ways to go about it. This is going to be an area that really will only bring you lots of tedium and probably lots of errors in how you go about it. So just don't do it.

Now, in general, if you want to pull some information out of a managed object, like a string, a date, some data, etc., that's fine to pass between threads. And just the usual rules around basic threading good practice apply. So make sure that you properly hand this data off from thread one to thread two.

Now, if you want to actually work with managed objects, the way to pass them between threads is with object IDs. These are unique identifiers for each and every individual managed object. And all you need to do is basically just pass these IDs to your second thread as you begin to work with them.

So let's take a look at how to actually do this. And we're going to start off by taking a look at the wrong way first. So the wrong way would be to create our second thread and to try to simply make a copy of our managed object context and its associated data and just pass it over to thread two. Now, again, why is this the wrong way? Well, let's take a look at... ...a particular situation that might arise.

So let's say that we wanted to now set one of our albums on thread two to the title of "First Gear." But on thread one, we wanted to set this ultimately same album to a title of "Cardinal Song." Well, as we set both of those in at the same time, what actually happened? Which one of those won? We're ultimately still talking to the same object.

I'm not sure which one that would end up as. Would it be some crazy combination of the two? Because we forgot to put in our own threading protection around that setter? Hard to say. Now, what happens if we decide then on thread two that we want to actually go and delete the object? And now let's say we want to go ahead and tell both of these managed object contexts to save. What would happen? I certainly don't know. We'd probably end up with some sort of inconsistent or inaccurate data in our database. And this is exactly the kind of situation that you want to avoid. to avoid.

So let's take a look at doing this the right way. So the right way on thread two is to create that new managed object context. And then to tell that managed object context what object IDs we'd like it to fetch. Now, once you've done that and you've gotten a bunch of objects back, you have the opportunity to work with them as you please. So you can create, delete, modify, et cetera.

And once you save that information out, you'll be able to get a list of the changed object IDs that you can either pass back to thread one, or you can simply tell thread one, hey, go back to the database, go refresh your view of the world, and represent that to the user. So again, create that second mock. You can do that through simply talking to the persistence store coordinator and asking for a new managed object context. And mocks, again, are very, very cheap and easy to create. So don't worry. about any overhead on that side of things.

All right, so in terms of working with UIKit, we took a look at working with the Fetch Results Controller, and again, it's fast, it's efficient, it's easy to use. And finally, if you remember nothing else, when you're threading, use different mocks on different threads. You need to make sure that you do that.

All right, so we took a look at structuring your data for efficient access. We took a look at actually fetching your data and how you can do so efficiently. And finally, we saw how you can integrate directly with UIKit. As always, if you have any questions, you can reach me at [email protected], or you can consult the iPhone Dev Center at developer.apple.com/iphone.