這里是另一篇好文章 http://blog.csdn.net/kesalin/article/details/6739319
這里是另一篇 http://hxsdit.com/1622 (不一定能訪問)
推薦書籍:Core_Data_by_Tutorials 還有就是apple的官方文檔了Core Data Programming Guide
另外可以查看coredata 如何和icould一起使用,這部分內容要看有關icloud的官方文檔。
還需要注意Core Data的多線程 和 數據庫結構更新 等具體問題,這些問題也是面試時比較容易問到的。
多線程問題可以參考Core Data Programming Guide 中的Concurrency with Core Data部分
關於多線程訪問,可以參考以下地址,http://ju.outofmemory.cn/entry/103434
http://blog.csdn.net/fhbystudy/article/details/21958999
由於CoreData涉及的類比較多,先籠統地看一下CoreData的使用思路:
1.通過數據庫文件和model映射文件(.xcdatamodeld)創建出Persistent Store Coordinator
2.用Coordinator創建出Managed Object Context
3.通過Context和其他類進行增刪改等操作
在NSManagedObjectContext的官方文檔中有以下重要信息:
Concurrency Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see Concurrency with Core Data). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread. Instead, you should pass a reference to a persistent store coordinator and have the receiving thread/queue create a new context derived from that. If you use NSOperation, you must create the context in main (for a serial queue) or start (for a concurrent queue). In OS X v10.7 and later and iOS v5.0 and later, when you create a context you can specify the concurrency pattern with which you will use it using initWithConcurrencyType:. When you create a managed object context using initWithConcurrencyType:, you have three options for its thread (queue) association Confinement (NSConfinementConcurrencyType) For backwards compatibility, this is the default. You promise that context will not be used by any thread other than the one on which you created it. In general, to make the behavior explicit you’re encouraged to use one of the other types instead. You can only use this concurrency type if the managed object context’s parent store is a persistent store coordinator. Private queue (NSPrivateQueueConcurrencyType) The context creates and manages a private queue. Main queue (NSMainQueueConcurrencyType) The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread. If you use contexts using the confinement pattern, you send the contexts messages directly; it’s up to you to ensure that you send the messages from the right queue. You use contexts using the queue-based concurrency types in conjunction with performBlock: and performBlockAndWait:. You group “standard” messages to send to the context within a block to pass to one of these methods. There are two exceptions: Setter methods on queue-based managed object contexts are thread-safe. You can invoke these methods directly on any thread. If your code is executing on the main thread, you can invoke methods on the main queue style contexts directly instead of using the block based API. performBlock: and performBlockAndWait: ensure the block operations are executed on the queue specified for the context. The performBlock: method returns immediately and the context executes the block methods on its own thread. With the performBlockAndWait: method, the context still executes the block methods on its own thread, but the method doesn’t return until the block is executed. It’s important to appreciate that blocks are executed as a distinct body of work. As soon as your block ends, anyone else can enqueue another block, undo changes, reset the context, and so on. Thus blocks may be quite large, and typically end by invoking save:. __block NSError *error; __block BOOL savedOK = NO; [myMOC performBlockAndWait:^{ // Do lots of things with the context. savedOK = [myMOC save:&error]; }]; You can also perform other operations, such as: NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:@"Entity"]; __block NSUInteger rCount = 0; [context performBlockAndWait:^() { NSError *error; rCount = [context countForFetchRequest:fr error:&error]; if (rCount == NSNotFound) { // Handle the error. } }]; NSLog(@"Retrieved %d items", (int)rCount);
有幾個要點:
1. 在context初始化是可以傳入一個參數,這個參數是這樣描述的:The concurrency pattern with which context will be used.單單設定這個參數,並不能保證對context的操作就在相應的線程。比如,我開啟新的線程,在線程中調用_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];之后還在新線程里調用 context的save函數,這個save的具體邏輯就會在新線程里執行。
『只能在同一個線程里操作某個context』,這句話不是說我調用某個context指針必須在同一個線程,而是指真正的數據庫操作必須在同一個線程,比如,我在子線程里創建的context對象,完全可以在主線程中引用指針,但是需要調用performBlockAndWait函數去執行具體的數據庫操作才行!這個performBlockAndWait函數才是NSMainQueueConcurrencyType的真正含義:NSMainQueueConcurrencyType 會導致performBlockAndWait的 block在主線程中調用,NSPrivateQueueConcurrencyType會導致performBlockAndWait在子線程里調用。如果你不利用performBlockAndWait這個函數,那么在哪個線程里調用context的具體操作方法(比如 save方法),那個方法就會在哪個線程執行,NSMainQueueConcurrencyType根本沒有作用,官方中的說明,可能是針對不使用performBlockAndWait函數時說的,不使用這個函數時,當然必須保證在同一個線程對context進行操作!另外,我認為,context的save函數僅僅是一段普通代碼塊,save函數在哪里執行,具體的save邏輯就在哪里執行,內部並沒有像performBlockAndWait這種線程操作邏輯。
讀icloud 使用文檔時發現了這句話
Dispatch onto the queue on which your context lives or use the performBlock: API. Notifications may not be posted on the same thread as your managed object context.
更好地說明了在別的線程里也是可以使用context對象的,但是調用context的具體方法必須通過performBlock:。
下面看看具體的測試例子:
我在子線程里建立了一個context,這個context的類型是NSMainQueueConcurrencyType,當執行
performBlockAndWait函數時,有以下截圖,注意,跳轉到了主線程,但是不用performBlockAndWait,單單使用save時,看不出線程跳轉!
而如果使用NSPrivateQueueConcurrencyType,當執行performBlockAndWait函數時,有下面的截圖,沒有跳轉到主線程,也沒有自己再建立新線程,因為滿足在子線程中執行的要求!
注意,這里的虛線分割代表一個thread中的2個由runloop 發出的runloop aciton
再看一段官方文檔中的並發例子,這里利用的是parent context 而不是通過通知的方式。
NSArray *jsonArray = …; //JSON data to be imported into Core Data NSManagedObjectContext *moc = …; //Our primary context on the main queue NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [private setParentContext:moc]; [private performBlock:^{ for (NSDictionary *jsonObject in jsonArray) { NSManagedObject *mo = …; //Managed object that matches the incoming JSON structure //update MO with data from the dictionary } NSError *error = nil; if (![private save:&error]) { NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]); abort(); } }];
今天又遇到一個問題,就是在main context fetch出結果后,把nsmanagedobejct用 strong指針保存了下來。之后在后台線程用privacy context更新了這個object,並保存,並且正確地實現了mergeContextChangesForNotification 方法。結果是前面的那個指針指向的內容還是舊的!如果執行的不是更新,而是刪除,那個指針還是指向舊的內容。我以為這是我的操作錯誤,結果,在NSFetchRequest Class Reference中找到了下面的說明:
A Boolean value that indicates whether the property values of fetched objects will be updated with the current values in the persistent store. Declaration OBJECTIVE-C @property(nonatomic) BOOL shouldRefreshRefetchedObjects Discussion YES if the property values of fetched objects will be updated with the current values in the persistent store, otherwise NO. By default, when you fetch objects they maintain their current property values, even if the values in the persistent store have changed. By invoking this method with the parameter YES, when the fetch is executed the property values of fetched objects to be updated with the current values in the persistent store. This provides more convenient way to ensure managed object property values are consistent with the store than by using refreshObject:mergeChanges: (NSManagedObjetContext) for multiple objects in turn.
就是說,默認fetch出來的nsmanagedobject對象,是不會自動update屬性的,但是mergeContextChangesForNotification的作用不是是保證2個context的內容一致嗎?這樣nsmanagedobject屬性都不一樣,算保持context內容一致嗎?帶着這個問題,我又做了一個測試:
數據庫里有2條數據,有2個managedContext,他們之間沒用用任何同步機制。
1.先在主線程里用一個context1 查詢了數據1,並對其中一個屬性進行訪問,使數據1不處於falut狀態。
2.在子線程里用context2改變了2條數據的內容
3.在主線程里再用context1查詢2條數據
代碼如下:
Knowledge *knowldge4 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:27] context:mainContext]; NSNumber *knowledge4ID = knowldge4.id; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSManagedObjectContext *context = [[DBManager sharedManager] privateContext]; Knowledge *knowldge2 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:27] context:context]; knowldge2.title = @"ddddd"; Knowledge *knowldge3 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:29] context:context]; knowldge3.title = @"3333333"; [[DBManager sharedManager] saveContext:context]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ Knowledge * knowldge5 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:27] context:mainContext]; NSLog(@"knowldge5 is %@",knowldge5.title); Knowledge *knowldge3 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:29] context:mainContext]; NSLog(@"knowldge3 title is %@",knowldge3.title); });
結果,最后一次查詢的2條數據內容的結果是這樣的:數據1的內容還是查詢過的內容,是舊的,是錯的。數據2的內容是更改后的數據,是正確的。
由這個測試,可以看出,mergeContextChangesForNotification,的確是有用的,因為沒有了mergeContextChangesForNotification,context1的數據只要是從fault 狀態 實例化了,即使重新查詢,也無法查詢出數據1的正確內容。
那么為什么即使沒有用mergeContextChangesForNotification數據2的內容卻是正確的呢?我感覺是這樣的:一個context 查詢后數據並解除falut狀態后,會把對應的nsmanagedobject 緩存起來,數據1在更改之前查詢過其屬性,所以有緩存,再次查詢時就不會到物理數據庫找真實的數據,就得到了錯誤的數據。而數據2,在數據更改前,在context1中沒有緩存,所以,必須到物理數據庫中取值,就取到了正確的值。mergeContextChangesForNotification的作用,並不是把context1 中緩存數據直接更改掉,而是把這個緩存數據設置了一個標志位,標識它是舊數據,當再次用context1查詢時,就會重新從物理數據庫讀取真數據(當然,這是默認查詢的情況,如果使用了shouldRefreshRefetchedObjects = YES,就不用再次查詢了,系統自動為我們再次查詢)
另外,我做了一個測試,在同一個context里,進行2次條件一樣的fetch查詢,2個managedObject的地址是一樣的;但是2個不同的context,2個managedObject的地址是不同的。
今天又遇到了類似的問題,子線程中的一個單例context先啟動,進行了程序更新, 這期間,將一個對象查詢並緩存了起來。之后用戶在主線程更新了這個對象的一個屬性,寫入了數據庫,但並沒有發送通知給子線程。最后,子線程再次操作這個對象時,取到的是以前緩存過的對象,再次保存到數據庫時,產生沖突,報錯。