这里是另一篇好文章 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. and later and iOS v5. 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 = ; [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:] context:mainContext];
NSNumber *knowledge4ID = knowldge4.id; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ NSManagedObjectContext *context = [[DBManager sharedManager] privateContext]; Knowledge *knowldge2 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:] context:context];
knowldge2.title = @"ddddd"; Knowledge *knowldge3 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:] context:context];
knowldge3.title = @""; [[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:] context:mainContext];
NSLog(@"knowldge5 is %@",knowldge5.title); Knowledge *knowldge3 = [KnowledgeDAO retrieveKnowledgeByID:[NSNumber numberWithInt:] 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先启动,进行了程序更新, 这期间,将一个对象查询并缓存了起来。之后用户在主线程更新了这个对象的一个属性,写入了数据库,但并没有发送通知给子线程。最后,子线程再次操作这个对象时,取到的是以前缓存过的对象,再次保存到数据库时,产生冲突,报错。

iOS CoreData学习资料 和 问题的更多相关文章

  1. iOS 开发学习资料整理(持续更新)

      “如果说我看得比别人远些,那是因为我站在巨人们的肩膀上.” ---牛顿   iOS及Mac开源项目和学习资料[超级全面] http://www.kancloud.cn/digest/ios-mac ...

  2. iOS安全相关学习资料

    https://github.com/zhengmin1989/iOS_ICE_AND_FIRE  (冰与火代码) http://weibo.com/zhengmin1989?is_hot=1 (蒸米 ...

  3. iOS学习资料整理

    视频教程(英文) 视频 简介 Developing iOS 7 Apps for iPhone and iPad 斯坦福开放教程之一, 课程主要讲解了一些 iOS 开发工具和 API 以及 iOS S ...

  4. iOS 学习资料汇总

    (适合初学者入门) 本文资料来源于GitHub 一.视频教程(英文) Developing iOS 7 Apps for iPhone and iPad斯坦福开放教程之一, 课程主要讲解了一些 iOS ...

  5. iOS超全开源框架、项目和学习资料汇总--数据库、缓存处理、图像浏览、摄像照相视频音频篇

    iOS超全开源框架.项目和学习资料汇总--数据库.缓存处理.图像浏览.摄像照相视频音频篇 感谢:Ming_en_long 的分享 大神超赞的集合,http://www.jianshu.com/p/f3 ...

  6. IOS 学习资料

    IOS学习资料 - 逆天整理 - 精华无密版[最新][精华] 无限互联3G学院 iOS开发视频教程UI 极客学院IOS iPhone 6的自适应布局

  7. 【转】iOS超全开源框架、项目和学习资料汇总

    iOS超全开源框架.项目和学习资料汇总(1)UI篇iOS超全开源框架.项目和学习资料汇总(2)动画篇iOS超全开源框架.项目和学习资料汇总(3)网络和Model篇iOS超全开源框架.项目和学习资料汇总 ...

  8. iOS CoreData技术学习资源汇总

    一.CoreData学习指引 1. 苹果官方:Core Data Programming Guide 什么是CoreData? 创建托管对象模型 初始化Core Data堆栈 提取对象 创建和修改自定 ...

  9. iOS 学习资料整理

    iOS学习资料整理 https://github.com/NunchakusHuang/trip-to-iOS 很好的个人博客 http://www.cnblogs.com/ygm900/ 开发笔记 ...

随机推荐

  1. hdu3487 伸展树(区间搬移 区间旋转)

    对于区间旋转使用lazy思想就能解决.然后对于区间搬移,先把a-1结点做根,b+1作为它的右孩子,这样ch[ch[root][1]][0]就是区间[a,b],现将他取出. 然后在将当前的树伸展,把c结 ...

  2. hdu3308 线段树 区间合并

    给n个数字 U表示第A个数改为B.A是从0开始. Q输出最大的递增序列个数. 考虑左边,右边,和最大的. #include<stdio.h> #define lson l,m,rt< ...

  3. Struts2(二)---将页面表单中的数据提交给Action

    问题:在struts2框架下,如何将表单数据传递给业务控制器Action. struts2中,表单想Action传递参数的方式有两种,并且这两种传参方式都是struts2默认实现的,他们分别是基本属性 ...

  4. Learn sed using these command on Linux(流线式编辑器——sed)

    是对文件中的每一行进行处理,不会对源文件进行修改 sed --version sed '11d' sed_file sed -n '/[Bb]erry/p' sed_file (由于设置了n,所以只打 ...

  5. BZOJ-2324 营救皮卡丘 最小费用可行流+拆下界+Floyd预处理

    准备一周多的期末,各种爆炸,回来后状态下滑巨快...调了一晚上+80%下午 2324: [ZJOI2011]营救皮卡丘 Time Limit: 10 Sec Memory Limit: 256 MB ...

  6. TYVJ1864 守卫者的挑战

    P1864 [Poetize I]守卫者的挑战 时间: 1000ms / 空间: 131072KiB / Java类名: Main 描述 打开了黑魔法师Vani的大门,队员们在迷宫般的路上漫无目的地搜 ...

  7. python urllib2使用心得

    python urllib2使用心得 1.http GET请求 过程:获取返回结果,关闭连接,打印结果 f = urllib2.urlopen(req, timeout=10) the_page = ...

  8. crossdomain.xml的配置详解

    目录 1 简介 2 crossdomain.xml的配置详解 3 总结 1 简介 flash在跨域时唯一的限制策略就是crossdomain.xml文件,该文件限制了flash是否可以跨域读写数据以及 ...

  9. mongo操作

    详细使用网址:http://blog.csdn.net/xinghebuluo/article/details/7050811 MongoDB基本使用 成功启动MongoDB后,再打开一个命令行窗口输 ...

  10. 在ECSHOP后台左侧导航中增加新菜单

    在ECSHOP后台左侧导航中增加新菜单 ECSHOP教程/ ecshop教程网(www.ecshop119.com) 2011-11-08   有个别高级用户(懂PHP的),提到这样的问题: 在后台管 ...