5. I love...
‣ ... my wife
‣ ... my 4 kids
‣ ... to code
‣ ... to play a game of squash
‣ ... good beer
6. I open sourced...
... some code:
‣ IIViewDeckController: “An
implementation of the
sliding functionality found in
the Path 2.0 or Facebook
iOS apps.”
‣ IIDateExtensions
‣ IIPopoverStatusItem
See: http://github.com/inferis
7. I made...
... some apps:
Butane Drash
http://getbutane.com http://dra.sh
Hi, @10to1!
8. Butane
Campfire client for iOS
‣ Official Campfire client
kinda sucks, so we
rolled our own
‣ Somewhat concurrent
app
‣ Uses Core Data
‣ Together with 10to1
13. What is: Core Data?
‣ Per the documentation:
‣ The Core Data framework provides
generalized and automated solutions
to common tasks associated with
object life-‐cycle and object graph
management, including persistence.
http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdTechnologyOverview.html#//apple_ref/doc/uid/TP40009296-SW1
15. What isn’t: Core Data?
‣ It’s not an RDBMS.
‣ If you want a database and SQL, use
Sqlite:
16. What isn’t: Core Data?
‣ It’s not just an ORM (Object Relational
Mapper)
‣ It may look like there’s SQL under the
hood, but that’s not necessarily the case.
17. So, what is it then?
Core Data provides:
‣ persistence
‣ change tracking
‣ relations (object graph)
‣ lazy loading (“faulting”)
‣ validation
‣ works well with Cocoa (KVO, KVC)
18. Basically:
‣ A system to store data
‣ Persistence agnostic (local storage,
iCloud, AFIncrementalStore, ...)
‣ No need to write SQL to query
‣ You can keep to Objective-C
22. MagicalRecord
‣ Writing Core Data code is tedious.
‣ You need quite a bit of boilerplate code
to do something simple:
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:moc];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:entityDescription];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:YES];
[request setSortDescriptors:@[sortDescriptor]];
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array)
{
// display items (eg in table view)
}
else {
// Deal with error...
}
23. MagicalRecord
‣ MagicalRecord tries to solve this.
‣ = ActiveRecord pattern for Core Data.
‣ Encapsulates the tediousness of plain
Core Data code.
24. MagicalRecord
‣ Writing MagicalRecord enable code is
tedious no more:
‣ That same example is now this:
NSManagedObjectContext *moc = [self managedObjectContext];
NSArray *array = [Employee MR_findAllSortedBy:@"firstname" ascending:YES inContext:context];
if (array)
{
// display items (eg in table view)
}
else {
// Deal with error...
}
27. MagicalRecord
+ write less code
+ your code becomes more readable
+ good for apps requiring simple storage scenarios (=most
apps, probably)
+ hides complexity
- hides complexity
- easy to start, but diving deeper becomes harder
- uses defaults that are suboptimal in a multithreaded app
- concurrency errors and issues are subtle
28. MagicalRecord
‣ used MagicalRecord from the start
‣ Took me a while to find out the problems
I was having were related to the
complexity hiding
‣ The defaults are okay only for so much
app complexity
29. MagicalRecord
‣ That said: Magical Record is great.
‣ It will speed up your Core Data
development by several factors.
‣ Also: take a look at mogenerator:
‣ http://rentzsch.github.com/mogenerator/
‣ or: brew install mogenerator
37. Problems?
‣ Core Data Objects are not thread safe.
‣ In essence: you can’t share them across
threads (except for NSManagedObjectID).
38. Problems?
‣ Core Data Objects are not thread safe.
‣ In essence: you can’t share them across
threads (except for NSManagedObjectID).
‣ Core Data locks objects, even for read
operations.
39. Object storage is locked
for read operations, too
‣ Objects used to power the UI must be
fetched on the UI thread.
‣ Heavy/complex fetch requests (queries)
block the UI thread while fetching the
objects. You don’t want that.
40. Objects aren’t supposed to
be shared between threads
‣ The NSManagedObjectContext “locks” an
object when you read one of its properties.
‣ This can cause a deadlock when you do
access the same data from 2 threads.
‣ Reason: faulting support can change the
object even while just reading from it.
‣ You can’t turn it off.
45. Keep to your thread
‣ pre-iOS5: use thread confinement
46. Keep to your thread
‣ pre-iOS5: use thread confinement
‣ iOS5 and later: use nested contexts
47. Thread confinement
‣ In essence: keep an NSManagedObjectContext per
thread
‣ Be very careful when going from one thread to
another.
‣ MagicalRecord tries to hide this from you:
‣ It automatically provides a context for each thread
‣ This is a bit counterintuitive since you start mixing
objects across threads quite easily.
49. Nested contexts
‣ Introduced in iOS5
‣ Uses Grand Central Dispatch and dispatch queues
‣ Core Data manages threading for you
‣ Better than thread confinement
‣ more straightforward
‣ more flexible
‣ MagicalRecord hides this from you, too.
‣ Automatically switches to dispatch queues on iOS5 even
though the API remains the same.
51. Nested contexts
‣ NSManagedObjectContext = cheap
‣ You can nest contexts
‣ Each context has its private dispatch
queue
‣ No manual thread synchronization
necessary
52. Queue types
‣ NSConfinementConcurrencyType
‣ The old way (thread confinement)
‣ NSPrivateQueueConcurrencyType
‣ The context has its own private dispatch queue
‣ NSMainQueueConcurrencyType
‣ The context is associated with the main queue (or runloop, or UI
thread)
parentMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[parentMoc setPersistentStoreCoordinator:coordinator];
moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = parentMoc;
53. Thread safe?
‣ performBlock:
performBlockAndWait:
‣ Run a block you provide on the queue
associated with the context.
‣ Object access in the block is thread safe
[context performBlockAndWait:^{
for (Room* room in [Room findAllInContext:context]) {
room.joinedUsers = [NSSet set];
room.knowsAboutJoinedUsersValue = NO;
room.unreadCountValue = 0;
room.status = @"";
}
NSError* error = nil;
[context save:&error];
}];
54. performBlock, and wait
-‐ (void)performBlock:(void (^)())block;
‣ executes the block on the context dispatch queue as soon as possible
‣ nonblocking call
‣ code will not execute immediately
-‐ (void)performBlockAndWait:(void (^)())block;
‣ executes the block on the context dispatch queue immediately
‣ blocks the current execution until block is done
‣ can cause a deadlock (if you’re already running code on the same
queue)
55. When to use what?
‣ performBlock:
‣ For actions which are “on their own”
‣ Consider the code in the block a Unit Of Work
‣ Best for save operations
‣ Useful for long fetches (use callbacks)
‣ performBlockAndWait:
‣ When you need stuff immediately
‣ Good for small fetches or “standalone” saves
56. How is this better than
thread confinement?
‣ No manual thread handling, Core Data
handles it for you.
‣ More flexible: as long as you access
managed objects in the correct context
using performBlock: you’re pretty safe
‣ also applies to the main/UI thread! (unless
you’re sure you’re on the main thread)
57. Saving nested contexts
‣ Saves are only persisted one level deep.
‣ Parent contexts don’t pull changes from
child contexts.
‣ Child contexts don’t see changes by
parent contexts.
‣ Make them plenty and short-lived
58. How to nest contexts
‣ 2 approaches:
‣ root context = NSMainQueueConcurrencyType
‣ root context = NSPrivateQueueConcurrencyType
59. Root = Main
‣ Many child contents
with private queues
‣ Root context on main
queue
‣ Actual persistence
happens on main
queue, could block the
UI
Image source: Cocoanetics
60. Root = Private
‣ Root context with private
queue
‣ Many child contents with
private queues
‣ context on main queue is
child of root
‣ Actual persistence
happens in background
(does not block the UI)
Image source: Cocoanetics
62. What problems did we
have again?
‣ No sharing of NSManagedObjects
between threads.
63. What problems did we
have again?
‣ No sharing of NSManagedObjects
between threads.
‣ Context locking
64. Sharing: solution
‣ Pass only NSManagedObjectIDs to
other threads, not objects.
‣ Refetch the object on a different thread
or queue to work with it.
‣ Don’t forget to save the ‘original’ object
first before working with it on the second
thread or queue.
65. Complex queries
‣ Use the same technique for complex or large queries.
1. do the heavy lifting in the background
2. pass list of NSManagedObjectIDs to another thread
(e.g. UI thread).
3. load objects as faults, and let Core Data fault them in
when you need them (e.g. when accessing a property)
‣ That’s lot of requests, but this is actually more performant
and desirable in most cases.
66. Locking: solution
‣ Use child contexts with their own
dispatch queues.
‣ Use: performBlock: and
performBlockAndWait: carefully.
‣ Deadlocks still possible, especially with
performBlockAndWait:
67. A word about
NSManagedObjectIDs
‣ Two types of IDs:
‣ temporary
‣ when the object hasn’t been persisted to a
store
‣ permanent
‣ when the object has been persisted to a
store
68. A word about
NSManagedObjectIDs
‣ Subtle bug: temporary IDs from a non-root
context don’t get updated to permanent
IDs when saving in the root context
‣ The object is saved just fine, but the ID is
not updated correctly.
‣ When passing these around to other
contexts after saving: you won’t find the
object in another child context!
69. A word about
NSManagedObjectIDs
‣ To the rescue:
-‐ (BOOL)obtainPermanentIDsForObjects:
(NSArray *)objects error:(NSError
**)error;
70. A word about
NSManagedObjectIDs
-‐ (BOOL)obtainPermanentIDsForObjects:(NSArray
*)objects error:(NSError **)error;
‣ Transforms objects with temporary IDs to permanent IDs
(through the persistent store of the root context).
‣ Do this when creating a managed object and you’re
safe.
‣ Obtaining permanentIDs is batched, so the performance
hit is not that high
71. MagicalRecord
‣ I still use MagicalRecord:
‣ Reduced form: no more “automatic”
context handling --> dangerous!
‣ Added some extra sauce to work with
the nested contexts.
‣ The methods MR supplies still allow for
a speedup when coding.
72. Useful References
‣ Nested context release notes: http://developer.apple.com/library/mac/#releasenotes/
DataManagement/RN-CoreData/_index.html
‣ Magical Record: https://github.com/magicalpanda/MagicalRecord
‣ Mogenerator: http://rentzsch.github.com/mogenerator/
‣ A good read on Cocoanetics: http://www.cocoanetics.com/2012/07/multi-context-coredata/
‣ Core data programming guide: http://developer.apple.com/library/mac/#documentation/
cocoa/Conceptual/CoreData/cdProgrammingGuide.html
‣ Butane: http://getbutane.com