IOS-CoreData的使用详解

前言:很多小的App只需要一个ManagedContext在主线程就可以了,但是有时候对于CoreData的操作要耗时很久的,比如App开启的时候要载入大量数据,如果都放在主线程,毫无疑问会阻塞UI造成用户体验很差。通常的方式是,主线程一个ManagedContext处理UI相关的,后台一个线程的ManagedContext负责耗时操作的,操作完成后通知主线程。使用CoreData的并行主要有两种方式

Notification child/parent context
何时会使用到后台-简单来说就是要耗费大量时间,如果在主线程上会影响用户体验的时候。
例如:导入大量数据 执行大量计算

CoreData与线程安全

CoreData不是线程安全的,对于ManagedObject以及ManagedObjectContext的访问都只能在对应的线程上进行,而不能跨线程。
有几条自己总结的规则:
对于多个线程,每个线程使用自己独立的ManagedContext 对于线程间需要传递ManagedObject的,传递ManagedObject ID,通过objectWithID或者existingObjectWithID来获取 对于持久化存储协调器(NSPersistentStoreCoordinator)来说,可以多个线程共享一个NSPersistentStoreCoordinator

CoreData几个对象的介绍如下:

具体实现之前先来认识如下几个对象

(1)NSManagedObjectModel(被管理的对象模型)

相当于实体,不过它包含 了实体间的关系

(2)NSManagedObjectContext(被管理的对象上下文)

操作实际内容

作用:插入数据 查询 更新 删除

(3)NSPersistentStoreCoordinator(持久化存储助理)

相当于数据库的连接器

(4)NSFetchRequest(获取数据的请求)

相当于查询语句

(5)NSPredicate(相当于查询条件)

(6)NSEntityDescription(实体结构)

为了方便实现,本文整理一个数据管理类来测试CoreData:CoreDataManager

CoreDataManager.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface CoreDataManager : NSObject<NSCopying>

@property(strong,nonatomic,readonly)NSManagedObjectModel* managedObjectModel;//管理数据模型

@property(strong,nonatomic,readonly)NSManagedObjectContext* managedObjectContext;//管理数据内容

@property(strong,nonatomic,readonly)NSPersistentStoreCoordinator* persistentStoreCoordinator;//持久化数据助理

//创建数据库管理者单例
+(instancetype)shareManager;

//插入数据
-(void)insertData:(NSString*)tempName;

//删除数据
-(void)deleteData;

//删除数据
-(void)deleteData:(NSString*)tempName;

//查询数据
-(void)queryData;

//根据条件查询
-(void)queryData:(NSString*)tempName;

//更新数据
-(void)updateData:(NSString*)tempName;

@end

CoreDataManager.m

#import "CoreDataManager.h"
#import "Car.h"

static CoreDataManager *shareManager=nil;

@implementation CoreDataManager

@synthesize managedObjectContext =_managedObjectContext;

@synthesize managedObjectModel = _managedObjectModel;

@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

//实例化对象
-(instancetype)init
{
    self=[super init];
    if (self) {

    }
    return self;
}

//创建数据库管理者单例
+(instancetype)shareManager
{
    //这里用到了双重锁定检查
    if(shareManager==nil){
        @synchronized(self){
            if(shareManager==nil){
                shareManager =[[[self class]alloc]init];
            }
        }
    }
    return shareManager;
}

-(id)copyWithZone:(NSZone *)zone
{

    return shareManager;
}

+(id)allocWithZone:(struct _NSZone *)zone
{
    if(shareManager==nil){
        shareManager =[super allocWithZone:zone];
    }
    return shareManager;
}

//托管对象
-(NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel!=nil) {
        return _managedObjectModel;
    }

    NSURL* modelURL=[[NSBundle mainBundle] URLForResource:@"myCoreData" withExtension:@"momd"];
    _managedObjectModel=[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

//托管对象上下文
-(NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext!=nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator* coordinator=[self persistentStoreCoordinator];
    if (coordinator!=nil) {
        _managedObjectContext=[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];//NSMainQueueConcurrencyType NSPrivateQueueConcurrencyType

        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

//持久化存储协调器
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator!=nil) {
        return _persistentStoreCoordinator;
    }
    NSString* docs=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSURL* storeURL=[NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"myCoreData.sqlite"]];
    NSLog(@"path is %@",storeURL);
    NSError* error=nil;
    _persistentStoreCoordinator=[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Error: %@,%@",error,[error userInfo]);
    }
    return _persistentStoreCoordinator;
}

//插入数据
-(void)insertData:(NSString*)tempName
{
    //读取类
    Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
    car.name=tempName;
    //保存
    NSError *error;
    [self.managedObjectContext save:&error];
}

//删除数据
-(void)deleteData
{
    //创建读取类
    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];

    //创建连接
    NSFetchRequest* request=[[NSFetchRequest alloc] init];
    [request setEntity:entity];

    //启动查询
    NSError *error;
    NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
    if(deleteArr.count){
        for (Car *car in deleteArr) {
            [self.managedObjectContext deleteObject:car];
        }
        NSError *error;
        [self.managedObjectContext save:&error];
    }else{
        NSLog(@"未查询到可以删除的数据");
    }

}

//删除数据
-(void)deleteData:(NSString*)tempName;
{
    //创建读取类
    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];

    //创建连接
    NSFetchRequest* request=[[NSFetchRequest alloc] init];
    [request setEntity:entity];

    //创建检索条件
    NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
    [request setPredicate:predicate];

    //启动查询
    NSError *error;
    NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
    if(deleteArr.count){
        for (Car *car in deleteArr) {
            [self.managedObjectContext deleteObject:car];
        }
         NSError *error;
        [self.managedObjectContext save:&error];
    }else{
        NSLog(@"未查询到可以删除的数据");
    }
}


//查询数据
-(void)queryData
{
    //创建读取类
    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];

    //创建连接
    NSFetchRequest* request=[[NSFetchRequest alloc] init];
    [request setEntity:entity];

    //启动查询
    NSError *error;
    NSArray *carArr=[self.managedObjectContext executeFetchRequest:request error:&error];
    for(Car *car in carArr){
        NSLog(@"car---->%@",car.name);
    }

}

-(void)queryData:(NSString*)tempName
{
    //创建读取类
    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];

    //创建连接
    NSFetchRequest* request=[[NSFetchRequest alloc] init];
    [request setEntity:entity];

    //创建检索条件
    NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
    [request setPredicate:predicate];

    //启动查询
    NSError *error;
    NSArray *carArr=[self.managedObjectContext executeFetchRequest:request error:&error];
    for(Car *car in carArr){
        NSLog(@"car---->%@",car.name);
    }

}

//更新数据
-(void)updateData:(NSString*)tempName
{
    //创建读取类
    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Car" inManagedObjectContext:self.managedObjectContext];

    //创建连接
    NSFetchRequest* request=[[NSFetchRequest alloc] init];
    [request setEntity:entity];

    //创建检索条件
    NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name=%@",tempName];
    [request setPredicate:predicate];

    //启动查询
    NSError *error;
    NSArray *deleteArr=[self.managedObjectContext executeFetchRequest:request error:&error];
    if(deleteArr.count){
        for (Car *car in deleteArr) {
            car.name=@"test";
        }
        NSError *error;
        [self.managedObjectContext save:&error];
    }else{
        NSLog(@"未查询到可以删除的数据");
    }

}

@end

测试一下效率:测试数据10000条

NSMutableArray *testArray =[[NSMutableArray alloc]init];
           int testMaxCount =10000;
           for(int i=0;i<testMaxCount;i++){
               NSString *string = [[NSString alloc] initWithFormat:@"%d",i];
              [testArray addObject:string];
           }

            //测试一下效率  第1种
           CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
           for(NSString *tempName in testArray){
                [[CoreDataManager shareManager]insertData:tempName];
           }
           CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
           NSLog(@"coreData数据插入 time cost: %0.3f", end - start);

           //测试一下效率  第2种
             start = CFAbsoluteTimeGetCurrent();
            [[CoreDataManager shareManager]insertDatas:testArray];
             end=CFAbsoluteTimeGetCurrent();
            NSLog(@"coreData数据插入 time cost: %0.3f", end - start);

insertData函数:

//插入数据
-(void)insertData:(NSString*)tempName
{
    //读取类
    Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
    car.name=tempName;
    //保存
    NSError *error;
    [self.managedObjectContext save:&error];
}

insertDatas函数:

//插入数据
-(void)insertDatas:(NSArray*)tempNames
{
    for(NSString *name in tempNames){
        Car *car=[NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
        car.name=name;
    }
    NSError *error;
    [self.managedObjectContext save:&error];

}

**运行结果:
第一种:8.408
第二种:0.162
但是有个超级大的问题,
第二种方式虽然效率高,但是插入数据乱序。
第一种正常但是效率超低,同样近似的数据量sqlite效率比这个高不知多少倍。
Coredata批量操作支持的不太好。
另外:CoreData 的数据模型升级兼容性比较差,如果模型不对,会导致程序连起都起不来。虽然提供了模型升级代码,但是在客户端的管理模型版本管理也会相对复杂。**

并行的解决方案之Notification

简单来说,就是不同的线程使用不同的context进行操作,当一个线程的context发生变化后,利用notification来通知另一个线程Context,另一个线程调用mergeChangesFromContextDidSaveNotification来合并变化。

Notification的种类

NSManagedObjectContextObjectsDidChangeNotification 当Context中的变量改变时候触发。

NSManagedObjectContextDidSaveNotification 在一个context调用save完成以后触发。注意,这些managed object只能在当前线程使用,如果在另一个线程响应通知,要调用mergeChangesFromContextDidSaveNotification来合并变化。

NSManagedObjectContextWillSaveNotification。将要save。
一种比较好的iOS模式就是使用一个NSPersistentStoreCoordinator,以及两个独立的Contexts,一个context负责主线程与UI协作,一个context在后台负责耗时的处理。

为什么说这样做的效率更高?

这样做两个context共享一个持久化存储缓存,而且这么做互斥锁只需要在sqlite级别即可。设置当主线程只读的时候,都不需要锁。

并行的解决方案之child/parent context

ChildContext和ParentContext是相互独立的。只有当ChildContext中调用Save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到NSPersistentStoreCoordinator中, parentContext就相当于它的NSPersistentStoreCoordinator。

这其中有几点要注意

通常主线程context使用NSMainQueueConcurrencyType,其他线程childContext使用NSPrivateQueueConcurrencyType. child和parent的特点是要用Block进行操作,performBlock,或者performBlockAndWait,保证线程安全。
这两个函数的区别是performBlock不会阻塞运行的线程,相当于异步操作,performBlockAndWait会阻塞运行线程,相当于同步操作。
举例
和上述类似,这次不需要监听变化,因为变化会自动提交到mainContext。

CoreData线程安全问题

NSManagedObjectContext不是线程安全的,只能在创建NSManagedObjectContext的那个线程里访问它。一个数据库有多个UIManagedDocument和context,它们可以在不同的线程里创建,只要能管理好它们之间的关系就没问题。

线程安全的意思是,程序可能会崩溃,如果多路访问同一个NSManagedObjectContext,或在非创建实例的线程里访问实例,app就会崩溃。对此要怎么做呢?NSManagedObjectContext有个方法叫performBlock可以解决这个问题:

[context performBlock:^{   //or performBlockAndWait:
    // do stuff with context
}];

它会自动确保block里的东西都在正确的context线程里执行,但这不一定就意味着使用了多线程。事实上,如果在主线程下创建的context,那么这个block会回到主线程来,而不是在其他线程里运行,这个performBlock只是确保block运行在正确的线程里。

NSManagedObjectContext,包括所有使用SQL的Core Data,都有一个parentContext,这就像是另一个NSManagedObjectContext,在真正写入数据库之前要写入到这里。可以获取到parentContext,可以让parentContext调用performBlock来做一些事,这总是在另一个线程里进行。parentContext和创建的NSManagedObjectContext不在一个线程里运行,可以通过performBlock在那个线程里执行你要做的事。记住,如果改变了parentContext,必须保存,然后重新获取child context。如果你想在非主线程载入很多内容,那么就全部放入数据库,然后在主线程去获取,这个效率非常快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值