Introduction to Core Data Programming Guide

Concurrency with Core Data

There are several situations in which performing operations with Core Data on a background thread or queue is beneficial, in particular if you want to ensure that your application’s user interface remains responsive while Core Data is undertaking a long-running task. If you do perform concurrent operations with Core Data, however, you need to take considerable care that object graphs do not get into an inconsistent state.

Note: 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.

If you choose to use concurrency with Core Data, you also need to consider the application environment. For the most part, AppKit and UIKit are not thread safe; in particular, on OS X Cocoa bindings and controllers are not thread safe—if you are using these technologies, multi-threading may be complex.

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.

These create a local version of the managed object in the receiving context.

You can use the methods provided by NSFetchRequest to make working with data across threads easier and more efficient. For example, you can configure a fetch request to return just object IDs but also include the row data (and update the row cache)—this can be useful if you're just going to pass those object IDs from a background thread to another thread.

There is typically no need to use locks with managed objects or managed object contexts. However, if you use a single persistent store coordinator shared by multiple contexts and want to perform operations on it (for example, if you want to add a new store), or if you want to aggregate a number of operations in one context together as if a virtual single transaction, you should lock the persistent store coordinator.

Track Changes in Other Threads Using Notifications

Changes you make to a managed object in one context are not propagated to a corresponding managed object in a different context unless you either refetch or re-fault the object. If you need to track in one thread changes made to managed objects in another thread, there are two approaches you can take, both involving notifications. For the purposes of explanation, consider two threads, “A” and “B”, and suppose you want to propagate changes from B to A.

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.

If you need finer-grained control, you can use the managed object context change notification, NSManagedObjectContextObjectsDidChangeNotification—the notification’s user info dictionary again contains arrays with the managed objects that were inserted, deleted, and updated. In this scenario, however, you register for the notification on thread B. When you receive the notification, the managed objects in the user info dictionary are associated with the same thread, so you can access their object IDs. You pass the object IDs to thread A by sending a suitable message to an object on thread A. Upon receipt, on thread A you can refetch the corresponding managed objects.

Note that the change notification is sent in NSManagedObjectContext’s processPendingChanges method. The main thread is tied into the event cycle for the application so that processPendingChanges is invoked automatically after every user event on contexts owned by the main thread. This is not the case for background threads—when the method is invoked depends on both the platform and the release version, so you should not rely on particular timing. If the secondary context is not on the main thread, you should call processPendingChanges yourself at appropriate junctures. (You need to establish your own notion of a work “cycle” for a background thread—for example, after every cluster of actions.)

Fetch in the Background for UI Responsiveness

The executeFetchRequest:error: method intrinsically scales its behavior appropriately for the hardware and work load. If necessary, the Core Data will create additional private threads to optimize fetching performance. You will not improve absolute fetching speed by creating background threads for the purpose. It may still be appropriate, however, to fetch in a background thread or queue to prevent your application’s user interface from blocking. This means that if a fetch is complicated or returns a large amount of data, you can return control to the user and display results as they arrive.

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

Asynchronous queues and threads do not prevent an application from quitting. (Specifically, all NSThread-based threads are “detached”—see the documentation for pthread for complete details—and a process runs only until all not-detached threads have exited.) 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 (see NSLocking).

If you share a managed object context or a persistent store coordinator between threads, you must ensure that any method invocations are made from a thread-safe scope. For locking, you should use the NSLocking methods on managed object context and persistent store coordinator instead of implementing your own mutexes. These methods help provide contextual information to the framework about the application's intent—that is, in addition to providing a mutex, they help scope clusters of operations.

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.\


Technology Overview

This article describes the basic features provided by Core Data, and reasons why it might be appropriate for you to adopt the technology.

Core Data Features

The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence. Its features include:

  • Change tracking and undo support.

    Core Data provides built-in management of undo and redo beyond basic text editing.

  • Relationship maintenance.

    Core Data manages change propagation, including maintaining the consistency of relationships among objects.

  • Futures (faulting).

    Core Data can reduce the memory overhead of your program by lazily loading objects. It also supports partially materialized futures, and copy-on-write data sharing.

  • Automatic validation of property values.

    Core Data’s managed objects extend the standard key-value coding validation methods that ensure that individual values lie within acceptable ranges so that combinations of values make sense.

  • Schema migration.

    Dealing with a change to your application’s schema can be difficult, in terms of both development effort and runtime resources. Core Data’s schema migration tools simplify the task of coping with schema changes, and in some cases allow you to perform extremely efficient in-place schema migration.

  • Optional integration with the application’s controller layer to support user interface synchronization.

    Core Data provides the NSFetchedResultsController object on iOS, and integrates with Cocoa Bindings on OS X.

  • Full, automatic, support for key-value coding and key-value observing.

    In addition to synthesizing key-value coding and key-value observing compliant accessor methods for attributes, Core Data synthesizes the appropriate collection accessors for to-many relationships.

  • Grouping, filtering, and organizing data in memory and in the user interface.

  • Automatic support for storing objects in external data repositories.

  • Sophisticated query compilation.

    Instead of writing SQL, you can create complex queries by associating an NSPredicate object with a fetch request. NSPredicate provides support for basic functions, correlated subqueries, and other advanced SQL. With Core Data, it also supports proper Unicode, locale-aware searching, sorting, and regular expressions.

  • Merge policies.

    Core Data provides built in version tracking and optimistic locking to support automatic multi-writer conflict resolution. 

Why Should You Use Core Data?

There are a number of reasons why it may be appropriate for you to use Core Data. One of the simplest metrics is that, with Core Data, the amount of code you write to support the model layer of your application is typically 50% to 70% smaller as measured by lines of code. This is primarily due to the features listed above—the features Core Data provides are features you don’t have to implement yourself. Moreover they’re features you don’t have to test yourself, and in particular you don’t have to optimize yourself.

Core Data has a mature code base whose quality is maintained through unit tests, and is used daily by millions of customers in a wide variety of applications. The framework has been highly optimized over several releases. It takes advantage of information provided in the model and runtime features not typically employed in application-level code. Moreover, in addition to providing excellent security and error-handling, it offers best memory scalability of any competing solution. Put another way: you could spend a long time carefully crafting your own solution optimized for a particular problem domain, and not gain any performance advantage over what Core Data offers for free for any application.

In addition to the benefits of the framework itself, Core Data integrates well with the OS X tool chain. The model design tools allow you to create your schema graphically, quickly and easily. You can use templates in the Instruments application to measure Core Data’s performance, and to debug various problems. On OS X desktop, Core Data also integrates with Interface Builder to allow you to create user interfaces from your model. These aspects help to further shorten your application design, implementation, and debugging cycles.

What Core Data Is Not

Having given an overview of what Core Data is and does, and why it may be useful, it is also useful to correct some common misperceptions and state what it is not.

  • Core Data is not a relational database or a relational database management system (RDBMS).

    Core Data provides an infrastructure for change management and for saving objects to and retrieving them from storage. It can use SQLite as one of its persistent store types. It is not, though, in and of itself a database. (To emphasize this point: you could for example use just an in-memory store in your application. You could use Core Data for change tracking and management, but never actually save any data in a file.)

  • Core Data is not a silver bullet.

    Core Data does not remove the need to write code. Although it is possible to create a sophisticated application solely using the Xcode data modeling tool and Interface Builder, for more real-world applications you will still have to write code.

  • Core Data does not rely on Cocoa bindings.

    Core Data integrates well with Cocoa bindings and leverages the same technologies—and used together they can significantly reduce the amount of code you have to write—but it is possible to use Core Data without bindings. You can readily create a Core Data application without a user interface (see Core Data Utility Tutorial).


參考:https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/cdProgrammingGuide.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章