Search posterous

Search all posts and users. Type a name, type a favorite song title, whatever! See what comes up.
  

More posterous blogs











More recommended blogs »

Here are posterous posts filed under coredata...

Archimage says...

So in the last two posts I dealt with setting up CoreData within an existing project, binding the data (NSManagedObjects to a control) and actually getting the objects to save to a file (store).  In this last entry, I'll deal with pulling data from the file in order to display it in the control.   This code assumes you have an NSManagedContext I'm calling newContext.

NSError **theError = nil;  // set up an error code

// We need to use an NSEntityDescription that the docs describe as being an abstract form of NSManagedObject. (It's like "id" is to classes.)  
// When we create an NSEntityDescription we need to map it (think cast it) to the entity in our data model we want to read within our context.
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Recent" inManagedObjectContext:newContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];  // Create a CoreData fetch request (NSFetchRequest)
[request setEntity:entityDescription];  // tell the fetch request what type of entity we want to fetch (using our NSEntityDescription)

 

 

 


// Here we need to specify our NSPredicate (think SQL SELECT predicate) to specify the subset of objects to read from our store.
// The bad news is that CoreData predicates don't use SQL syntax, which is a shame.  They use their own special syntax which makes learning a bit more problematic
// and difficult.
// I am keeping things simple here and am going to assume if I don't specify a predicate CoreData will do the sensible thing and return all objects.
// (I am just guessing here, since I didn't feel like wading thru the NSPredicate Programming Manual.)
// NSPredicate *predicate = [NSPredicate predicateWithFormat:...];
// [request setPredicate:predicate];

 

 

 


// Next, we create an NSSortDescriptor which is used to sort the object during the fetch (think an SQL ORDER BY clause.)

// Specify the property name within the entity we want to sort by (I'm using gitName) and the sort order.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor allocinitWithKey:@"gitName" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];  // tell the NSFetchRequest to use the sort descriptor
[sortDescriptor release]; // clean up

 

 

 


NSArray *array = [newContext executeFetchRequest:request error:theError]; // create an array with the results of executing the fetch request on our context.
if (array != nil)
{
// move the results to our array of objects which are bound to our control.
}
else
{
// Deal with error...
}
[array release];

That's it--it works!

So, what have I learned?  Once things are set up CoreData isn't bad or overly difficult to use in this simplest of approaches. The code I've presented is pretty brute-force, but it works.  The complexity as I see it comes in learning how to manage the various layers of the graph:  the persistent store, the context, the managed objects themselves, and probably the hardest layer, the predicates.  There are some analogs to what and how SQL works, but its not as simple as SQL.  SQL is declarative and purely syntax driven.  CoreData is obviously object-oriented, but its more complex than an ORM (Object-Relational Mapping) tool such as hibernate.  

Am I going to keep using CoreData?  Yes, mainly because I got the basics working, it gives me a level of confidence, and I know I can pick up the more complex options as I need them bit by bit, and I personally have no need to learn all of CoreData at once.  

Is CoreData more cumbersome and complex than it has to be? Probably.  

Filed under: CoreData

Archimage says...

Continuing on my quick CoreData excursion, here is what I figured out and learned today  regarding actually getting your NSManagedObjects to save to a real file.   This assumes the previous post's code.

Saving your objects to the file/store is pretty straight forward.  Again, I'm just trying to get basic functionality working.   Once I have that done I can expand on my knowledge and what I try to code after I understand what is going on. 

Saving is pretty straight forward once you have the setup code done.  In the previous entry's code example, there is a comment you need to remove and implement:

//- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:nil URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error     // this is what you use to set up the database type, etc.  In this snippet I don't worry about it because I just want it to compile and display something.

This is my brute force code:
NSError *error;  

// Build an NSURL to our physical database file.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *folder = @"~/Library/Application Support/Tiny±Git/";
folder = [folder stringByExpandingTildeInPath];
if ([fileManager fileExistsAtPath: folder] == NO)
{
[fileManager createDirectoryAtPath:folder withIntermediateDirectories:NO attributes:nil error:error];
}

    

NSURL *fileURL  = [NSURL fileURLWithPath: [folder stringByAppendingPathComponent: @"TinyGit.sqlite"]];

// Now tell our NSPersistanceStoreCoordinator the type of DB you want (I'm using SQLite, you can also use XML, etc.) 
// Pass in our fileURL NSURL which is where the database will be strored. 
// There are also configurations, and options, but we can ignore this--I'll worry about those once I get this working.
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:fileURL options:nil error:error];

Once you have the store coordinator set up this way, then all you have to do to store your NSManagedObject(s) is call:

[newContext save:error];

after you have created them and added them to the NSManagedObjectContext.  The interesting thing here is that the the save: actually forces all the objects within the context to be saved all at once.  It's all or nothing.  So make sure you only add the objects to the context that you want to end up in the database.

Just to verify that this actually did work, I went to my Application Support folder and sure enough there was a new Tiny±Git folder and within it a new file TinyGit.sqlite.  Just to see what got stored (if anything) I opened it up with SQLiteManager:

Compare this with the actual data model:

So, it looks like CoreData builds lots of infrastructure.  Just to see what data got stored I drilled into the data:

Well at least the data that I specified for my simple example in the previous post got saved properly, but there sure is a lot of extraneous stuff going on under the covers.

What do I conclude from all this?  I honestly am not sure. I'm feeling a bit more confident after seeing how relatively easy it is (although limiting) to save a context.  I'll have to see what it's like to actually retrieve/fetch data using CoreData.

Filed under: CoreData

Archimage says...

I've been trying to wrap my mind around CoreData.  It's a major mind-shift for me since I come from an SQL-heavy mainframe environment and SQLite.   This is what to keep in mind when learning CoreData:

  • It's not a relational-file system per se.
  • It's not based on understanding rows, columns, tables.
  • It has no declarative language (SQL).
  • It is a relationship between objects that manage various layers of the environment.
  • You need to create and manage these objects properly to create a working "object graph".
  • The XCode data model document that ends in .xcdatamodel is NOT the data model you load.  It gets compiled into an intermediate form which you then have to find and load.
  • You can't use the entities in the data model directly to create object instances.  This is not obvious from the docs, but you have to create a subclass of the entity in the model and THAT is what you use.  There is no way around this (which is a pain).  I'd like to just use the entity as is with no extra step UNLESS I want to subclass it.
  • The documentation from Apple is fragmented and assumes a lot without explaining a lot.
  • The sample code is minimal.
  • The online internet samples/tutorials are again minimal and mostly assume you want to know how to bind things rather than implement things.

Here is what I've come up with that works code wise to set up a working object graph (comments added here to help explain).  This is part of the init method.  This is a snippet and has no error handling, etc.  It works as is.

// find the location of the COMPILED xcdatamodel document named "Resource"
NSString *path = [[NSBundle mainBundle] pathForResource:@"Resource" ofType:@"momd"];
if (!path) {
path = [[NSBundle mainBundle] pathForResource:@"Resource" ofType:@"mom"];
}
NSAssert(path != nil, @"Unable to find Resource in main bundle");

// Create a URL to the path
NSURL *url = [NSURL fileURLWithPath:path];

// Create the thing that manages the model and its relationship with the graph, point it to the compiled model document.
NSManagedObjectModel * managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];

// Create the thing that manages the file/db-type to use to store the data objects. Give it our object model so it knows about the entities, etc.

NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];

//- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:nil URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error     // this is what you use to set up the database type, etc.  In this snippet I don't worry about it because I just want it to compile and display something.

// set up a context in which the objects live--think of this as a business context. You have to have one.
newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:psc];  // This context will use our store coordinator (database mgmt object)

// Create an instance of an object based on an entity in our xcdatamodel document.  Here it is called Recent.  Insert it into our context to be managed.
// Before this will work you need to subclass NSManagedObject -- Recent :  NSManagedObject otherwise you will get an entity not found error.
NSManagedObject * o = [NSEntityDescription  insertNewObjectForEntityForName:@"Recent" inManagedObjectContext:newContext];

// set up some attributes within the entity object
[o setValue:@"/path/here" forKey:@"path"]; 
[o setValue:@"Name" forKey:@"gitName"];

// Add it to an array to be bound to a control in IB.
[a addObject:o];

The actual IB bindings are straight forward if you realize you have to bind the ManagedObjectContext you created (newContext) toward the bottom of the bindings panel in IB in addition to the normal bindings.  

And this just sets up the environment and displays a single line in my table view.  I haven't actually stored anything or fetched anything off the disk.

I hope that helps someone get kick-started in CD and saves a lot of hunting and pecking to get the right sequence in the setup.

Filed under: CoreData