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: wwdc2005-126
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 126
$eventShortId
Shortened ID of event: wwdc05
$year
Year of session: 2005
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC05 • Session 126

Implementing Core Data

Application Technologies • 1:13:59

In this hands-on session, we'll expand on the basics of Core Data by walking you through a series of examples to demonstrate how you can use Core Data to manage data effectively in your application. From data models to Interface Builder bindings to managed object context initialization, you'll discover practical techniques for using this compelling new Tiger technology in your application.

Speakers: Rick Ballard, Chris Hanson

Unlisted on Apple Developer site

Transcript

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

Good morning. Welcome to session 126, Implementing Core Data with Hands-on Examples. I'm Rick Ballard. This is Chris Hanson, and we're engineers with Development Technologies. Today we're going to be going through several steps in showing you how to build an effective core data application. We're going to start out in the Xcode Data Modeler, showing you how to build your core data model.

Then we're going to move on to a command line example that shows you how to set up your persistence stack. Next, we'll show you how to build an application using AppKit that has a simple table view populated with data from core data, but without using bindings yet. And then we'll move on to another example showing you how to use the power of core data with Cocoa bindings.

Finally, we'll show you a completed app. We're going to be building a recipe manager today with core data. So this is the core recipes application. And what we'll be building in the hands-on examples is a simplified version of the recipe manager, designed to show you examples about core data. In the end, we'll take a brief look at what a more complex core recipe manager might look like.

So this is a hands-on session. And if you're following along today, there are a few things you'll need. You'll need a laptop with a copy of OS X Tiger installed, either Xcode 2.0 or Xcode 2.1. You'll need your WWDC CD, which has a sample code disk image on it.

You want to look in there, find one of the Core Data sessions, pull up the Core Recipes disk image. Inside that disk image is a sample code disk image. And you'll need that sample code if you want to try and follow along. Inside, you'll find a hands-on examples folder with four examples on it. And those are the examples we'll be stepping through today.

Now, if you're having trouble following along, you're having trouble keeping up, don't worry. Inside the sample code, there's a begin folder and an end folder for each example. The end folder is what we'll be building. So it's better that you follow along with us, pay attention, make sure you don't miss anything.

And you can always go back using the read me in the disk image and build from the begin to the end example on your own time. So we're going to start out, for those of you who haven't read the documentation or maybe missed yesterday's session or just need a quick refresher, with a quick overview of some of the concepts of Core Data, which Chris Hanson is going to give us.

[Chris Hanson]

Thanks, Rick. So Core Data, as we often describe it, is an object graph management and persistence framework. Here's the architecture of what we call the Core Data Persistence Stack. Starting sort of at the top level, you have your data model. This model describes the model objects in model view controller terms in your application in rich detail. It describes the entities, the types of things that you're working with in your application, as well as the attributes that they have and how they relate to each other.

This model is then fed into a persistent store coordinator that manages any number of persistent stores. These persistent stores are what actually store the data that's represented by your model and that's used in your application. The Persistent Store Coordinator basically acts as a facade on any number of stores, presenting a unified interface to the higher levels of the stack, so they don't have to care so much where data comes from or where it goes.

On top of the persistent store coordinator, you have one or more managed object contexts. These are sort of like your drafting table. They're where you keep your work products while you're working with different object graphs. You might have one managed object context in one application. Another type of application might have several, all talking to the same persistent store coordinator. And finally, you have your managed objects.

These represent the actual data in your application and are connected up into what we call an object graph. The managed object context and the managed objects are really how you interact with the data in your application. If you're performing operations on individual bits of data, you're generally going to be working with your managed objects.

If you're working with your entire object graph as a whole, for example, if you're saving it or if you're fetching things into it, you'll be working with the managed object context. So to start with, Rick is going to show us how to do some Core Data modeling to describe your model objects.

Those of you following along will want to open up in the example one, working with a Core Data Model folder, you'll see a file called corerecipes-begin.xcdatamodel. That's an Xcode data model file, and that's what we'll be working with in a moment. When we go through the data modeler, we'll show you how to work with Xcode's data modeling tool, how to add entities, attributes, and relationships between entities. And finally, we'll show you the Predicate Builder and how to build a simple predicate for a fetch request. So let's go to our hands-on part of this presentation.

And you can go ahead and open up the Exi Data Model. Going to turn up the zoom a little bit. So here you can see we have two entities, one named Recipes and one named Ingredients. Both of these have several attributes, such as name and preparation time. And in the upper part of the Data Model, we have another list of the entities and of the attributes for the currently selected entity. So we're going to start out by adding an attribute to an existing entity. So let's select the Recipe entity. We're going to click on the plus button up in the Property pane and choose Add Attribute.

Now let's add a number of servings attribute. So we'll call it number of servings. When you're adding attributes, you want to start them with a lowercase letter. And then generally, the convention is to use bumpy case. So we say number of servings. And we're going to set this type to int16.

We can leave this as an optional attribute, so don't uncheck the optional checkbox. And we will set the minimum value to zero, because you can't have fewer than zero servings. And we'll set a default value of 4 so that when you create a new instance of a recipe entity, its number of servings will start with 4.

And you can see if you're familiar with entity relationship diagrams, this diagram should be fairly familiar. You can see there's a relationship already between recipes and ingredients. And the arrowheads on the relationship indicate that it's a one-to-many relationship. That is, every recipe can have many ingredients, denoted by the two arrowheads, and every ingredient can have but one recipe, denoted by the single arrowhead.

So let's go ahead and add a new entity. Let's say that a recipe is written by one chef. So we're going to add a chef entity. So up in the Entities pane, you're going to want to click on the plus button. And you may want to drag that over to the left so it's not obscuring the rest of your diagram. And let's name this entity Chef.

I'm going to do that either in the diagram or in the upper right-hand corner where it says Name. Let's add a couple attributes to our new entity. Well, a chef has a first name and a last name, so let's add first name and last name attributes. I'm going to go up to the Properties pane, click the plus button, choose Add Attribute. And we're going to name this one First Name, again with the same capitalization convention, lowercase first, uppercase N for name. Let's do it again. Add another attribute. We'll call this one Last Name.

Now these things, these two attributes have some things in common. They're both strings. We don't have to edit them all separately. We can select them both up in the properties pane. And now we can drag down on the type and choose string, and it will set it for both of them at the same time.

So now let's go and go back to the first one, just selecting one at a time. And let's set the default value of first name to first name. Let's go to the second one and set the default value to last name. So now when we create a new instance of a Chef entity, it will come with a first name of first name and a last name of last name.

So now let's make this relationship between chefs and recipes that we need. Well, every recipe is written by one chef in our world, and a chef can write multiple recipes. So let's go ahead and start with the chef entity. Click on the chef entity. We're going to add a relationship to recipe. Then we'll add a relationship back from recipe to chef and make them inverse of each other to create a one-to-many relationship. So start by clicking the plus button and choosing Add Relationship.

We're going to name this relationship Recipes because it's the recipes that a chef wrote. And we're going to set the destination of this relationship to the recipe entity. Finally, we will check the Too Many Relationship checkbox. And now every chef is related to many recipes. Well, let's put the inverse in. So click on Recipe, click the plus button in the Property pane again, and choose Add Relationship. And we'll name this one Chef, because it's the chef that wrote the recipe.

So set the destination entity after you've named it to Chef. Now we have two relationships here, but they're really the inverse of each other. So we want to set them to be an inverse relationship. This will allow Core Data to do its relationship maintenance for us. So you're going to choose the inverse dropdown and set the inverse of this relationship to the recipe's relationship on the Chef. And you see that the two arrows we had going between them have now become one arrow that signifies a one-to-many relationship.

So let's do one more thing. Yesterday, in the introduction to Core Data Session, they talked about fetch requests as how you fetch the data from the persistent stack. Well, normally, when you create a fetch request for an entity, it will fetch all the entities available, or all the instances of the given entity available in the persistent stack. But you may only want to fetch a subset of those entities. And so you may want to assign a predicate to your fetch request, which will resolve to true or false for each instance of an entity.

And when it resolves to true, it will fetch that instance. If it resolves to false, it will not fetch that instance. You can do this all programmatically, but we have a nice predicate builder for graphically building your predicate for a fetch request in the data model. So let's go ahead and do that now.

We're going to add a fetch request for the Chef entity. So you want to click on Chef. And again, in the Properties pane, you're going to click on the plus button. You can choose Show Fetch Requests if you want from the dropdown first, but you can add one either way. You're going to click on the plus button and add a new fetch request.

Let's name this fetch request Chef Name Fetch Request, because we're going to design a predicate that will allow us to fetch a chef or all the chefs whose name matches a given name that we'll supply when we use the fetch request. So once we've named it, click the Edit Predicate button to bring up our Predicate Builder.

And what we're going to do here is make it so that when you use this predicate, you'll supply a value for a variable called name. And the predicate will match a given chef if the first name is equal to the value you supply or the last name is equal to the value you supply. So we want to OR two conditions together. So let's click the plus button to add a second condition. And the default it comes up with is to AND them together. We're going to choose to OR them together instead.

And we want to make one of the conditions on first name and the other on last name. So you're going to choose the second box and change it to last name. Let's change both of those equal boxes to like because we don't want to compare to see if the value we supply is exactly the same. We want to see if it matches like a regular expression, though reject is a little more like a regular expression. Like is more like shell-style globbing matching. And we're going to Control-click to the right of that field and choose Variable.

This lets us enter in a name for a variable that we're going to see if the first name is like. So let's say our variable's name, name. We're going to do the same thing for the second condition. Control-click to the right of the field, change it to a variable, enter in the name of our variable, which is name. And we can hit OK now. You see it's got a string representation of that predicate. That string representation is what you would use if you were building a predicate programmatically using the predicate with format method.

So this is a predicate that allows us to fetch any chef whose first name or their last name is equal or is like the value we supply. If you play around with the predicate builder a bit more, you can see that you can drag conditions around, you can create fairly complex predicates, move parts around. It's a very nice tool. Chris, can I show you a little bit of that? So if we added a new condition, for example, or added an and, you can move parts of it around.

This is something you can experiment with when you get a chance. There we go. All right, so let's move on. So go back to our slides. So what did we just do? Well, we opened the data model and added an attribute to an existing entity. We created a new entity in our data model.

We added a one-to-many relationship between two existing entities. And we created a fetch request in our model that we could access later in our application and used the Predicate Builder to assign it a predicate. Now we're going to move on to building a command line example that sets up a persistent stack, and Chris is going to lead us through this.

So now that we have a data model, we need to actually do something with it. What we're going to do here is create a simple command line application that sets up a Core Data persistent stack, fetches some managed objects from a persistent store, and then if it doesn't have any managed objects in that store yet, it'll create one, insert it in a managed object context, and save that context to the persistent store. So let's go hands-on.

And in your example two folder, you have Core Recipes, CocoaView. Actually, you have example two, building a Core Data command line application. So open Core Recipes tool begin. And let's look at corerecipes-tool.m. So if we scroll down to the main routine here, we have a pretty giant font size here. It's probably smaller on your screen. We can see sort of the structure of this tool. What we've done is-- Set up a persistent stack in initialized Core Data stack.

Then we try to fetch all the chefs that are available through that stack using the fetch all chefs routine. If that succeeds, we check how many we fetched. If we didn't actually fetch any, we try to insert and save a chef. And finally, if we did fetch anything, we try to print it out. So let's just build and run this.

We get an error. This error happens because we haven't even initialized our stack yet. We've only written the skeleton of the code. So let's go back to core-recipes-tool.m and look at initialized Core Data stack. What we have here is a commented out block of code that actually finds our model and then sets up our persistent stack. Let's just uncomment this large comment block.

We can see here that the first thing we do is try to find our managed object model describing our data from our bundle. Since this is a command line tool, it'll just look in the same directory as the tool. Then we set up a persistent store coordinator to provide access to our data using that model. Finally, we create a managed object context to use when working with our data and attach it to the persistent store coordinator.

And then we add an XML persistent store. Now, you can actually use one of three store types that represent on-disk stores. You can use XML, you can use a binary format, or you can use SQLite. And we also have a fourth type of store, an in-memory store, that doesn't actually write any data to disk on save, but is very useful if you have your own document formats. We're not going to go into how to use that here. I encourage you to come to Advanced Core Data Usages tomorrow, where we might be talking about a little bit more of that.

So now let's build and run, saving as we go. And this time, we get an error while fetching. So obviously, it's gone through. It's set up our persistent stack. It's tried to add that store. But there's nothing in it. So let's go back to corerecipes.tool.m and find the fetchAllChefs method. Not only was there nothing in the store, we didn't even actually have the code working that does the fetch. So again, we have a commented out block of code that you can uncomment.

And we can see that the first thing it does is find the Chef entity. Generally in Core Data, you need to work with entities directly rather than by name, although there are places where you'll work with them by name too. So we can use the utility method on NSEntityDescription, EntityForName and ManagedObjectContext to find our entity.

Once we have our entity, we programmatically set up a fetch request that will just find all Chef entities. The reason that it'll find them all is that we don't specify a predicate. If we don't specify a predicate on a fetch request, it's assumed that you want all instances of the entity that you're fetching. Once we have our fetch request, we simply ask our managed object context to find all of the instances of that entity that it possibly can.

Once we have our fetch request, we simply ask our managed object context to find all of the instances of that entity that it possibly can. Once we have our fetch request, we simply ask our managed object context to find all of the instances of that entity that it possibly can.

and we get just a little further. Now you see that we get an error while inserting and saving a chef. Well, this happened because we tried to fetch our chefs, but we didn't actually have any. So of course it tries to create an initial one, but because that code isn't hooked up yet, it doesn't actually get anywhere. So let's go back to corerecipes.tool.m. And in our next method, we have insertChef and save.

Here we have one more little piece of commented out code that shows you how to create a new managed object. So generally, when you're creating new managed objects, you use an entity description insert new entity-- insert new object for entity for name in managed object context. You don't actually have to find the entity description object for the entity that you're referring to, because you can simply specify the name and the managed object context you want to create it in. The managed object context can find that entity because it's attached to a persistent store coordinator, which in turn is attached to a model.

Once we've created our object, we set a couple of initial properties on it. We're just using John Doe for the chef's name. And finally, now that we have an object in our managed object context, we save it. and finally, we return it. So at this point, we can build and run one more time.

And you see that no Chef was found, so we created one and inserted it in our managed object context and saved it. And we just use NSLog to output the Chef that we saved. So you can see that not only does it have the first name and last name that we set, it also has an empty collection of recipes, and it has a managed object ID.

Excellent, we've got screen zoom. So if we run this one more time, You can see that we actually found that chef now that it's been saved in a persistent store. And we can get the name of the chef that we found. So what have we learned? Going back to slides.

Well, we figured out how to set up a Core Data persistent stack that provides access to our data. We fetched some data from it, some managed objects. When we didn't find any managed objects, we created one, and then we saved our object graph containing that one object. Now that we know how to work with Core Data from a command line tool, let's kick it up a notch and go to an AppKit application.

So those of you following along will want to look in the example three folder and open up the Core Recipes, Cookerview-begin project. This project, we're going to go through how to display data from your managed objects in a table view. We'll show you how to traverse the relationships in your object graph.

will show you how to create a custom NSManageObject subclass, which you can provide your own business logic or custom model behavior in. We'll show you how to delete managed objects, and we'll show you a little bit about delete rules. So let's go hands-on. So we're going to open up the Core Recipes Cook Review Project. Let's start out in our nib file. So go ahead and open up mainmenu.nib. This machine is not quite configured right, so go ahead and launch Interface Builder.

[Transcript missing]

Here we have a blank window which we're going to use for our application. So we want to display data in a table view. Let's start out by dragging an NS table view from the appropriate palette into the window. If you're not familiar with Interface Builder, you might not want to try and follow along now. If you are, you should be able to keep up.

I'm going to resize that to be most of the size of our window. We're also going to drag in an NS button and place it below the table view down in the lower right. Let's name that button Delete. We'll use it to delete our recipes in a bit. Now you can see when you look in the document window in this nib that we have a Core Recipes Controller instance. That's our main controller class we're going to use for this application. And it needs to know where the table view is.

So let's start out by control dragging from the Core Recipes Controller up to our table view. Now you can see it tells you the name of the object you're currently pointing at. You want to make sure it says NS Table View so you don't connect it to the scroll view, for example. So go ahead and control drag up there and let go. And it should bring up the recipe table outlet on our controller. That's an outlet we set up. Let's go ahead and connect that.

Now our table view needs to have a data source outlet pointing back at the controller since we're not using bindings yet. So let's go from the table view, drag down to the controller, Hook up the data source outlet. Now, if the data source isn't one of the outlets showing, you probably dragged from the scroll view or a different view, maybe a column view. So try again, drag from the table view down to your controller. Hook up data source. Finally, let's Control-drag from the Delete button to our controller and hook up the Delete Recipe target on the controller.

Now let's set up our table view to display our data. You want to click down until your inspector says that it's an NSTableView you're inspecting. And you want to go to the attributes part of the inspector, which is in the pop-up, the first option. And it starts out with two columns by default. So let's add a third column. Because we have three pieces of information we need to display.

We need to display the recipe name, the first name for the chef who is associated with the recipe, and the last name for the chef who is associated with the recipe. So let's go ahead. We've got three columns. Let's resize them so we can see all three of them in the table view.

Let's click on that first column header and name it "Recipe." That's where we'll put the name of the recipe we're looking at. Let's click on the second one and call it "Chef First Name." From the third column, we will call chef last name. Now we need some way for the table view to tell its data source what goes in what column. We're going to use the identifier for a column to do that.

So select the first column again, and in the attributes inspector, there's an identifier field. Just put in name. We're going to use the identifier field as a key path, which we're going to call value for key path on our recipe NSManage object to get the name of the recipe.

For the second one, we're going to put chef.firstname. That's a key path, which accesses chef and then the first name of chef. Well, now chef is the name of the relationship we added to the recipe entity in the first step. So what that's going to do when we call value for key path, chef.firstname, is traverse the relationship between the recipe entity and the chef entity.

We're going to, in this application, fetch all our recipes at the beginning, but we're never going to use an NSFetch request to get the name of the recipe. To pull our chefs out of the persistence stack. We don't have to do that because we can traverse the relationship between the recipe and the chef to get at our chefs.

For our third column, we're going to call it chef.lastname to get at the last name. And make sure, if you can't see up here, we're doing lowercase l for last name, uppercase n. It's the same naming convention we used when we were naming our attributes in the data modeler.

That about wraps it up for our Nib. So let's go ahead and close the Nib document. And it'll ask us to save. We'll go ahead and save. And go back to Xcode. We're going to open up Core Recipes Controller. Let's open up the header file and take a brief look at it.

I should note for this application, we created a data file that's pre-populated with recipes. So we're not creating any recipes in this example. And the recipes for this example are in a store file, which is included in the project. It's an XML store. So in the header file, you can see this is just an NSObject to control it.

And we're going to hang on to our context, our managed object context, and our managed object model. We also have a mutable array of recipes. Well, for this example, we're going to fetch all our recipes, cache them in an array in our object, and use them to give values to our table view.

So let's go ahead and go over to the implementation file, which is corerecipescontroller.m. And from the function pop-up, you should be able to get down to the number of rows in table view data source method. This is one of the two data source methods. And since we're not implementing all the functionality here, if you're doing all the data source functionality, you'd have a lot more methods. And you'll find when we jump to bindings that it'll do a lot of this for us.

But for now, we're going to do it with data source. Go ahead and uncomment this block of code. And you'll see it checks and sees if we've already populated our recipes array. If not, it'll go ahead and populate it, make sure we have it. And it'll get the count of the number of recipes that we fetched. So let's take a look at that populate recipes method and see what that does. Go to the function pop-up again.

And let's uncomment that first block of code. You'll see this is very similar to what we did before. We get our managed object context. And if you look up in the file later, you'll see that there's the standard code to initialize the context in the managed object model. This is something you'll get when you create a new Cocoa Core Data application because it comes in the template. We create a fetch request, just like we did in the last example. We set its entity to recipe.

Let's uncomment the second block of code in which we execute the fetch, get back an array of recipes, and we cache it in our object controller. So let's go to the last data source method. From the function pop-up, you want table view object value for table column row. Let's go ahead and uncomment this.

Here again, we make sure that we populate our recipes. We get the column identifier for the column we're supplying data for. That's what we set up in Interface Builder. We make sure that it's within the valid-- that the row we're getting actually exists as a recipe. And finally, we simply get the recipe managed object context back out of our array with object index.

And we call value for keypath passing that column identifier. So value for keypath name to get the recipe's name. Value for keypath chef.firstName to traverse that relationship and get the first name of the chef. Let's go ahead and build and run this. We're going to save as we go.

For those of you on laptops, don't worry if you can't keep up quite that much, but you'll see in our table view, we've got four recipes. This comes out of our store that we created that's in the bundle. And you've got a chef first name and a chef last name.

So there, we've accessed data that we didn't fetch out. We just traversed the relationship to get to that Chef data in our store. So let's go ahead and quit this and add the ability to delete a recipe. So there, we've accessed data that we didn't fetch out. We just traversed the relationship to get to that Chef data in our store.

Let's go ahead and uncomment this method. All this does is it gets the selected row in the table view, gets the recipe that corresponds to that table row back out of our array of Cache recipes, and it tells the managed object context to delete the recipe. When you want to delete an NSManaged object, you tell the context to delete it, and that marks it for deletion.

At that point, you shouldn't use that object anymore. If you simply release the object, well, it will go out of memory, but it's still persistent. It's still in your store, assuming that you added it to a managed object context. So to actually delete the NSManaged object, you tell the context to delete it, and then you save. We don't actually save in this application because we're not changing the store file. So let's go ahead and build and run, saving as we go.

And select a recipe. Let's try clam chowder. And press the delete button. You see it goes away. Now here, there was one chef that had two recipes. We deleted one of them. What Core Data did, since we set the chef relationship and the recipes relationship as inverse of each other, was automatically maintain that relationship for us. Previously, the chef had the recipes relationship pointing to two recipes. Well, we deleted one of them. But instead of leaving the chef's relationship pointing to a deleted recipe, Core Data went through that relationship to the inverse and told the chef to remove the recipe.

And the chef removed that deleted recipe out of its set of recipes it was related to. That's automatic relationship maintenance. The reason it just removed it from that set, nullifying the relationship, is because the delete rule we had set was nullify. So let's quit this and go to our data model and see where we set that delete rule.

Now if you click on the Chef relationship in Recipe, In the upper right, you'll see it says Delete Rule, and it's set to nullify. So that means when you delete me, nullify the relationship on the other side of anything that's related to me, so they're not pointing at a garbage object. Well, what if we wanted, when we deleted a recipe, for it to delete the associated chef as well? Let's change that delete rule to cascade. And let's go ahead and build and run.

Now select Clam Chowder and press the Delete button. And you see that Lily Benthen, the chef of Clam Chowder, who is also the chef of Pan-Seared Swordfish, has been deleted and disappears out of Pan-Seared Swordfish. Well, when she was deleted, why did she disappear from Pan-Seared Swordfish? Because her recipes relationship also has a nullified delete rule. And so the cascade delete rule on our recipe deleted the associated Chef. The nullify delete rule on the Chef nullified it back on the last remaining recipe that was related to that Chef.

So let's go ahead and quit this. We have those two columns, firstName and lastName. Well, wouldn't it be better to display the full name of the Chef in one column instead of having two extraneous columns? We don't have a full name attribute on the Chef, and if we added one, that would be redundant information. We wouldn't have to keep the full name in sync with both the first name and the last name attributes. So instead, we're going to create a new accessor on that ManageObject that specifies a derived attribute of full name. So to start out, open that data model up.

We'll click on Chef. We're currently using a base NSManageObject for this chef. We really want to use our own new class, a Chef, which is going to inherit from NSManageObject. So when you select Chef, you'll see that the class is set to NSManageObject. We're going to change that to Chef with a capital C.

Let's save this data model and close it. And let's go to the File menu, say New File. We're going to create a new Objective-C class. Under Cocoa, you'll see the Objective-C class type. Choose that and hit Next. And we're going to name this Chef.m. This is our Chef class.

Now you see, when it comes up, it inherits from NSObject. NSManageObject is the root object of our models. So we're going to change this to NSManageObject to inherit from. This is a custom NSManageObject subclass. Right now, it doesn't provide any new custom behavior, so let's add that full name method. So you're going to just use standard obc method declaration syntax to add NSString full name. Let's go over to the implementation file, chef.m.

had that same method declaration, but we're going to put the implementation here instead. This is a very simple method. We really just need to concatenate the first name and the last name and return that. So we're going to return We're going to use the in a string class method, string with format.

We provide a format string that has two objects, so %at%at for our format string, Now for the first object, we're going to want the first name. So we send a message to self, value for key, first name. That's how we get the first name out of the NSManage object. and finally, self-value-for-key-lastname to provide the second half of that format string. I'm going to close that method, put a semi on it. Let's build and run.

One more thing we have to do. So let's quit this and actually use that method. We're not using it yet. So close this file and open up the nib file again. So we have two columns, and they're using chef.firstname, chef.lastname. Let's select the table view. In the Attributes Inspector, let's change columns back to two instead of three.

And we may want to rearrange the columns up there so it looks nice and neat. And let's rename that Chef first name column to just Chef. Because we're going to put the full name of the chef in that column. For the identifier, instead of saying Chef.FirstName, we'll say Chef.FullName.

So now, when we call value for key, it's going to traverse the Chef relationship and then call value for key full name on the Chef, which will invoke that full name accessor we added through key value coding, get back the full name by concatenating the first name and the last name, and returning it. So let's close this nib, saving as we go. And let's build and run and see if it works now. There we go. A recipe column and a Chef column.

Now note that's a derived attribute. When you get into bindings, you'll see you want to set that as a dependent key, that the full name is dependent on the first name and the last name. And if you go to the binding sessions later, you should see how to do that or drop in the Core Data Labs. Since this isn't a bindings application, we didn't have to do that.

So let's go ahead and quit, and let's switch back to our slides. So what did we just do? We populated an NS table view with data we pulled out of our Manage Objects using value for Keypath. We demonstrated how to traverse a relationship between our recipes and our chefs. And you don't have to fetch everything out of the stack. You really just need to fetch something that you can use to traverse relationships to get at your other objects from.

We created a custom NS Manage Objects subclass. This was a fairly simple one. You can create your own much more complex ones with your own business logic, customizing your model classes. We demonstrated the nullify and the delete cascade rules. And there's one more thing, a custom data formatter. Let's jump back to hands-on real quick and show that.

Can we see our hands on again, please? So say you wanted to display some information about an NSManage object in the debugger. Let's go ahead and put a break point in the delete recipes method. If you're following along, I wouldn't try and follow along for this part. We're going to go pretty quickly, type in some obscure syntax. To learn about custom data formatters on your own, I would look in the Xcode user manual. We're just going to give you a quick demo of this. So go ahead and put a break point on self-managed object delete object. Let's close this file and build and debug.

So go ahead and delete one of those recipes. Here we're stopped in Xcode's debugger. It uses GDB. And you can see in the upper right-hand pane where it lists the visible variables, we have recipe to delete, which is our NSManage object. But it doesn't display anything very useful about that. Say we wanted to display some useful information in the summary column about our NSManage object. We're going to use a custom data formatter to do this. So double click in the summary column.

This syntax is a little obscure. You can look it up in the Xcode user manual. What we're going to say is curly brace, then paren ns_string*paren to return an NSString. Square bracket, square bracket, $var in uppercase is essentially self in a custom data formatter. So we're going to send this variable, message entity, to get its entity.

Close square bracket, and we're going to send that entity description, the method name, to return its name. Close square bracket, close curly brace. Finally, I'm going to stick a colon S on the end to tell it to display the summary of the object that this returns, which sends it the description method. Let's go ahead and hit Return.

You can see now, instead of displaying no real useful information about our NSManage object, it tells us that it's a recipe NSManage object. If we had a custom subclass, we could call value for key, print some information about that managed object. It can be very useful when you're debugging. You can also open up the console and look at it that way as well. Let's go back to our slides.

So finally, our last step, we created a custom data formatter. So let's see what happens when you put Core Data together with Cocoa Bindings. Chris is going to walk us through this. Thanks, Rick. Cocoa is great. That's not a lot of work to hook your data up to a table.

With Cocoa bindings, you can do even more with even less work. So what we're going to show you is how to connect your prototype data model up to a prototype interface, the kind of interface that you might get from a human interface designer that you've hired, or that you might have just thrown together yourself while trying to design your application.

Then we're going to show you how to hook your single window application into Core Data's provided undo handling. Core Data actually provides full undo support across your managed object graph. That's part of why we call them managed objects. However, in a single window application, you need to actually tell the window to use Core Data's undo manager. And finally, we're going to iteratively add a couple features to our application. So let's go hands-on.

We're going to open-- excuse me. We're going to open example four, Core Recipes Cocoa Bindings Begin. Excuse me here. Let's just open up our nib file. On our demo machine, we'll probably have to do this by navigating the Open File dialog in Interface Builder. We're most of the way there already. Fortunately, we put this stuff on our desktop.

And we can see that we already have an interface set up. However, it's not actually wired up to our data model yet. So what we're going to do is take advantage of some of the new features in Interface Builder in Tiger and in our data model. So let's switch to Xcode and bring up our data model.

And let's drag that a little bit to the right so that we'll be able to see both our Interface Builder document and our data model at the same time, specifically the diagram view. Now, you've all probably seen the demo where you drag-- you option drag-- an entity from your data model into an Interface Builder design window and generate an interface for it. But you can also drag directly to an Interface Builder document window and create a controller. This is great if you don't actually want to use the default interface, but you do want a controller set up already with all of the appropriate keys for your managed object entity.

So the first thing we're going to do, since our interface shows recipes and ingredients, is option drag our recipe entity into our Interface Builder document. Interface Builder will pop to the front and ask us if we want to create a controller for one or many objects. Since we're going to be controlling a table view, We're going to tell it that we want a controller for many objects. This will create an NSArrayController.

You can see that we have a recipe array controller, and if you inspect it-- You can see that it's already configured to control an entity instead of a class. The entity's name is recipe. And it's set to automatically prepare its content. That means that the first thing it will do is it'll just fetch all of the instances of that entity.

Now let's actually set up the table. So double-click the Recipe table at the left of the Design window. That'll select the table view itself. Now click on the Recipe column. When we're working with bindings, we generally don't bind to the table itself, although there are some bindings on the table. Most of the time, though, we'll be binding values on the table column. So switch over to our Inspector and bring up the Bindings pane. and pull down the value.

Yeah, might want to not zoom quite so much. You can see that it's already set to bind to our recipe array controller. And we'll definitely want to bind to the arranged objects key of our array controller. This is the sorted collection of objects that were fetched. And we'll want to use the model key path. If you pop down the model key path combo box, we'll want to use a model key path of name.

You can see all of the keys for the recipe entity in that model key path combo box because we dragged our entity over from Interface Builder and told Interface Builder all about it. Next, let's set up our ingredients table. Let's go back to our data model again and scroll a little bit to the right and do the same thing. We can just option drag directly to the Interface Builder document and create a controller for many objects.

Inspecting our Ingredients Array Controller, we can see that, again, its managed object context is pre-bound for us. We can also go to the Attributes Inspector. and see that it's configured to be an entity array controller for the entity named Ingredient. Now, if we go back to our bindings, We can see that only the managed object context is set. What this means is that this controller, since it automatically prepares content, will immediately try to fetch all of our ingredients.

Since our interface is kind of a master detail interface, we don't necessarily want that. Instead, we want to be able to select a recipe In the first column, and then in the ingredients table, see only the ingredients for that recipe. So what we can do is we can bind the content set binding of our ingredient array controller.

So pop that down. And we can use the recipe array controller Actually, no, we do want to use the Ingredient Array Controller. Sorry. No, we want to use the Recipe Array Controller. Getting myself confused here. We'll want to use the Selection Controller key because this represents the actual selected objects in the Recipes table. And we'll want to use a model key path of ingredients. This is a too many relationship key, but it's still a key.

So let's close this up. Close the content set. I'm sorry. And now we need to actually set up the bindings on the individual columns in the Ingredients table. So just like the last table, we can double-click the table view and then click the ingredient column. And pop down the value binding. And bind to the Ingredient Array Controller.

and use the controller key arranged objects and the model key path of name. Now, just highlight the amount column. You can see that we're now inspecting the amount column, and our bindings inspector has barely changed. Obviously, this isn't bound yet, so our bind checkbox isn't checked, and our model keypath isn't filled in, but our bind to pop-up is still set to what it was the last time we used this pane.

This is sort of a shortcut that makes it very easy to successively set bindings on various controls in your window. So now we can just pull down the model keypath and choose amount. And we can do the same thing for the Measure column and choose the model key path of Measure.

Finally, we have a big text view at the bottom of this window. This is where the selected recipe is going to show its ingredients. So let's double click this. And that will let us inspect the text view. You want to make sure that you're inspecting the text view and not the scroll view itself.

And you see that we have several value bindings on the text view. What we're going to bind to is the data binding, because this text view is actually going to let us put in styled text. So we're going to pop down the data binding. We're going to use the recipe array controller with the selection controller key.

And we're going to use a model key path of directions. If you were to look at this in the model, you would see that the actual attribute type of the directions attribute on the Recipe entity is binary data. But we're binding this to a text view, so what we're going to need to do is add a value transformer.

And just pop that down and use NSUN Archive from Data. And this way, we can maintain the fidelity of the data as we push it back and forth between the model object and our bindings. So we only need to do a couple more things. We need to set up our add and remove buttons. For this, we're just going to use target action. So highlight the Add button under our recipes table and Control-drag from it to the recipe array controller. An Interface Builder will helpfully switch to the target action subpane of the Connections Inspector.

And let's just choose Add. Connect that up. And now, Control-drag from the Remove button right next to it to the REST PRA controller. And you can see the Interface Builder automatically highlights the Remove method. It tries to help you, and it tries to derive what to do from what you're doing. So let's just connect that up.

and do the same thing under the Ingredients table. This time, we're going to Control-drag from the Add button to the Ingredient Array Controller and choose its Add method. and from the ingredient tables remove button to the ingredient array controller connecting to remove. We now have our interface wired up. Let's save this Interface Builder document. and go back to our application. Now we can just build and go.

And we have no data at first, so our application doesn't show anything. But because we have Core Data managing our object graph and we have bindings managing our interface, we can actually start using our application. So let's click the Add button under Recipes, and we can add a recipe. Let's add another recipe too. And let's just call the first one Apple Pie.

and the second one, Chocolate Cake. I'm in kind of a dessert mood this morning. So let's highlight Apple Pie and add an ingredient to it. And you can see that we added a new managed object which can have a default value for each of its attributes. So the default value for the name attribute of an ingredient is new Ingredient, and the default value for the amount of an ingredient is 1.

So let's just add some apples here, since that's the main component of Apple Pie. And Rick can pick the amount. He's the chef. And for our measures, we can just say apple. And of course, we need more than just apples and apple pie. We also need some pie crust. Can buy that stuff ready-made, I guess.

And let's just, let's use two because it's kind of nice to have the strips layered on top. And we're just going to use Crust for the ingredients. Now let's exercise our Remove button here. Let's highlight the chocolate cake recipe, since we're not going to fill that in yet, and click Remove. And quit our application.

And we're just going to use Crust for the ingredients. Now let's exercise our Remove button here. Let's highlight the chocolate cake recipe, since we're not going to fill that in yet, and click Remove. A lot of users, though-- well, actually, I'll hold off on that. Let's actually change the pie crust to just say crust.

Now let's try to undo this. So go up to the Edit menu, and Undo isn't highlighted. Well, this is because we made a small change to the template for purposes of illustration. We can quit the application. and go back to Xcode and open up corerecipescontroller.m. Now, if you're building a document-based application, the persistent document machinery handles hooking up your Managed Object Context's Undo Manager to your window.

And our application template allows you to do that too, but if you're building your own application and you're creating your windows on your own, you'll need to know how to do this. Basically, a window gets asked for an undo controller to use. So since we're using Core Data, we want to provide that window with our managed object context undo manager. So we do that by making our object the windows delegate, which we've already done for you in Interface Builder. And then we fill in the window delegate method. Window will return undo manager.

So we just have a single line of code commented out here for you that gets the undo manager from our managed object context and then returns it as the Windows undo manager. So if we build and run again, We can see that our change was saved. Now let's change that back to Pycrust.

and Undo. We can see the Undo is highlighted, so we can undo that change, and we can even redo that change, because now the Windows Undo Manager and our Managed Object Context Undo Manager, which is managing the object graph Undos, are one and the same. So we can even undo and redo adding and removing objects. So let's add a new ingredient, and let's undo that.

Core Data provides you with automatic undo and redo support. So we save on quit, but a lot of users like the security of also being able to save whenever they want. After all, sometimes you have power outages. Sometimes your applications crash, especially during development. And if you look at the File menu, we actually have an active Save menu.

However, it's not actually wired up. It'll look like it's wired up, but it's not actually going to do anything. So let's quit the application and make that work. Well, I've already done the work in our sample of wiring it to the Save Action. In our controller class, but we haven't actually saved. So let's uncomment that comment block.

And you can see that all we're doing is asking our managed object context to save our object graph. And then if we get an error, we use the new error presenting machinery in our application to present that error to the user in a nice form. So let's actually exercise that. Let's build and go, saving as we go.

Now let's add a new recipe. And just as an aside, Core Data also provides validation machinery. We have a validation rule on recipe that says that you need at least one ingredient in a recipe. You can't save a recipe without ingredients. So let's try to save one using File Save. We'll get an error from our application using present error that says, too few items in ingredients. It's actually picking up that the ingredients relationship on our recipe So let's click OK.

Let's remove our new recipe. And let's try to save again. And this time we saved. So what have we learned? Let's go back to the slides. Well, we created a prototype application from a prototype human interface and a prototype data model. The human interface is something that we might have gotten from our human interface designer or something we may have knocked up in Interface Builder while we were making our proposal for our project. We integrated Core Data undo handling in a single window application. And this is exactly the kind of thing that you'll need to do if you're building an application with lots of windows, where you're creating the windows on the fly.

And we started iteratively adding features to our prototype application, fleshing it out. We've used the power of bindings to make it very easy to get our interface up and running so we can use more time to actually add features to our application. Now, Rick's going to show us what happens if you sit and do this for a while with the Core Recipes application.

Thank you, Chris. So if you want to open this up and take a look at it, it's in the Core Recipes sample applications folder. It's called Core Recipes App. We've already built it here, so we won't take the time to do that. But on your own time, I encourage you to take a look through this. It demonstrates a lot of advanced techniques you can use in Core Data.

This is a recipe manager written with Core Data, Core Recipes. This application works with both the XML and the in-memory stores. allows you to use multiple stores all under one persistent store coordinator. In fact, when you add new stores to your machine, it automatically finds them with Spotlight. and adds them under the Persistence Store Coordinator so they show up in your Persistence Stack.

It uses separate managed object contexts for each editor window. This allows each editor window to have its own undo stack to allow them to be saved separately without having to save them all at once. And those contexts are all sitting on top of the same persistent store coordinator.

It allows you to create smart groups using NS predicates and fetch requests to fetch a subset of the recipes in your library. It generates URLs for managed objects for recipes that you can use to go back and look at a particular recipe in the application. It integrates with Spotlight, as I said. And this is a bindings application. So let's take a quick look at this.

Just going to open it up. Like I said, we've already built it. You see it's got an iTunes-like interface. We can add a new group. And if we get info on that group, we can build a predicate for it. This is similar to the Predicate Builder, but what it's really just doing is programmatically building an NS predicate. which we'll then use to set on our fetch request to fetch a certain set of recipes. We can go to our library and look at a recipe. You open it up.

Let's close this and take a look at building a URL for a recipe. Close this recipe and we've got a little button in the upper right that'll save it as an RTF document. Export it. If you look in that RTF document, it's formatted nicely. Down at the bottom, there's a URL to go back to our recipe in Core Recipes app. And if we click on that, it opens it right up.

We don't have time today to show you all the features of Core Recipes app, but like I said, I encourage you to take a look at it, read through the code, read the README file, play around with it, see the sort of things you can do. So let's jump back to our slides. And Chris is going to go over a few advanced topics quickly for us.

So really right now, what we've done is give you sort of a taste of what it's like to develop with Core Data. But as you go on and develop your applications, there's some advanced topics that you might run into that we haven't covered. For example, if you actually look at all these calls that we make to add persistent store with type, which adds a persistent store at a URL to a persistent store coordinator, you'll see that there's a configuration parameter. But what does that actually mean? Well, a configuration is just a named collection of entities that you set up in your data model.

And what this lets you do is constrain your storage for a particular store to a subset of the entities in your model. For example, you can store some entities in one store, and you can store some other entities in a different store, but use the same model. Now, you can't create relationships between entities in different persistent stores, but you can record, for example, object IDs by using the URI representation. Oops.

We also have this concept of transient properties. As you're editing your model, you'll see a transient checkbox on your attributes. that might be a little, you know, mysterious. I mean, we talk about Core data as an object graph management and persistence framework. Well, transient attributes get you all the management of Core data, but without the persistence.

So these are really useful for computed values that you need to model. For example, we added the Chef -- the full name key under Chef. We could have added that as a transient attribute and implemented it in the same way. However, what we would have then been able to do is actually option drag our Chef entity into an interface builder design window and generate an interface that included that full name attribute.

This also lets you use your own value classes. For example, you can create an attribute with an undefined attribute type that is backed by, for example, another attribute in your entity. And you can give your first attribute, say, an NSColor. Now that NSColor can't be stored by Core Data because Core Data doesn't know how to store those, but you can turn it into a data object that you store in a separate attribute and that you populate your transient with on fetch.

And finally, this is a big topic that's been coming up recently on the Cocoa Dev mailing list, copy and paste and drag and drop of managed objects. This is something that you'll need to think about a little bit. It's not supported automatically, but you can, if you're just doing things within your own application, pass around object IDs.

Now, you're going to need to think a little bit about this, though, because you won't want to pass object IDs to newly inserted objects between contexts. Because the contexts sit on top of the persistent store coordinator, but until an object is saved, it sort of lives in that context alone.

So a better strategy might be to encode just the data that you need from that object on the pasteboard. Of course, once you start encoding data from your object, you need to consider what this means for relationships. After all, you don't want to actually encode your entire object graph onto the pasteboard when you're just copying a single object.

So you need to consider whether a relationship represents containment. For example, a recipe has a bunch of ingredients, and those ingredients are tightly bound. They're contained within that recipe. Or whether it just represents association. For example, a recipe also has a chef, but it doesn't own that chef. That chef actually owns the recipes.

So copying a recipe shouldn't copy the chef, but it should copy its ingredients. So these are just some of the things that you might want to consider. And then you might want to start thinking about as you start building applications with core data. and for more on this topic, you can attend Advanced Core Data Usages, Session 143 tomorrow at 2:00 PM. Now Rick's going to tell us where to go from here.

Thank you for coming today. If you're looking for more information, such as our documentation, sample code and other resources, please go to the WWDC 2005 website. Also on your Tiger DVD when you install Xcode it will install developer examples, if you look in Developer Examples Core Data you'll see some examples there.

We've got some excellent Core Data documentation written by Malcolm Crawford online and in the documentation installed with Xcode. We've got a bunch of Core Data sessions left today. We have two Core Data labs, one at 12:00 and one at 3:30. I encourage you to come there with your code, with your questions, and we'll sit down with you and help you make the most of Core Data. There's a session on getting the most out of Cocoa Bindings today at 2:00.

Cocoa Bindings doesn't need to be used with Core Data, but they go hand in hand very well, and I recommend you attend that one if you want to build Cocoa applications that work well and build them quickly. Tomorrow at 9:00 we have our feedback forum. Come and give us your questions, your comments, your concerns, and your raves.

And tomorrow at 2:00 p.m. is the advanced Core Data usage session. We'll go over a lot more of the advanced tips and tricks you'll need to use when you're building a real complex real-world application. We'd love your feedback on how this session was for you, so please talk to Xavier Allegros or Matthew Formica. You can find their email addresses here or probably on the WWDC website, and let them know what we could do better next time, how it worked for you. Thank you for coming, and have a great rest of your day. of WWDC.