Friday, July 19, 2013

Core Data Programming Guide Notes -- Part 5

This is the notes of Core Data Programing Guide from reading Apple's document




Concurrency with Core Data

You can use threads, serial operation queues, or dispatch queues for concurrency. For the sake of conciseness, this article uses “thread” throughout to refer to any of these.

Use Thread Confinement to Support Concurrency 
The pattern recommended for concurrent programming with Core Data is thread confinement : each thread must have its own entirely private managed object context. 

There are two possible ways to adopt the pattern:
  1. Create a separate managed object context for each thread and share a single persistent store coordinator.
    This is the typically-recommended approach. 
      2.Create a separate managed object context and persistent store coordinator for each thread.
         This approach provides for greater concurrency at the expense of greater complexity (particularly            if you need to communicate changes between different contexts) and increased memory usage. 

You must create the managed context on the thread on which it will be used. If you use NSOperation, note that its init method is invoked on the same thread as the caller. You must not, therefore, create a managed object context for the queue in the queue’s init method, otherwise it is associated with the caller’s thread. Instead, you should create the context in main (for a serial queue) or start (for a concurrent queue).

Using thread confinement, you should not pass managed objects or managed object contexts between threads. To “pass” a managed object from one context another across thread boundaries, you either:

  •  Pass its object ID (objectID) and use objectWithID: or existingObjectWithID:error: on the receiving managed object context. 

          The corresponding managed objects must have been saved—you cannot pass the ID of a newly-             inserted managed object to another context. 

  •  Execute a fetch on the receiving context.


Track Changes in Other Threads Using Notifications


Typically, on thread A you register for the managed object context save notification, NSManagedObjectContextDidSaveNotification. When you receive the notification, its user info dictionary contains arrays with the managed objects that were inserted, deleted, and updated on thread B. Because the managed objects are associated with a different thread, however, you should not access them directly. Instead, you pass the notification as an argument to mergeChangesFromContextDidSaveNotification: (which you send to the context on thread A). Using this method, the context is able to safely merge the changes. 


Fetch in the Background for UI Responsiveness 

Following the thread confinement pattern, you use two managed object contexts associated with a single persistent store coordinator. You fetch in one managed object context on a background thread, and pass the object IDs of the fetched objects to another thread. In the second thread (typically the application's main thread, so that you can then display the results), you use the second context to fault in objects with those object IDs (you use objectWithID: to instantiate the object). (This technique is only useful if you are using an SQLite store, since data from binary and XML stores is read into memory immediately on open.)


Saving in a Background Thread is Error-prone

If you perform a save operation in a background thread, therefore, it may be killed before it is able to complete. If you need to save on a background thread, you must write additional code such that the main thread prevents the application from quitting until all the save operation is complete. 

If You Don’t Use Thread Containment
If you choose not to use the thread containment pattern—that is, if you try to pass managed objects or contexts between threads, and so on—you must be extremely careful about locking, and as a consequence you are likely to negate any benefit you may otherwise derive from multi-threading. You also need to consider that:

  • Any time you manipulate or access managed objects, you use the associated managed object context. Core Data does not present a situation where reads are “safe” but changes are “dangerous”—every operation is “dangerous” because every operation has cache coherency effects and can trigger faulting. 
  • Managed objects themselves are not thread safe. If you want to work with a managed object across different threads, you must lock its context (seeNSLocking). 

Typically you lock the context or coordinator using tryLock or lock. If you do this, the framework will ensure that what it does behind the scenes is also thread-safe. For example, if you create one context per thread, but all pointing to the same persistent store coordinator, Core Data takes care of accessing the coordinator in a thread-safe way (the lock and unlock methods of NSManagedObjectContext handle recursion).
If you lock (or successfully tryLock) a context, you must keep a strong reference to that context until you invoke unlock. If you don’t, in a multi-threaded environment, you may cause a deadlock. 



Core Data Performance

Fetching Managed Objects 
 Fetch Predicates
 How you use predicates can significantly affect the performance of your application.
 Especially if the predicate involves text matching (contains, endsWith, like, and matches) since correct Unicode searching is slow. If the predicate combines textual and non-textual comparisons, then it is likely to be more efficient to specify the non-textual predicates first, for example (salary > 5000000) AND (lastName LIKE 'Quincey') is better than (lastName LIKE 'Quincey') AND (salary > 5000000).
 


Fetch Limits

You can set a limit to the number of objects a fetch will return using the method setFetchLimit: as shown in the following example.

NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setFetchLimit:100];

Faulting Behavior
You can safely invoke the following methods on a fault without causing it to fire: isEqual:, hash, superclass, class, self, zone, isProxy, isKindOfClass:, isMemberOfClass:, conformsToProtocol:, respondsToSelector:, description, managedObjectContext, entity, objectID, isInserted, isUpdated, isDeleted, and isFault. 

Batch Faulting and Pre-fetching with the SQLite Store

When you execute a fetch, Core Data fetches just instances of the entity you specify. 


Analyzing Performance 

Analyzing Fetch Behavior with SQLite
With OS X version 10.4.3 and later, you can use the user default com.apple.CoreData.SQLDebug to log to stderr the actual SQL sent to SQLite. (Note that user default names are case sensitive.) For example, you can pass the following as an argument to the application:

-com.apple.CoreData.SQLDebug 1

 






Troubleshooting Core Data

Object Life-Cycle Problems 

Merge errors 

Problem: You see the error message, "Could not merge changes".
Cause: Two different managed object contexts tried to change the same data. This is also known as an optimistic
locking failure.
Remedy: Either set a merge policy on the context, or manually (programmatically) resolve the failure. You can retrieve the currently committed values for an object using committedValuesForKeys:, and you can re-fault the object (so that when it is next accessed its data values are retrieved from its persistent store) using refreshObject:mergeChanges:.


Assigning a managed object to a different store 
Problem: You see an exception that looks similar to this example. 
  [_assignObject:toPersistentStore:]:
Can’t reassign an object to a different store once it has been saved.
Cause: The object you are trying to assign to a store has already been assigned and saved to a different store. Remedy: To move an object from one store to another, you must create a new instance, copy the information
from the old object, save it to the appropriate store, and then delete the old instance.
Fault cannot be fulfilled
Problem: You see the error message, "Core Data could not fulfill a fault". Cause: The corresponding object's underlying data has been deleted from the persistent store. Remedy: You should discard this object. This problem can occur in at least two situations:
First:
Start with a strong reference to a managed object.
Delete the managed object via the managed object context.
Save changes on the object context.
At this point, the deleted object has been turned into a fault. It isn’t destroyed because doing so would violate the rules of memory management.
Try to retrieve an attribute or relationship from the previously retained reference.
Core Data will try to fault the faulted managed object but will fail to do so because the object has been deleted from the store. That is, there is no longer an object with the same global ID in the store.
Second:
Delete an object from a managed object context. Fail to break all relationships from other objects to that object. Save changes.
Managed object invalidated
Problem: You see an exception that looks similar to this example:
[_assignObject:toPersistentStore:]: The NSManagedObject with ID:#### has been invalidated.
Cause: Either you have removed the store for the fault you are attempting to fire, or the managed object's context has been sent a reset message.
Remedy: You should discard this object. If you add the store again, you can try to fetch the object again.
Class is not key-value coding compliant
Problem: You see an exception that looks similar to the following example.
 [ valueForUndefinedKey:]:
this class is not key value coding-compliant for the key randomKey.
Cause: Either you used an incorrect key, or you initialized your managed object with init instead of initWithEntity:inManagedObjectContext:.
Remedy: Use a valid key (check the spelling and case carefully—also review the rules for key-value coding compliance in Key-Value Coding Programming Guide ), or ensure that you use the designated initializer for NSManagedObject (see initWithEntity:insertIntoManagedObjectContext:).
Efficiently Importing Data

Cocoa Fundamentals

No comments:

Post a Comment