Masterclass: Core Data tidbits

I have been using Core Data for a while now, several years at least. Over time, I have learned how it works internally and learned to side-step possible issues and shortcomings. The following is a unordered list of tidbits.

The Stack

Don’t ever check the Core Data flag when creating the project. Add your model afterwards and…

Use RTCoreDataStack. I’m eating my own dog food and am using it (or its lesser variants) for years now, rarely an issue, if the following rules are obeyed.

This particular stack is amazing for background imports that also update/change stuff that may influence main thread/UI.

NSFRC cacheName argument

Ignore it.

NSFRC always maintains cache, even if you don’t set the cacheName. Setting cacheName means that it will persist its cache between app starts. Not setting it means that cache will be memory-only for the duration of current app session.

You can change both sortDescriptors and predicate for the NSFetchRequest, if you kill the cache first. If you haven’t set cacheName, there’s nothing to kill.

So, don’t set the cacheName for the NSFRC. Ever. Win-win.

Relationships changes and NSFRC

NSFRC will not react when you change relationships to the object it’s based on. This will not affect you if your views are very simple (use only that one particular object) or your object graph is totally denormalized. Mine are usually neither.

Say you have canonical example of Person object that you are showing and it has Department relationship. In each person’s cell you want show its department’s name. If that name changes, NSFRC will not pick that up on its own - it only looks into the attributes for the Person.

Thus you need to trick it; this is how to kick NSFRC’s reaction:

// get reference to Department object
XXDepartment *mo = 

//	NSFRC hack
[mo.person willChangeValueForKey:@department];

//	make the changes = @xxxx;

//	NSFRC hack end
[mo.person didChangeValueForKey:@department];

It’s a hack, but it works in practice.

Wounds from practice

If you have a transformable property which is actually an NSArray, this will work:

NSNumber *n = …
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%@ IN %K", n, arrayAsTransformableProperty]];

But, if you supply a value of n which does not exists in the arrayAsTransformableProperty in any of the objects, then you get a very ugly EXC_BAD_ACCESS (code=1,address=0x0) crash:

Thread 1Queue : NSPersistentStoreCoordinator 0x7fbf284c6db0 (serial)
#0	0x0000000112d138ff in CFStringGetLength ()
#1	0x00000001129df98f in _NSCoreDataStringSearch ()
#2	0x00000001144d4357 in sqlite3VdbeExec ()
#3	0x00000001144a44a2 in sqlite3_step ()
#4	0x000000011298e5a0 in _execute ()
#5	0x000000011298e2bb in -[NSSQLiteConnection execute] ()
#6	0x00000001129a8104 in newFetchedRowsForFetchPlan_MT ()
#7	0x000000011299532c in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#8	0x0000000112994cc9 in -[NSSQLCore executeRequest:withContext:error:] ()
#9	0x0000000112a7a51f in __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke ()
#10	0x0000000112a83b4d in gutsOfBlockToNSPersistentStoreCoordinatorPerform ()
#11	0x00000001138f549b in _dispatch_client_callout ()
#12	0x00000001138d9fb5 in _dispatch_barrier_sync_f_invoke ()
#13	0x0000000112a74c45 in _perform ()
#14	0x0000000112994934 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#15	0x00000001129930f3 in -[NSManagedObjectContext executeFetchRequest:error:] ()

So yeah – don’t save NSArrays into the Core Data. Convert it to CSV string and save that.

Don’t use direct relationships in sortDescriptors. It can crash during [NSMO compare:] when your didSaveNotification is handled and causes NSFRC delegate call to do tableView.reloadData.

I have done this and in some obscure confluence of edge cases got this crash:

[NSMOSubclass compare:] unrecognized selector sent to instance …

For NSFRC sortDescriptors, use number/string attributes, never relationships.

When defining your model, do not insert default values, unless you really, really need them. Instead, make the attribute optional, if possible.

The reason is that if you have only some of the values, it becomes much harder to use some advanced data extraction features, like calculating averages (where missing data should be ignored).