Application • 56:56
Learn about the more advanced features of the new Core Data framework, including how to work with multiple persistent stores at the same time, how to use predefined fetch requests and predicates to find your objects, how to get more out of your validation rules, and how to manipulate schemas at runtime.
Speakers: Ben Trumbull, Melissa Turner
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to session 436, Advanced Core Data Features. Like the slide said, this is a confidential session, so please refrain from taking pictures, camera phones, filming, microphones, that kind of stuff. I am... Melissa Turner, I'll be joined on stage later by some of my co-conspirators in the code that we're about to show you, Ben Trumbull and Chris Hansen.
So what is Core Data? For those of you who didn't make yesterday's session, Core Data is a model-driven object graph management and persistency framework. What does that mean? Basically, it's a framework that gives you scalable object lifecycle management, gives you automatic undo, redo, tracks changes in your object graph.
I suppose I should say at this point, object graph is a fancy way for saying collection of objects that actually have pointers to each other. It gives you-- ensures data consistency and correctness. And the persistence part will allow you to read or write to various types of files. And we're integrated with the Cocoa bindings to give you user interface synchronization.
Why should you use Core Data? Well, it relieves you from having to reinvent the wheel. There's a lot of persistence frameworks out there. I'm sure many of you have actually written one. You don't need to redo it over and over again. There's nothing terribly app-specific about it. We give you a choice of persistent stores, so you can pick the one that best suits the type of application you're writing.
One of the big advantages is it prefers your application for future features in Cocoa. If we're going to add new stuff, it's most likely going to be tied in with the stuff we've already written, so if you're using the stuff we've already written, you'll probably get those new features for free. It gives you a faster development lifecycle. Writing an object graph management or persistence framework is not a simple thing.
Trust us, we've been working on it for months. We'd like to spare you that and let you concentrate on writing the stuff you want to write. Because it's there, it's used by a number of other people, it's a lot more likely to be robust. Other people are going to find the bugs. You don't have to do it yourself.
In this session, we're going to sort of give you an overview of the architecture of the system, talk about the individual components. We're going to talk about how they're all put together. We're going to talk about some of the stuff you want to do, or if you're advanced, you're going to want to do with the stack. And we're going to give you a few, as we go through, tips and tricks, things you should know, things you should know not to do.
So what are the pieces of the Core Data architecture? Well, we're built-- there's four basic components. There's predicates, models, managed objects in the managed object context, and there's object stores with the store coordinator. And like I said, we have some integration with the Cocoa documents and controllers. Core Data Xcode UI can be used to graphically create models and configure how Core Data is going to work in your application.
Here's the basic architecture. Up at the top, which we're not going to talk about in this session, is the user interface, which is connected with the binding stuff. Below that, we've got the controller layer. Again, we're not going to talk much about that. Middle part's where we start getting interesting. That's the Managed Object Context. That is sort of the core of the object graph management part of the system. It manages the managed objects. We've got a persistent store coordinator living below that, which talks to a model and a data file.
Well, I've mentioned models several times. They're sort of really the most important part of the system. They're the blueprint. They're the thing that describes what data exists in your application and how those objects are related to each other. It tells you what interactions you've got going, what needs to be updated if something else is updated, all that kind of stuff. The model is really sort of the description. It contains all the really important application logic of your system.
It's the foundation for managed objects, and you can create it from Xcode or programmatically. Xcode gives you the basic development tools, the thing you use up front. It's nice, it's spiffy, it's graphical, it's visual. You can see what you're actually doing. Often that's not quite enough, and you want to make some minor runtime tweaks to the model, so we let you create them programmatically or modify them programmatically if you've already created them.
Why do we use a model? It's a foundation for data-driven development. What that means is you can concentrate on the data architecture of your application rather than the control flow through it. You think about what objects you have, how they interact with each other, as opposed to, well, if I click this button here, then I need to do this in this object, and if that then, or while, don't need to think of any of that. You just concentrate on the objects. We take care of the rest of it. The model, as I said, defines this data architecture, and what this allows you to do, as some of you saw in yesterday's session or Bertrand's keynote, is get behavior without writing code.
What's in a model? Some of these terms are going to be familiar to those of you who are familiar with entity relationship modeling, but a model contains entities which have attributes, relationships, and fetch properties. It also contains templates of fetch requests, which I'll talk about all of this later, and it contains configuration information.
This is the classes these things map to. This is what you should be looking for in the API headers you've got on disk. Entities map to instances of NSEntityDescription, attributes to NSAttributeDescription, relationships are NSRelationshipDescription, fetched properties are NSFetchedProperty, and fetched request templates are NSFetchedRequest. Configurations, well, those are just strings usually with sets associated with them.
NSNC description is loosely analogous to class definition for data type. It supports inheritance, which means you can have abstract entities, things that are never actually instantiated in your application. Entity inheritance is not necessarily the same as class inheritance. You can have an entity inheritance hierarchy that does not map one-to-one with a class inheritance hierarchy.
As a matter of fact, you can map everything to NSManagedObject if you want and skip having a class hierarchy entirely. It has multiple property types, the two most important of which are attributes and relationships. And they're editable only until first use. This is something important to remember. Once you fetch data into an application using an NSEntity description, you can no longer change that NSEntity description.
It gets too complicated trying to figure out what we're supposed to do with that extra pieces of data and new stuff, old stuff. You can't do it. You'll basically have exceptions thrown. And by the way, that's true of all the classes I'm going to talk about in the model section.
NSAttributeDescription is analogous to a field containing an NSValueType. We support all the standard NSValueTypes, or standard NSDataValueTypes. You can provide a default value using an NSAttributeDescription, and you can set validation rules. This attribute has a minimum value of this, has a maximum value of that, must have, for strings, can set a wildcarded pattern, can set a maximum string length, that kind of thing.
Attributes can be optional, which means they don't necessarily have to appear. And they can be transient, which is a concept that makes a lot more sense when you think about it in the context of there being a persistent layer. But a transient attribute is one which is managed by the object graph management layer, but which is not actually persistent. This is the kind of attribute you'd use for storing caching information, stuff that's derived at runtime and can be recomputed, but that you just want to keep around for convenience and speed purposes. It's pretty much a scratch pad.
Relationship descriptions. Relationship descriptions, well, those are pointers to one or more managed objects. They can be one-to-one, they can be one-to-many. They support minimum and maximum counts. This bicycle has two wheels. This vehicle has at most 18 wheels. You can specify both of these values. A relationship usually defines an inverse relationship.
There's some rare contexts in which it doesn't, and if you don't know the specifics, you want to define an inverse relationship. It specifies a delete rule, what should happen when an object is deleted. The options are nullify, when this object is deleted, delete the objects in all of its relationships. Cascade? No.
Nullify is when this object is deleted, just null out the back pointers in all of the relationships. Cascade is delete all of the children in its relationships. Deny, if this object has any objects in its relationships, you're not allowed to delete it. No action means don't bother touching anything on the other end of the relationships.
It also allows validation rules like the attributes. And like the attributes, it can be optional or it can be transient. NSFetch property is kind of a special beast. It allows you to make a unidirectional link between an entity and a query. This allows a single managed object to have a loose relationship with a collection of other objects, and that relationship is not managed. It's always too many because we can never tell how many objects the fetch is going to bring back. And it uses an NSFetch request, which we'll talk about next, to specify the query. The concepts of optional and transient don't really apply here since, well, it's a query.
NSFetchRequest is an object-oriented representation of a query operation. It specifies a query that you want to search for, or an entity that you want to search for, a predicate that you're going to use to define which instances of that entity you're looking for, a sort descriptor that tells you what order you want them to come back in.
Fetch requests in the model are named templates, which means that they contain predicates that can have variable expressions in them. If it has a variable expression, you need to do a variable substitution at runtime before you use it. And you get an instance of a fetch request from the template from the model using fetch request from template with name set substitution variables.
And one thing to note is that these fetch requests, the predicates will be evaluated in the store where possible. So if you have an SQL light, you can do that. If you have a SQL light store, the predicate will actually be translated down into a where clause, which SQL light will actually run. Some of the other stores actually do in-memory queries.
Configuration is essentially a named collection of NSEntity descriptions. It's typically mapped to a single persistent store. This collection of entities is mapped to this store. This other collection of entities is mapped to this store. Something to note is that a single entity can belong to multiple configurations. So you can get instances, managed objects of a given entity from multiple stores. I can pull an address from LDAP or from AddressBook. I can get a song in my playlist from my local repository, or it can be something I pulled from a rendezvous store.
The programmatic interaction with models is actually pretty simple. If you want to load a model, we have NSManagedObjectModel init with contents of URL. Sometimes you want to merge multiple models that exist in multiple frameworks. We have init with contents of URL and then model by merging models. Something to note is that this will get a little bit upset if you have multiple entities with the same name. But if you have configurations with the same name in each model, the entities in those configurations will be merged into one uber configuration.
Programatically creating a model is very simple. It just takes a lot of steps. First thing you're going to want to do probably is create an entity description. Here we're creating an album. You set the name on it to album. Most albums have attributes, so we're going to give this one a title. Just create an attribute description, set its name to title, set its type to string.
Create a song, since albums usually have songs. It's another entity. Just give it a name. Create a relationship. The entity comes from the entity album and sets the destination entity songs. This means that we're creating a relationship from the album to songs. An album has multiple songs. Set the relationship. Set the properties on the album.
Set the entities in the model, and now you've got a really simple model. Has two entities, couple of attributes, doesn't do much. And it's actually a lot of code, even though none of it's terribly complicated. So we're going to bring Chris Hansen up on stage to show you why it's not actually as scary as you might think it is.
CHRIS HANSEN: Can we go to demo one? Can we go to demo? There we go. Thanks, Melissa. So what I'm going to show you is a conversion of an existing application, an existing Cocoa document-based application, to use Core Data. I'm going to show you how it's actually pretty easy to take your existing software and bring it forward to the new world. So I have a lot of books, and I like to lend them to my friends. But I need to keep track of what I lend, because otherwise I'll just forget. And then my library will get smaller.
So I have this little lending library application, as soon as X comes up here. And it's very simple. I'll just start it building now and walk through the code. It's a simple Cocoa document-based application that has just a couple of very simple model objects, book and friend, and the document itself. persists using data representation of type and load data representation of type, just using NSKey to archiving. Bigger font size. Got it. Text editing.
Sorry about that. Syntax coloring. Monaco, what do we say, 14. Bigger than that? Bigger? Okay. That was 18. There we go. So its persistence is really simple right now. We're just adding a couple-- we're just adding our objects, our two arrays of books and friends to a dictionary, and then writing that dictionary out using keyed archiving.
The first step in bringing this forward is, of course, to duplicate the project. So I'll just make a copy of this and open up the new project. And then I need to actually make a model for Core Data to work from. So what I'm going to do is add a new group called Models.
And then add a new file to that group. And that new file is going to be a design data model. I'm just going to name it Lending Library. And here I get to pick classes to add to that model. So I'm just going to pick book and friend and add all the classes that are in those header files. And I get a model.
And if I show the class browser here, or the model browser, I just went to Design, chose Show Model Browser. And let's see, I'll kick this up for you too. We can see that it picked up the actual types of the attributes and even the type of the relationship by picking the destination. However, that relationship is named toFriend, and in the actual file here, It's called Lent2, so I'm just going to go back here, and I'm going to change this to Lent2.
Now, we also can see that here it's actually set the class names appropriately. So these model objects will be backed up by instances of these classes. However, right now these classes just descend from NSObject and implement NSCoding. Since Core Data is going to manage all of our persistence and all of our change tracking for us, we don't really want to descend from NSObject anymore. We want to descend from NSManagedObject.
And since ManagedObject handles all of our persistence for us, we don't need to use NSCoding anymore either. So we can actually get rid of these attributes, and we can even get rid of this relationship, I'm going to get rid of a couple of accessors, and I can even get rid of that relationship accessor, because everything in our interface is going to be done through bindings and key value coding. Now I can also get rid of my DLX method, because I have no more storage to manage myself. Core Data manages all that for you.
I'm going to convert my setAuthor method to show you how you might want to do this in your own applications to use what we call primitive key value coding. So the first thing I need to do is, in setAuthor, I need to inform the rest of the system that we're going to change the object. So I'm going to just say self will change value for key of author.
Then I'm going to actually do a self-set primitive... didn't complete for me... Set primitive value for key will actually call into our superclass implementation, which manages all the storage for us, and do the right thing for both attributes and relationships. It checks against the entity. And finally, I'm going to tell the system that we did change the value for key.
And this way, if your application uses your accessor methods, you don't have to get rid of them and just go through key value coding. You can still use your accessor methods. You can just make them a lot simpler and act as covers for primitive KVC. And I need to do the same thing in author. I just need to say a string author.
will access ValueForKey. This tells the framework that we're going to start accessing that key, so if it needs to pull anything into memory, if it needs to fire any faults, that will happen. Then we say author equals self primitive value for key. to actually get the data. Then we say self did access value for key. There's a lot of symmetry in this framework.
and finally we return the value that we retrieved. But if you're just using key value coding, key value coding will do all of this for you. So you don't need to actually even have these accessors. If your entire interface is built through bindings and you're using key value coding, you're fine. And this works both for attributes and for relationships.
And finally, we also don't need our NS coding support anymore, because all of our persistence is being handled by Core Data. And we can do the same thing to our friend object. We can just convert it to superclass. Get rid of its IVARs. Get rid of its accessors. And, well, let's see. Pretty much get rid of all the code in it.
Now we only have a couple more things to do. I think three more things. First, we need-- since Core Data is managing our persistence, we can also get rid of the instance variables that we're using to store our collections of books and friends. In our document, we just need to tell it that it's not an NS document anymore, it's an NS persistent document.
We don't need these anymore because everything's going to be accessed via key-value coding, and same with these. And of course, we can delete the implementations, too. So... We don't need to actually handle our own persistence anymore, so we can delete these. And finally, since we don't have any more IVARs to memory manage, we can delete init and dialog, too. So this is our new document subclass.
Now we only have two more steps. Our first step is to go in and-- Change the type of document that we handle. Right now, I'm just handling a document type of lending lib. I'm going to change that to XML so Core Data knows to use the XML persistence.
And then I'm going to go and make a couple minor modifications to my nib file. Our nib file still has some knowledge about the classes that we were using, and we need to update it. So our nib file is very simple. It's just a simple two-tab interface with a couple of table views, all set up using bindings.
This application would run fine on Panther before moving it to Core Data. We don't actually need to do anything with the table views. All we need to do is modify our array controllers. So our array controllers right now point to a class and a class name. We're going to change them to point to an entity and an entity name.
So friends points to the friend entity, and books points to the book entity. And then we're going to set one additional binding on each of the array controllers. This binding-- well, and we're going to remove the content array binding, because we're not actually getting the content from the document anymore. Instead, we're going to connect the binding on each of the array controllers for its managed object context.
And that comes from our document. Every persistent document has a managed object context and actually also has its own persistent store coordinator. So that handles all of your change tracking for you automatically. So I just did that for books. Let me go do that for friends. Actually, just object context. Context, bind, get rid of content array, save.
And we're set. So now I'll just build and run this one. Actually, while I'm building this, I'll run the original. And as you can see, it's just a very simple document-based app. Can add an author of a book I have, say John Brenner, the shockwave writer. I can add another book that I have, Douglas Adams.
The Hitchhiker's Guide. And I can add a couple of my friends, Ben and their phone numbers in case I need to contact them because I need a book back really quickly. and Melissa. These aren't their real phone numbers. Don't try them. And I can see that in my Lent 2 pop-up, I can set, oh, while I lent Ben the Shockwave writer, I lent Melissa Hitchhiker's Guide to the Galaxy.
And notice what we don't get. If I add another book, say, Weinberger, The Clue Train, Manifesto, and I want to undo that. I can undo the typing in there because that's handled by the text subsystem. But I can't undo anything in the actual document because I haven't written any undo code for this application.
But this is the old version. Notice also that I don't get any document dirtying. Again, I would have had to write code for that. I still can save and load documents, though. But let's check the new version. If I run this-- I can start out, I can say John Brunner, the Shockwave writer. I can say Douglas Adams. Hitchhiker's Guide.
I can see that I get all the same table view behaviors. I haven't touched this part of the interface. There's no value for these pop-up menus because I haven't added any friends yet. I can add a couple of friends. and they show up in the pop-ups. And all of this relationship management, all of the persistence that's going to happen when I hit Save is going to be handled by Core Data. But notice what we also got.
We also got dirtying. The document is now marked as modified. So if I just try to close it, it automatically comes up and asks me if I want to save. So I'm just going to save this on the desktop. I'll just call it My Library. I rerun the app. I choose Open. I go to the desktop. I open that up, and everything's in there. So it's just round trip through Core Data.
Now I'm going to try adding Weinberger's book again, Weinberger, the Kluetrain Manifesto. But I lost that book. It's not in my library anymore. So I'm just going to undo. I can undo that, and I can undo that. And then I can go up here, and I can see, oh, I can redo those insertions.
So I just redid the add of that record. Now I redid the update of that record, and now I redid the update of that record. And that all came for free just by using Core Data. So now I'm going to turn it over to Ben, who's going to tell you a little bit more.
Good morning. My name is Ben Trumbull, and I'm one of the senior engineers with Chris and Melissa in the Development Technologies Group at Apple. And I'm going to talk to you today about the two classes that you'll probably interact with the most in the Core Data framework. And the first is NSManagedObject. If you take something away from this session, if you're looking for a noun to work with from Core Data, you'll probably find it here.
So NSMagic objects are instances of an entity. And they always have an entity description in the same way that a Cocoa object always has a class. So it's the data equivalent. They're a generic data object. So as Chris showed you, you don't need to use storage. You can just have it go through and magically will manage the memory for you.
You can use key value coding to access those fields and get back the values. It's a basic unit that you're going to be working with. These are the objects that you push around. You fetch, insert, change, save, et cetera. And they all implement key value coding and key value observing for you.
The fundamentals is each instance is described by a single entity and has a reference to it. Each one has a unique object ID, and each one is associated with a single managed object context, which we'll get more to later. It's important to note that each managed object can only be used by a single managed object context at a time.
And subclasses can implement custom logic. As you saw in the demo that Chris did, a lot of times you don't really need any custom logic in your subclasses. You don't have to have the subclass. You can just say in your nib or whatnot that you're going to use the entity, and we'll automatically just use the generic superclass, and it'll get all the behavior for you as long as you don't need any accessors or anything custom.
So to take a brief tangent, it's interesting to note that the IDs that are unique for each managed object are objects in and of themselves. And they're an instance of NSManagedObjectID. And one useful feature of this is that different managed objects with the same ID refer to the same persistent data. So if you have two objects and they have equivalent object IDs, then they refer to the same XML node and an XML file or the same row in an SQLite database.
So one of the things that you can implement either in the model or in a subclass is validation. And the managed objects do validation upon themselves. These validation callbacks are invoked at save time. So you'll get a callback for deletion, insertion, or updates. And you can also validate on individual properties. I have lost my formatting.
But at the very bottom there, you'll see Validate Key. And key here is not the method name, but the name of the property that you're validating. So in the previous example, you might have Validate Book or Validate Friend. And you can just go through in your subclass to validate on an individual property.
Other useful managed object callbacks are Awake from Fetch and Awake from Insert. Here's a great place for you to initialize transient properties or to set up any caches or do any calculations that you want to set aside for the object. And two other callbacks for you are WillSave and DidSave. And a subclass can do something interesting here. The Awake methods are generally more useful, just to set up initialization.
So to talk a little bit more about primitive key value coding, these are the methods you use to implement custom accessors, setters and getters. There's a primitive value for key and a set primitive value for key. They're only for your use in the implementation of an accessor method.
The framework uses them to do low-level initialization, to do bit shuffling, and stuff like that. They don't go through the regular key value coding, and they don't perform any kind of validation, any kind of argument checking. They assume you kind of know what you're doing. So they're fairly low-level, and they don't manage any of the integrity for relationships.
To create a managed object, typically you'll just do a fetch. As you saw in Chris's example, When he opened up the document, they just come up for you, and they're automatically created for you and registered with their context. If you want to create a new one, you just alloc insertWithEntity and give it an entity. And after that, you pass it to its context, and you say insert object.
One other thing you can do is if you decide to use Core Data without its native persistence mechanism, you can use your own persistence mechanism or use the NSCoding behavior you already have, and just use the context to do change tracking and do the undo/redo, and you can do the saving yourself.
In this case, you can use registered object to tell the context, this isn't a new object. It doesn't need to be inserted into the data file. You've pulled it off from some other data store somewhere, and you just want it to know about the object to track changes.
So the NSManage objects have all the nouns. They have the entity description. They have the object ID. They have all the data from the data store. The verbs live here in the NSManage object context. So this section, a lot goes on here. So we have a little preview. So we're going to talk about fundamentals. I have a slide on fetching, inserting, deleting, updating, saving, reverting, notifications, memory management, and threading. Like I said, a lot of verbs with the Manage Object context.
So this is the primary center of the framework for your use. It initiates all the actions, and it does all the heavy lifting for the change tracking. It performs retrieval of the objects from the external store. It does the saving and the reverting of changes. It implements undo and redo. It propagates the deletes and maintains the inverses.
And the exact behavior of the delete propagation and whatnot happens based on what you've set in your model in the energy description. And it also is involved at the center of all the activities. So it's the one that posts the notifications and does the callbacks. For fetching-- It's a little small, but there's an execute fetch request method.
And you just pass it a fetch request, and the managed object context goes off and figures out who to pass that request to to have it fulfilled. The only mandatory part of a fetch request is the entity, and everything else will do a useful default. One important feature to note is that the context will merge in changes that are pending, that haven't been saved out, into the results of the fetch.
So if you have a bunch of inserted objects and a bunch of deleted objects, and you execute a fetch, if the predicate that describes the search you're doing matches the insert objects, those will get included in the results you get back. And the deleted objects will be excluded.
And changed objects will be included if they match. But if you change an object so it's no longer True, when it values to the predicate, then it will be excluded as well. And you can always create a new managed context and do the fetch directly with that, or you can do a reset.
So walk through a little bit the fetch control flow. In this diagram here, A is the fetch request. Turns out graphic artists are very, very literal. Yes. So you create the fetch request, and you pass it to the NSManageObject context. It takes the fetch request, it does some work with it, and then it passes it to its persistent store coordinator. Then the Persistent Store Coordinator figures out which object stores have the relevant data and passes it off.
So you can have multiple stores. You'll get back some results. Each of the stores does the fetch with its own native mechanism, whether it's an SQL query for SQLite or walking through the XML for an XML file. And then the results come back, and they get collated, and it brings them back. And they're sorted. And the NSManagedObjectContext merges in the change set so that the fetch accurately reflects what it knows as the state of the world.
OK, so we've talked a little bit about inserting. All you do is invoke insert object with your new managed object that you alloc and hit it with entity. It's best to define the initial values after you've performed the insertion. The reason for this is context only track changes after they know about objects.
And they only know about objects after you've called insert object or register object. And you won't get the correct undo redo behavior if you set a whole bunch of initial values and then you insert it, because the managed object context will assume that that was the initial state. So as you saw in the demo, you can undo the text edits that way. And when you're done, you'll just have a blank row.
Deleting an object, you just pass the managed object to delete object on the context. The deletes will be propagated through the relationships that that object has. And the exact behavior will depend on which delete rule you've set. We also talked about the different delete rules, nullify, cascade, deny, or no action.
It's important to note that if you use no action, this means you have assumed responsibility for maintaining the integrity of those relationships. So if you've got objects with pointers to each other, and you say no action when you delete one, you're going to get dangling pointers, and you've promised that you will clean that up. Another important thing to note is just because you've deleted an object, it's pending. This doesn't take effect until you save. So this is a perfectly good object. You can keep working with it, asking for data.
It's valid. And you can undo the delete. So it's really a pending of a delete. A lot of things happen when the objects change. And this is really an overview of what's going on behind the scenes. You don't really have to worry much about this, but this is what the context is doing for you.
A key value coding observation notification gets posted by the object itself. And the managed object context receives an internal callback. At this time, if it's necessary, it takes a snapshot of that object so it can maintain the correct undo/redo behavior. And then, it makes sure that the bidirectional relationships are maintained. And it flags the object as updated and queues it up.
It also queues up an end of event notification. So, it tries to minimize the amount of work it does for each change. So if you do a whole bunch of sets with key value coding or with an accessor method, it doesn't want to take up too much time. And it will coalesce things later at the end of the event.
So at the end of the run loop, we get our callback back to ourselves. We coalesce all the changes together. We push an action, a single action for each object, onto the undo manager stack. And we pass up a notification for you if you want to know about it. And that's the point where the bindings receive some of the notifications. And it's one of the points of integration with the Cocoa bindings. Thanks.
For saving changes, there's just a save method. You can pass in an NSError if you want to find out what happens when things go wrong, if things go wrong. And this is the same control flow as the fetch, except you don't get any results back. We group all the insertions together and the deletions and the updates. We pass it to the persistent store coordinator, and the persistent store coordinator figures out where everything goes. And then each store writes it out.
So reverting changes, you can use the rollback method. This just does a simple revert and restores all the objects to their state before they were-- sorry, the same state they had when they were last saved. So this will throw away insertions. It'll undo changes. And deleted objects will now go back to their previous normal state. You can also do a reset.
So the reset will basically take this context and flush it and restore it to its initial state. It'll forget about all the objects it's seen. And you can just reuse it. It's useful if the context belongs to something else like the nib. And you don't want to replace it with a new one.
But it's a similar effect to just creating a new one. And it also has the undo and redo methods. These use the NSUndoManager. You can get a hold of the Manage Object Context UndoManager and customize its own behavior, like set levels of undo or throw away all the actions, remove all actions. So it's just integration with the default foundation NSUndoManager.
notifications. The one that's most useful is the first one, the objects changed in managing context. And this happens at the end of the event. After we've done all the changes and we've coalesced everything together, you can find out what other people have done to your soup of objects.
Then when you do a save, after the save operation is successful, we post this notification. The Managed Context did save changes notification. And that might be useful if you want to know who's been committing changes to the file. The User Info Dictionary has the same structure for both these notifications. There's an inserted key, lowercase i, updated and deleted keys.
And both these keys map to a value, which is an NSSet. And the set contains all of the managed objects that are inserted, updated, or deleted. And it's also important to note that the managed object makes themselves individually post key value observing notifications with the normal foundation mechanism.
So just a brief overview on this. We touched briefly in the demo about faulting and why you have to call will access or will change, followed by did access or did change. And that's so that we can do faulting, where we basically prune objects that you're not using. And then if there's a relationship and you want to do something, we'll go and we'll fetch it for you to try to reduce the amount of stuff you have in memory. This is particularly useful with the SQLite database.
So this is analogous to kind of virtual memory. So it's that kind of faulting. When we talk about faulting, it's at a higher level than page faulting, but it's basically the same sense there. You don't really have to worry about it. It's fairly transparent. You touch something, and we'll bring it into memory.
And then Core Data, for the most part, is a big cache of things. And faulting is adding or replacing the cache lines. So your working set is mostly the changed objects. And you may have fetched other objects to work with them or whatnot. But once you're done with them, then we're going to keep little shells of them around. But we don't need all the data because it's still there.
And this could probably be a session in its own right, so I'm not going to say much about it, so I don't say anything wrong. But locking and unlocking the managed object context is pretty useful. It implements an NS recursive lock, so you can use it that way.
But it's not just for thread safety. It's important to signal to the framework that you're using this context, and it's basically wrapping a transaction. So this prevents other threads from interspersing their changes, mixing up the undo stack, or you save a bunch of threads changes together. This is why it's not enough to just lock in between an insert operation and then unlock after the insert operation. You want to group a whole bunch of things together, and this is how you do that.
It also prevents the framework from processing notifications that might interrupt your state. So basically, it's a signal that you're using this managed object context, and then you unlock it when you're done with your transaction. Great. And now Melissa's going to come on, and she's going to tell you about the rest of the framework underneath it. Just a pretty quick section to wrap up on. Thank you.
Thanks, Ben. So the stuff Ben and I have talked about so far is pretty much the stuff you're most likely to have to interact with. But there are lower levels in this framework, and this is sort of a quick overview for those who like to know how things work. You probably won't have to do much coding at this level, but if you do, or if you're just curious, this is how it works.
Each managed object context has a persistent store coordinator. The persistent store coordinator is pretty much the bridge between the object lifecycle management part of our stack and the persistence mechanisms. In many ways, it's like the managed object context and the managed objects are the center of your world. The persistent store coordinator is the center of the framework's world. You have one persistent store coordinator per stack and only one, but you can have many contexts. You can have many stores using that persistent store coordinator. It's really the thing in the middle that does, well, coordination.
The really important part about it is that it provides a facade of a single object stored in the managed object context. The managed object context knows that it has to talk to one thing. It doesn't care what that thing is, it has to talk to one thing. And we initialize it with a managed object model. That's important because this is where the managed object context and the underlying stores get their model.
I mentioned that a persistent store coordinator can have multiple stores. Object stores are things you interact with in this release only through the NS Persistent Store Coordinator, but so you know, they're the actual persistent backing store mechanism. We have three stores that we provide this release, XML, SQLite, and Binary. Each store type has its own advantages and disadvantages, and you can move data between stores transparently, or almost transparently.
They said we provide three stores and they each have advantages and disadvantages. The binary store is fast, but it only operates on the whole object graph. Your entire collection of objects that you're persisting comes into memory and is saved as one unit. This is fine if you've got a small application but can get unwieldy if you've got something with thousands or hundreds of thousands of objects.
The XML file is very much like the Binary Object Store, only it's XML. It's a bit slower, but it is externally parsable. So if you're building an application whose data you want to share with other applications, this might be the way you want to go. And then we have the SQLite store.
Scalable. It's easily the fastest store. And most importantly, it gives you the partial object graph management. This is the only one of the stores that gives you faulting out of the box. You don't need to worry about which objects get read into memory. As long as you're doing the will access and did access methods, The object graph management context will take care of pulling everything in and out of the SQLite store for you.
So now we're going to talk a little bit about how you initialize a stack. It's actually-- that covers most of what you need to know. You create the URL for the location of where you're going to find your model and create a model. Initialize the persistent store coordinator with the model.
Tell the persistent store coordinator, add persistent store with type. In this case, we're using an NSXML store. And the URL where you want that data to be stored. We create a managed object context and set its persistent store coordinator. At this point, you have a stack that's fully ready to have managed objects pushed into it, managed objects pulled out of it. Of course, as Chris showed you in the demo earlier, that's how much initialization you've got to do if you're using a document-based application. We do it all for you.
Some more details about working with stores. There's a few other things you might want to do. Here we're doing the same kind of creation and adding of a store that we did in the previous slide. But we're also showing you how to migrate. It's pretty simple. Create a URL and tell the persistent store coordinator to migrate data from store one to another store created at that new URL.
Once you've done that, you might want to remove the old store. Again, it's simple. Tell the persistent store coordinator to remove it. But once you've done that, it's really important for you to do this. You have to reset the managed object context. Object graphs can be complicated. And if you remove a store, you've essentially removed the underlying backing for a part of your context.
You need to reset the context so that all the objects that were in that store will get flushed out. Don't forget, if you see confusing behavior, you might have forgotten. Now we're going to bring Chris back up, and he's going to show you a demo of how you can use the Core Data frameworks to stripe objects across multiple stores.
Thanks, Melissa. I'd just like to point out one other thing with the document-based application that I showed. In here-- I forgot to mention that we don't implement any storage methods. But if you have a legacy application that already has a file format, you can override the main entry points for the document loading and saving and still handle your own file format. So you can just get objects into memory from your file format and insert them into your document's context and then use Core Data to save them out again.
But here, I've built a little application for looking at the WWDC schedule. I just got an XML version of the schedule for a couple of days. And I actually built just a standard Cocoa application. I didn't build a Core Data application. So I just created a basic application delegate, just like you would see in any Cocoa example.
And in its init method, for when it's instantiated in the Nib file, I have the initialization of its managed object model, its persistent store coordinator, and a managed object context to use for the application. So here, I'm just getting a model from our bundle. are getting the Path to a Model from our bundle. I'm actually instantiating that model. I'm creating a persistent store coordinator that refers to that model. And finally, I'm creating a managed object context that is attached to that persistent store coordinator. And let's start this building and running.
Now here I just have a basic table view all done with bindings, and I have a button. This button has one action method, and that action method is AddStore. And what this does is it brings up an open panel and grabs a store from a file and adds it. So I can go in here. I can pick Monday's schedule. And I see all of the sessions that were on Monday and their day and time and the room they were in.
But notice that this is fairly generic. I just get the first URL that I've selected in my OpenPanel, and add it. This means-- oh, and then I tell my array controller that's connected to the table view to fetch. And the new fetch method on array controller will actually go out to its managed object context and run a fetch. So if I click Add Store again, it goes through the same steps.
And I can pick Tuesday's schedule, and Tuesday's schedule shows up in my table view. But if I sort this by day and time, I see that Tuesday's sessions were added to Monday's sessions. This is actually a managed object, one managed object context connected to one persistent store coordinator that's now connected to two stores.
Both of these are XML stores, but they don't have to be. One could be an XML store and one could be an SQL store. And that's it. This is a very simple code, barely any in fact, and it lets you work with multiple stores, all from one context. So I guess I'm going back to Melissa.
There's some random other stuff. Didn't really fit anywhere else in the presentation, so this is the other stuff section. We've mentioned, Chris mentioned during the demo, and it's sort of been alluded to a few other times, we have a NS persistent document. It's a subclass of NS document that integrates with the Core Database persistence and object management stuff automatically.
You don't need to think about it, just create it, we do everything else for you. It's got full undo redo support, document dirtying, and it does provide hooks for configuring the stack if you want to do something special. If you want to create a document-based application using models for multiple frameworks, we provide hooks for that. that let you do that.
We've got, as we said, Cocoa Bindings integration. The big takeaway message here is it just works. Managed object can be bound like any other object that supports key value coding. You get undo, reduce support through the context. And as you saw in Chris's demo, the controllers are now model aware, so you can drive it all using the models.
And the array controller actually has a field that will allow you to configure an NSFetchRequest to fetch context from the object context. So if you want an array controller that only contains a subset of the objects in your context, you can build an NS predicate that fetches only those objects.
Well, that's actually about all we've got to talk about. You're going to get, in the WWDC CD, you've all got a seed. It's already got a fair amount of documentation for which we thank our doc team. Some pieces aren't quite fully functional. The SQLite support only supports basic fetching and updating. The live controllers aren't as live as we'd like them to be, and the fetch properties don't fully work.
There isn't any localization, and there's no optimization, but please try it out, and if you find something, submit bug reports. We like bug reports. They tell us what parts of the stack people are using, what parts they don't really care about, what problems they're having with things. So, things you should remember. Core Data is model-driven.
It gives you object graph management, object graph persistency, Cocoa bindings, and some additions to Cocoa bindings. It helps you solve difficult problems. Things like undo, redo, and scalability. Both of these are actually relatively hard, as many of you know who've had to solve these problems before. We do it for you. We support a variety of persistent stores. You pick which particular storage mechanism you want.
We have support in Xcode and Interface Builder at the user interface level for allowing you to build applications using Core Data. And you'll write a lot less code, as Chris showed you in his demo, a lot, lot less code. You don't even need classes. You can just use NSManagedObject. Before we go to the question and answer, I want to point out this slide. Big slide. Report bugs. Griping on the list, talking on the list, that's all well and good, but you know, Apple engineering is bug driven. Submit bugs if you want us to fix things.