Showing posts with label coredata. Show all posts
Showing posts with label coredata. Show all posts

Saturday, June 29, 2013

Unique objects with Core Data: Find and Create

I don't think it's a secret that I love Core Data. It's my opinion that the people who don't like it or use it are either very stupid or extremely smart.

There is one problem that has plagued me for years, how to ensure that when i'm perform imports I don't create duplicate objects. In reality the problem is fairly simple conceptually, you create a hash table, and then hash the Incoming objects to test for their membership in the set, Easy.

But if you believe in DRY, the problem is then making this unique constraint checking code modular and portable across projects, and different Core Data Models.

The first issue we need to is identify what property of a NSManagedObject should be unique relative to the rest of the greater set. We can achieve this by adding a class method to the NSManagedObject class

+ (NSString *)uniqueKeypath {
  return nil;
}


However, in an more advanced model there might be multiple properties that need to be evaluated to determine if an object is unique so …

+ (NSArray *)uniqueKeypaths {
  return nil;
}


It's important to note that since we are using Keypaths, we can actually evaluate the values of related entities.

Finding a Needle in a Haystack

Now it's time to make the magic happen. We apply the concept, we create a hash table, then lookup the unique value of the new objects in the table. If we get a 'hit', we don't do anything, if we 'miss' we create a new object. The sudo code is effectively.

FOR EACH object IN newObjects:

  IF NOT object IN allObjects:

    createObject(object)


The above is simplistic, but you get the point. However you should have made the observation, that if we do it exactly like this, it will not only take ages but we'll run out of memory long before we've began doing the comparison object if we have an extremely large set.

So, the solution is GCD and batched fetching. Given that we can assume that only one import is happening at any one time. So once we have performed the initial fetch of all the existing objects, we can split the comparison operations for each of the new objects on to a concurrent GCD queue. We also need a place to store the objects that need to be created, rather than copying them into another collection, we can keep the current collection and gather the indexes of all the new objects, and then create a subset of the superset.

//Compare new hashes against all the known hashes on multiple threads
dispatch_apply([newObjects count], concurrentQueue, ^(size_t idx) {

   //Note that everything that happens here is on a concurrent queue
   if (![hashes member:[[newObjects objectAtIndex:idx] valueForKeyPath:aKeypath]]) {

   //We have synchronize access to the mutable indexset

    [lock lock]; //Lock the index set

    [uniqueIndexes addIndex:idx]; //add the unique index

    [lock unlock]; //Unlock the index set

  }

});


In this case I'm using dispatch_apply (which I personally think is awesome). It will spawn multiple instances of the block on a concurrent queue. Because of the concurrent nature of this method it's important that we lock the NSMutableIndexSet to ensure that it doesn't blow up when two indexes are added at the same time. The current implementation with a simple NSLock results in poor performance on the initial import as every single block will attempt to get the lock so they can add an index. One possible solution is to use a serial dispatch queue to handle the adding of the indexes, and call it via a dispatch_async.

The next part is to split the work of fetching the objects in to smaller batches so we can not only perform smaller units of work, but also have a lower high memory watermark. NSFetchRequest has support for batching requests so this is apparently handled transparently to the rest of the code using a special type of NSArray. However I haven't tested this to ensure it behaves as I expect.

I've posted an implementation as a abstract subclass of NSManagedObject on Github, fork away!


Sunday, March 17, 2013

FRCoreDataExportOperation

Core Data is possibly one of the most useful frameworks in iOS. People have different feelings towards it, but i'm a solid fan. One issue that i'm sure everyone has had with core data at one point or another is getting data in and out of Core Data. This ultimately led to creation of the FRCoreDataOperation project.

A few months ago I started using FRCoreDataOperation to serialize Core Data objects to disk. Sure you could do this with plists, but thats a little too restrictive especially if you want the serialized data to be compatible with other applications and platforms. Usually my format of choice is CSV, as you can import it into excel/ google docs easily, also python has a great module for loading CSV data.

For another project I found myself needing to do the same thing again, the classic sign that it was time to do make something reusable and adaptable. So FRCoreDataExportOperation was born. FRCoreDataExportOperation is a concrete subclass of FRCoreDataOperation and can be used without subclassing. Below is an example of how you use the class.

There are a series of initializers that you can use in tandem with the class, but the one you'll probably most interested in is below.

As you can see by using a predicate and sort descriptors along with the entity name you can select the objects you want to export. The export operation also supports relationships, it's not completely recursive, and will only explore entities that have a direct relationship with the entity you specified.

The next step is to select a formatter, the actual serialization format is completely abstracted from the class and is performed by the object assigned to FRCoreDataExportOperation's entityFormatter property. Thus far i've only created the FRCoreDataEntityFormatter, FRCSVEntityFormatter, but you can create your own by implementing the FRCoreDataEntityFormatter protocol.

The FRCSVEntityFormatter is somewhat customizable, and allows you to specify a NSArray with the  keypaths that you want to be serialized to disk. The columns will be written in the order they appear in the array. If you choose to use the formatter without a whitelist, all of the properties will be written in the alphabetical order of the property name.

Best of all you can get this for yourself via Cocoapods, pod is aptly called FRCoreDataOperation

Happy exporting

Tuesday, November 13, 2012

When Magical Record stops being magical

Some time ago i discovered Magical Record. It offers some fantastic categories to help you setup your Core Data stack with a single line of code. But it's much more than a Core Data boilerplate thingy. It also offers a range of Active Record pattern like shorthand methods. For example you can find all the instances of a particular type of object without having to write your own NSPredicate and NSFetchRequest using MR_findAll.

Somewhat recently they moved to version 2.0, along with lots of others features they added support for Nested object contexts. If you haven't read about nested/ parent-child managed object context your missing out, especially if you've ever tried to use Core Data with multiple threads.

So when did it stop becoming magical?

Usually i use Magical Record to setup my core data stack, and take advantage of its singleton default context, for those times when i'm too lazy to pass it around like a good engineer. I've also been known to dabble a little with it's Active Record implementation.

However with 2.0, the developer(s) have chosen to change the internal structure slightly. A "Root" context is created at the top of the tree, this has a child context that is returned when you call the MR_defaultContext method. The root context controls the writing of data to disk. This means that if you attempt to save the MR_defaultContext your changes are not persisted to disk, but instead pushed up to the root context.

Well that awesome, right ...

Well it is, if it was obvious. Instead I was left no errors, an empty sqlite db, and no data. Personally I really think they should have just made the MR_defaultContext the root context, and allowed users to nest contexts themselves if they wish. In case your interested you can reach the 'Root' context by calling MR_rootSavingContext.

Anyways after digging around the source code, and a little RTFM I discovered that in order to actually persist your data to disk you have to call MR_saveNestedContexts. This method will recursively call up the tree and save each context. Neat, but not obvious, especially when the API convention is to simply call the native save:

So really this post exists to say, if your a fan of Magical Record and your pulling your hair wondering why save: has stopped working the answer is use MR_saveNestedContexts.



Tuesday, May 24, 2011

Core Data & Multi-Threading, part 1337

So i've made yet another Core Data Discovery, at this rate i might need to write a ebook. Now we all know that the golden rule of using Core Data with different threads is to not share contexts between threads.
So if you need to refer to an object on multiple threads you need to use it's NSManagedObjectID. Thats all well and good, but what if you need to call [NSManagedObjectContext save:] multiple times. Well in short it's gonna suck. Well thats cool you say, i can just use the id anyway ... Not quite. There are two types of ID's, permanent and temporary.
Now in my experience Core Data seems to get confused when you use temporary ID's to look up objects on another NSManagedObjectContext. So really to stop these conflicts we need to get a permanent id, but we need to obtain one of these ID's without saving the NSManagedObjectContext.


Well are you ready for the magic ... Shazam

[NSManagedObjectContext obtainPermanentIDsForObjects: error:]

This awesome method will take an array of managed objects, and give them permanent ID's. From my brief tests the overhead is minimal compared to the traditional save method.

Thats all i've got for today, keep on trucking.

[]'s 4 life