iOS CoreData学习资料 和 问题

这里是另一篇好文章 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: %@
%@", [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先启动,进行了程序更新, 这期间,将一个对象查询并缓存了起来。之后用户在主线程更新了这个对象的一个属性,写入了数据库,但并没有发送通知给子线程。最后,子线程再次操作这个对象时,取到的是以前缓存过的对象,再次保存到数据库时,产生冲突,报错。

原文地址:https://www.cnblogs.com/breezemist/p/3564854.html