一.简介 Core Data 是一个对象-关系映射
持久化方案,处于应用与持久化存储之间,可以将应用中的对象转化成数据,保存在SQLite文件等持久化存储中,也可以缓存临时数据。在单台设备上 Core Data 支持回滚重做,在多台设备间使用同一 iCloud 账户同步数据时,Core Data 会自动将结构映射到 CloudKit 中。
在.xcdatamodeld
数据模型中,可以定义数据类型与关系、生成对应的类定义。Core Data 会自动管理数据对象并提供以下功能:
抽象化映射对象到持久化存储中的细节,让保存数据更加简单,你甚至不用写数据库管理代码。
持久化存储包括四种:SQLite, Binary, XML, In-Memory,其中 XML 在 iOS 中不可用。
Core Data 的回滚管理器会跟踪数据变化,支持单独、批量或者全部回滚数据。
支持在后台处理会堵塞UI线程的任务,例如将JSON解析成对象。稍后你可以将结果缓存或保存起来,节省与服务器的数据交互次数。
为table
与collection
视图提供数据源,帮助处理视图与数据的同步。
给.xcdatamodeld
文件设置版本,支持在model变化后将数据迁移到最新版本。
二.Model-模型 .xcdatamodeld
文件用来定义应用中数据对象的结构,包括数据对象的类型、属性、关系。可通过新建文件
→Data Model
手动创建,其默认文件名为Model.xcdatamodeld
。
三.Entity-实体 .xcdatamodeld
=库
,Entity
=表
,Entity 描述的是一个对象,包括它的名字、属性、关系。在.xcdatamodeld
编辑器侧边栏中表示为Entity
,可配置的字段包括:
1.Entity Name model文件中实体的名字。
2.Abstract Entity 若实体只作为父类使用而不会被直接实例化,可勾选此复选框。默认未勾选,即创建具体的实体。
3.Parent Entity 当有多个属性相似的实体时,可在一个父实体中定义通用属性,然后让子实体继承这些属性。
4.Class Name 以实体为基础创建托管对象实例时所指定的类名,默认与实体名相同。
5.Codegen 在.xcdatamodeld
编辑界面定义好实体后,还需要生成对应的托管对象类和属性文件,以便在实际开发中创建和使用实体的实例。这些文件可以手动生成,也可以让 Core Data 帮我们自动生成。我们需要做的是为Codegen
选项指定不同的值。以下是选项值与具体场景的对应关系:
选项
场景
Class Definition
由Core Data自动生成托管对象类和属性文件,且我们不会主动修改这些文件。托管对象类和属性文件在编译时被放到编译目录,而不会出现在工程列表中;
Category/Extension
由Core Data自动生成托管对象类和属性文件,同时我们可自己生成分类,添加自己的业务逻辑或方法,以便能完全控制托管对象类的实现。
Manual/None
Core Data 不会帮我们生成任何文件,由你自己手动维护类和属性。通过编辑面板中设置的class name
来定位和关联这些文件。
6.Renaming ID 给实体重命名后,需要将新model中实体的renaming identifier
设置成原model中实体的名字。
四.Attribute-属性 配置属性时至少需要指定属性名、类型、是否必须保存到存储中、保存时是否必须有值。
对于一些属性,你还可以选择是否使用纯量类型、属性的默认值、指定数据校验规则。
1.Type 属性的可用数据类型如下所示:
其中transformable
用于保存我们自定义的类、数组及图片等非常规数据类型。
2.Optional 属性默认为Optional
,即保存到持久化存储时,属性不要求必须有值。Core Data 不推荐使用这个选项,因为SQL中对Null
的比较与OC中对nil
的比较机制完全不一样,在数据库中NULL ≠ 0
,且NULL ≠ 空字符串
,所以用SQL搜索数字0
的结果与搜索NULL的结果并不相同。
3.Default Value 属性默认值,初始化实体时,该属性会被自动赋上此默认值。可勾掉Optional
选项搭配使用。
4.Transient 默认情况下,属性会被保存到持久化存储中,但Transient
属性不会。短暂的
属性适用于临时保存一些值的场景。Core Data 不会跟踪这种属性的变化,所以也就没法做回滚这种操作。
5.Validation 设置校验规则,例如给数值类型设置最大值最小值,或者给字符串类型设置正则表达式。
6.Renaming ID 给属性重命名后,需要将新model中属性的renaming identifier
设置成原model中属性的名字。
五.Relationship-关系 设置关系时需要指定其名字、目标实体、删除规则、对1或对N类型,并且配置反相的关系。
1.Destination 目标实体,例如一个课程
对应多个老师
时,在课程
实体中可设置关系的目标实体为名老师
。
2.Inverse 设置关系的另一半,因为在面板中只能从一个方向定义关系,这个选项是让两个关系组合起来成为一个完整的双向关系。这样 Core Data 才能在实体发生变化时在双方间传递这种变化。
3.Delete Rule 当源实体被删除时,关系的目标实体如何响应。
选项
删除规则
No Action
删除源实体时,目标实体中保留关系的引用,由你手动更新
Nullify
删除源实体时,目标实体中的关系引用自动置空
Cascade
删除源实体时,同时删除关系中的所有目标实体
Deny
只在关系未指向任何目标实体时才删除源实体
无操作,删除A后其关联的B没任何操作,不会将B中关联属性(A)指向nil。删除A后使用B的关联属性(A),可能会导致其他问题,所以一般不推荐使用此配置。
翻译为作废,默认选项,当A对象被删除时,B中指向的对象A会被置空,B本身不受影响,所以删除A不会删除B。例如,删除一个部门时,把员工对应的部门字段置为nil。
级联,当A对象被删除时,A对象包含的对象B也会被删除。一般用在 1-N 的关系中,1 的一方被删除,则 N 的一方随之被全部级联删除。相反,在 N-1 的关系中,则一般不能使用级联关系,删除多的一方时一定不能直接级联删除一的一方。例如,删除了部门以后,所有的员工对象都要被删除。
拒绝,删除当前对象时,如果当前对象还指向其他关联对象,则当前对象不能被删除。例如,删除部门时,如果还有一个员工,删除操作就会被拒绝。
4.Cardinality Type 关系分为对1
和对多
两种类型。
5.Renaming ID 给关系重命名后,需要将新model中关系的renaming identifier
设置成原model中关系的名字。
六.Core Data Stack 创建数据模型文件后需要设置相关类以便真正操作数据对象,这些类被称为Core Data Stack
。
1.MOM ManagedObjectModel,托管对象模型,对应着Xcode中的.xcdatamodeld
文件,保存在工程或 framework 里,通过URL
加载。
1 2 3 4 5 6 7 8 - (NSManagedObjectModel *)managedObjectModel { if (_managedObjectModel != nil ) { return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd" ]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return _managedObjectModel; }
MOM
中包含了一个或多个NSEntityDescription
对象,维护着Entity
与对应的managed object
间的映射关系。MOM
支持使用预定义的Fetch request
,以返回符合条件的数据对象。可以在.xcdatamodeld
编辑面板中定义 fetch request 模板,也可以通过代码创建。
1 2 3 4 NSManagedObjectModel *model = [CoreDataStack shareInstance].managedObjectModel;NSFetchRequest *request = [model fetchRequestTemplateForName:@"Fetch1" ];NSError *error ;NSArray *matchArr = [self.viewContext executeFetchRequest:request error :&error ];
2.MOC ManagedObjectContext,托管对象上下文,在managed object
的生命周期中扮演者重要角色。
1 2 3 4 5 6 7 8 9 10 11 12 - (NSManagedObjectContext *)managedObjectContext { if (_managedObjectContext != nil ) { return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil ; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; return _managedObjectContext; }
所有的托管对象都必须在MOC中,而 MOC 位于RAM中。通过 MOC 你可以从持久化存储中返回托管对象,并且 MOC 会负责跟踪它们的变化,例如对托管对象的增删改、校验、反向关系的处理、回滚/重做等。这些变化会先保存在内存中关联的 MOC 中,直到执行 save 操作时,Core Data 才会将此变化同步到持久化存储中。
1 2 3 4 5 6 7 8 9 10 11 12 NSFetchRequest *fetchRequest = [Course fetchRequest]; Course *indexedCourse = _mDatasourceArr[index];NSPredicate *predicate= [NSPredicate predicateWithFormat:@"id = %d" ,indexedCourse.id]; [fetchRequest setPredicate:predicate]; NSError *error = nil ;NSArray *fetchedObjects = [self .viewContext executeFetchRequest:fetchRequest error:&error];for (Course *c in fetchedObjects) { [self .viewContext deleteObject:c]; } [self save];
我们的数据对象需要保存到持久化存储中,而持久化存储一般在磁盘中,读写速度相对较慢,不应频繁地访问。MOC位于 RAM 中,读写速度相对较快,它可以快速访问内存中的托管对象,跟踪托管对象的频繁变化,提供完整的回滚和重做支持。开发者只需定期通过 MOC 调用save
方法,将这些托管对象真正写入磁盘中。
3.PSC NSPersistentStoreCoordinator,是MOC与应用持久化存储之间的桥梁。MOC需要通过PSC访问MOM,PSC将注册在其下面的持久化存储集中展示,便于MOC一次性操作,而非一个个去操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (NSPersistentStoreCoordinator * )persistentStoreCoordinator { if (_persistentStoreCoordinator != nil ) { return _persistentStoreCoordinator; } _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSURL * storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent :@"ASDF.sqlite" ]; NSError * error = nil ; NSDictionary * options = @{NSMigratePersistentStoresAutomaticallyOption : @YES , NSInferMappingModelAutomaticallyOption : @YES }; if (! [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:& error]) { } return _persistentStoreCoordinator; }
PSC在私有串行队列上执行任务,如果有需要你也可以使用多个PSC以便在不同的队列上执行任务。
使用PSC可以增加或删除某个持久化存储,更改存储的类型或位置,查询存储的元数据,定义存储的迁移,定义两个对象是否源自于同一个存储等等。
4.NSPersistentContainer 这是 iOS10 之后新出的一个封装了MOC、MOM、PSC的容器,用来简化 Core Data stack 的初始化和管理工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (NSPersistentContainer *)persistentContainer { @synchronized (self ) { if (_persistentContainer == nil ) { _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"Model" ]; [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) { if (error != nil ) { NSLog (@"error %@, %@" , error, error.userInfo); } }]; } } return _persistentContainer; }
它提供了我们常用的三个 Stack 属性,通过 persistentContainer 对象调用即可:
1 2 3 @property (strong , readonly ) NSManagedObjectContext *viewContext;@property (strong , readonly ) NSManagedObjectModel *managedObjectModel;@property (strong , readonly ) NSPersistentStoreCoordinator *persistentStoreCoordinator;
七.托管对象 定义好MOM、初始化 Core Data Stack 之后,就可以为持久化存储创建、保存数据对象了。
1.MO NSManagedObject,表示持久化存储中保存的数据对象,它是所有MO对象的基类,是Entity
在代码层面真正对应的类。在.xcdatamodeld
编辑界面中,选中Course
这个Entity
,打开右侧的属性面板,在Entity
栏下有个Name
字段,它是Entity
在.xcdatamodeld
文件中的名字;而下方有个Class
栏,这些是与此Entity
绑定的MO子类信息,其中Name
字段就是对应的MO子类名,一般与Entity
同名,即Course
。当然,你也可以根据自己的规范定义成别的名字。
1 2 @interface Course : NSManagedObject @end
通常Entity
属性面板中codegen
字段设置为Class Definition
时,Core Data 会自动为我们生成此类定义,只是这个类不会出现在代码列表中,我们可在代码中导入其头文件直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #import "Course+CoreDataClass.h" Course *c = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .viewContext]; c.id = index; c.name = @"英语" ; NSManagedObjectContext *context = self .persistentContainer.viewContext;NSError *error = nil ;if ([context hasChanges] && ![context save:&error]) { NSLog (@"~~error %@, %@" , error, error.userInfo); }
2.NSEntityDescription 通过上面MO的示例可以看到,使用MO时需要两个元素的配合:NSEntityDescription
与MOC
。
其中NSEntityDescription
是对模型文件中Entity
的描述,包括名字、属性、关系,及代码层面此Entity
代表的MO实体类。我们在.xcdatamodeld
编辑面板中定义Entity
,而 Core Data 使用NSEntityDescription
来匹配持久化存储中的Managed Object
。
后者是管理MO的上下文,顾名思义,就是用来跟踪MO及其关系的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];NSEntityDescription *desription = [NSEntityDescription entityForName:@"Course" inManagedObjectContext:self .viewContext]; [fetchRequest setEntity:desription]; Course *indexedCourse = _mDatasourceArr[index];NSPredicate *predicate= [NSPredicate predicateWithFormat:@"id = %d" ,indexedCourse.id]; [fetchRequest setPredicate:predicate];NSError *error = nil ;NSArray *fetchedObjects = [self .viewContext executeFetchRequest:fetchRequest error:&error];for (Course *c in fetchedObjects) { [self .viewContext deleteObject:c]; } [self save];
八.增删改查 1.查询 1.1.结果类型 MOC需通过NSFetchRequest
执行查询操作,查询结果以数组形式返回,默认情况下数组中返回的是托管对象MO
。当然你也可以通过 fetch 的resultType
属性将数组中的对象指定为其他类型。
1 @property (nonatomic ) NSFetchRequestResultType resultType;
Core Data 支持四种返回类型:
1 2 3 4 5 6 typedef NS_OPTIONS (NSUInteger , NSFetchRequestResultType ) { NSManagedObjectResultType = 0x00, NSManagedObjectIDResultType = 0x01, NSDictionaryResultType = 0x02, NSCountResultType = 0x04 };
NSDictionaryResultType
,返回字典类型的对象;
NSCountResultType
,返回请求结果的count数值;
NSManagedObjectResultType
,默认值,查询结果数组中的元素为MO对象;
NSManagedObjectIDResultType
,查询结果数组中的元素为NSManagedObjectID 对象,即MO对象的ID。这种类型的内存占用比较小,MOC可以继续通过ID对象获取对应的MO。
1 2 NSManagedObjectID *moID = objectIDs[0 ];NSManagedObject *obj = [managedObjectContext existingObjectWithID:moID error:&error];
1.2.Fault对象 1 @property (nonatomic ) BOOL returnsObjectsAsFaults;
这是 Fetch 请求的一个属性,默认值是YES,表示让查询结果返回Fault
(惰值)状态的托管对象。Fault
对象是托管对象的占位对象,作为查询结果被保存在 MOC 中,而 MOC 位于内存中。为了节省内存开销,Fault
对象的属性值暂时不会填充到对应字段上,而是先保存在持久化存储的row cache
(行缓存)中。当访问或修改这些属性值时,Core Data 才去持久化存储的行缓存
中取出属性值并填充到Fault
对象中,使其成为完全体的托管对象。
这种设计减少了内存消耗,却有一定的性能开销,如果你想在 fetch 结果返回后立即访问其中MO对象的属性,则应该将这个属性的值置为NO
。
另外,这个属性只在返回类型为ObjectResultType
时有效,对ObjectIDResultType
无效, 因为 object IDs 没有属性值,设置此属性没有意义。
1.3.过滤条件 1 @property (nonatomic , strong ) NSPredicate *predicate;
运算符
作用
示例
> 、< 、== 、>= 、<= 、!=
比较运算
age > 18
IN
被包含
name IN {‘张三’,’李四’}
BETWEEN
在区间内
age BETWEEN {18,80}
BEGINSWITH
开头是
name BEGINSWITH ‘张’
ENDSWITH
结尾是
name ENDSWITH ‘四’
CONTAINS
包含有
name CONTAINS ‘四’
LIKE
通配符 *和?
name LIKE ‘*四’
MATCHES
正则
name MATCHES ‘(regex)’
1.4.排序规则 返回数据的排序规则,这是个数组,即支持多重排序,按数组中的前后顺序先后执行。
1 @property (nonatomic , strong ) NSArray <NSSortDescriptor *> *sortDescriptors;
1.5.分页查询 1 2 3 4 5 6 @property (nonatomic ) NSUInteger fetchLimit;@property (nonatomic ) NSUInteger fetchBatchSize;@property (nonatomic ) NSUInteger fetchOffset;
1.6.案例演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (void )loadDataAtPage:(NSUInteger )page{ NSFetchRequest *request = [Course fetchRequest]; NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES ]; request.sortDescriptors = @[descriptor]; request.fetchOffset = (page - 1 ) * kLimiteSize; request.fetchLimit = kLimiteSize; NSError *error; NSArray *matchArr = [self .viewContext executeFetchRequest:request error:&error]; }
2.修改 先查询MO对象,再逐一修改其属性,最后由MOC执行save,保存到持久化存储中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (void )updateDataAtIndex:(NSUInteger )index{ NSFetchRequest *request = [Course fetchRequest]; NSPredicate *predicate= [NSPredicate predicateWithFormat:@"id = %d" ,index]; [request setPredicate:predicate]; NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES ]; request.sortDescriptors = @[descriptor]; NSError *error; NSArray *matchArr = [self .viewContext executeFetchRequest:request error:&error]; NSInteger count = matchArr.count; for (NSInteger i = 0 ; i < count; i++) { Course *c = matchArr[i]; c.name = [c.name stringByAppendingString:@"X" ]; } NSError *error = nil ; if ([self .viewContext hasChanges] && ![self .viewContext save:&error]) { NSLog (@"~~error %@, %@" , error, error.userInfo); } }
3.插入 插入数据,要用到以下这个NSEntityDescription
的静态方法来执行数据的创建工作:
1 2 + (NSManagedObject *)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
这里的name
参数是模型文件中Entity
的名字,Core Data 根据Entity
的结构来创建MO对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 - (void )insertData{ int32_t index = (int32_t)self .mDatasourceArr.count; Student *s1 = [[Student alloc] init]; s1.sID = 101 ; s1.name = @"学生甲" ; Student *s2 = [[Student alloc] init]; s2.sID = 102 ; s2.name = @"学生乙" ; Teacher *t = [NSEntityDescription insertNewObjectForEntityForName:@"Teacher" inManagedObjectContext:self .viewContext]; t.id = index; t.name = [NSString stringWithFormat:@"老师%d" ,index]; t.avator = [UIImage imageNamed:@"1" ]; t.descript = @"省级优秀教师" ; Course *c = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .viewContext]; c.id = index; c.name = [NSString stringWithFormat:@"课程%d" ,index]; c.url = @"https://www.baidu.com" ; c.teachers = [NSSet setWithObject:t]; c.students = [NSSet setWithObjects:s1, s2, nil ]; NSError *error = nil ; if ([self .viewContext hasChanges] && ![self .viewContext save:&error]) { NSLog (@"~~error %@, %@" , error, error.userInfo); }
4.删除 先查询MO对象,再通过MOC执行delete,删除这些MO,最后执行save同步到存储中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (void )removeDataAtIndex:(NSUInteger )index{ NSFetchRequest *fetchRequest = [Course fetchRequest]; Course *indexedCourse = _mDatasourceArr[index]; NSPredicate *predicate= [NSPredicate predicateWithFormat:@"id = %d" ,indexedCourse.id]; [fetchRequest setPredicate:predicate]; NSError *error = nil ; NSArray *fetchedObjects = [self .viewContext executeFetchRequest:fetchRequest error:&error]; for (Course *c in fetchedObjects) { [self .viewContext deleteObject:c]; } NSError *error = nil ; if ([self .viewContext hasChanges] && ![self .viewContext save:&error]) { NSLog (@"~~error %@, %@" , error, error.userInfo); } }
九.类型转换 在定义Entity时,属性可以是以下类型:
Integer
Float
Double
Decimal
Boolean
String
Date
Binary Data
UUID
URI
Transformable
Core Data 不支持直接保存图片、音视频文件、颜色、数组、自定义类型等,这时可以将实体中对应的属性设置成 Binary Data 类型,再将这些文件或自定义类型转换成 Data,赋值给实体的属性,执行 save 保存到持久化存储中。读取属性时则反过来,将持久化存储中的 data 手动转换成对应的文件或自定义类型即可。
但是,每次都自己手动转换显然比较麻烦。这时可以这样做:
将属性设置成Transformable
类型;
提供一个继承自NSValueTransformer
的子类,重写必须的方法;
在模型编辑窗口,设置属性的Transformer
与Custom Class
字段;
保存属性时,直接给属性赋值,Core Data 会通过我们指定的NSValueTransformer
子类,自动执行数据转换,将属性中的值转换成 data,写入持久化存储;
读取属性时,从持久化存储中读取 data,由NSValueTransformer
子类自动转换成对应类型;
这里的NSValueTransformer
是一个抽象类,支持单向或双向的类型转换。其内部已经封装好了数据类型转换所需的抽象方法,子类需要自己提供实现:
1 + (Class )transformedValueClass;
例如将Student
保存入库时,需要将Student
对象转换成Data
,这里就返回Data
类型。
1 - (null able id)transformedValue:(null able id)value;
例如保存Student
类型时,需要将Student
对象转换成Data
,这里就返回转换之后 Data 的值。
1 + (BOOL)allowsReverseTransformation;
例如将Student
转成data入库后,读取时是否允许将data回转成Student
。
1 - (null able id)reverseTransformedValue:(null able id)value;
例如读取属性时,要将库中的Data
转换成Student
,这里就返回转换之后的Student
实例。
下面来看一些常见的类型转换怎么重写:
1.颜色转Data 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @interface RGBColorValueTransformer : NSValueTransformer @end @implementation RGBColorValueTransformer + (Class)transformedValueClass { return [NSData class ]; } - (id )transformedValue:(id )value { UIColor * color = value; const CGFloat * components = CGColorGetComponents (color.CGColor); NSString * colorAsString = [NSString stringWithFormat:@"%f,%f,%f,%f" , components[0 ], components[1 ], components[2 ], components[3 ]]; return [colorAsString dataUsingEncoding:NSUTF8StringEncoding ]; } - (id )reverseTransformedValue:(id )value { NSString * colorAsString = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding ]; NSArray * components = [colorAsString componentsSeparatedByString:@"," ]; CGFloat r = [[components objectAtIndex:0 ] floatValue]; CGFloat g = [[components objectAtIndex:1 ] floatValue]; CGFloat b = [[components objectAtIndex:2 ] floatValue]; CGFloat a = [[components objectAtIndex:3 ] floatValue]; return [UIColor colorWithRed:r green:g blue:b alpha:a]; } + (BOOL )allowsReverseTransformation { return YES ; }@end
2.图片转Data 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @interface ImageToDataValueTransformer : NSValueTransformer@end @implementation ImageToDataValueTransformer + (Class)transformedValueClass { return [NSData class] ; }- (id)transformedValue :(id)value { if (value == nil) { return nil ; } return UIImagePNGRepresentation (value); }- (id)reverseTransformedValue :(id)value { return [UIImage imageWithData:(NSData *)value] ; } + (BOOL)allowsReverseTransformation { return YES ; } @end
3.数组转Data 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @interface ArrayToDataValueTransformer : NSValueTransformer @end @implementation ArrayToDataValueTransformer + (Class)transformedValueClass{ return [NSArray class ]; } - (id )transformedValue:(id )value{ if (value == nil ) { return nil ; } return [NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:nil ]; } - (id )reverseTransformedValue:(id )value{ NSSet *unarchivedSet = [NSSet setWithObjects:[MyClassA class ], [MyClassB class ], nil ]; return [NSKeyedUnarchiver unarchivedObjectOfClasses:unarchivedSet fromData:value error:nil ]; } + (BOOL )allowsReverseTransformation{ return YES ; }@end
注意,当被转换的是自定义类型时,需要这些自定义类型实现NSSecureCoding
协议,重写encodeWithCoder:
和initWithCoder:
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @implementation MyClassA - (void )encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_property1 forKey:@"property1" ]; [aCoder encodeObject:_property2 forKey:@"property2" ]; } - (nullable instancetype )initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { _property1 = [aDecoder decodeObjectForKey:@"property1" ]; _property2 = [aDecoder decodeObjectForKey:@"property2" ]; return self ; } return nil ; } + (BOOL )supportsSecureCoding{ return YES ; }@end
十.性能优化 IO操作通常是比较费性能的,而 Core Data 的底层就是对 sqlite 文件等持久化存储进行读写,大量的读写操作会阻塞线程或引发性能问题,所以要考虑内存、性能、线程、并发等问题。
1.多MOC 1.1.并发 为了缓解主线程的压力,对于一些不涉及到UI更新的数据库操作,通常是放到新开辟的一个或多个线程进行。需要注意的是,Core Data的MO与MOC不是线程安全的,对MO与MOC的操作不会上锁去保证操作的原子性,多个线程共用MOC时可能会出现数据混乱甚至闪退。
错误示范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 - (void )testOptionsInMultiThread { _sameContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType ]; _sameContext.persistentStoreCoordinator = self .persistentStoreCoordinator; NSMutableArray *sectionData = [NSMutableArray array]; for (int i = 0 ; i < 30 ; i++) { NSString *descript = [NSString stringWithFormat:@"this is %d" ,i]; [sectionData addObject:@{@"id" : @(i), @"name" : @(i), @"descript" : descript}]; } dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ for (NSDictionary *params in sectionData) { Section *section = [NSEntityDescription insertNewObjectForEntityForName:@"Section" inManagedObjectContext:_sameContext]; int idf = [params[@"id" ] intValue]; section.id = idf; section.name = idf; section.descript = params[@"descript" ]; if (idf == 10 ) { sleep(1 ); } } [_sameContext save:nil ]; }); dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Section" ]; NSArray *results = [_sameContext executeFetchRequest:fetchRequest error:nil ]; for (Section *section in results) { int i = arc4random(); section.id = i; section.name = i; section.descript = [NSString stringWithFormat:@"this is %d" ,i]; } [_sameContext save:nil ]; }); }
示例中线程A
、线程B
分别执行插入和更新操作。由于是共用一个MOC且是异步操作,线程A
新增的MO可能在线程B
中被提前执行了save
,两种操作混在一起会产生闪退。
苹果推荐的是每个线程使用一个独立的MOC,这样MOC在自己所属的线程中管理自己监听的MO,不受其他MOC的影响,从而避免MOC保存前它监听的MO被其他MOC篡改或提前 save 的情况。
创建多MOC时需要指定它的并发类型以便进行多线程管理,Core Data 提供了两个选择:
Main
:MOC与主队列关联并且依赖应用的event loop
;
Private
:MOC会创建并管理一个私有串行队列。
1 2 3 4 5 6 _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType ]; _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ];
基于队列的多 MOC 需要搭配以下两个API使用:
1 2 [moc performBlock:] [moc performBlockAndWait:]
前者是异步操作,被调用后立刻返回;
后者是同步操作,会堵塞线程直到任务完成才返回。
对于更新UI或其他需要在主线程中执行的操作,推荐使用Main
主队列MOC。
对于一些耗时的任务,推荐使用Private
私有队列MOC+异步block执行。
1.2.同PSC 示例1:多MOC使用同一个PSC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 - (void )initMulMOCWithOnePSC { _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType ]; _mainContext.persistentStoreCoordinator = self .persistentStoreCoordinator; _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.persistentStoreCoordinator = self .persistentStoreCoordinator; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onUpdate:) name:NSManagedObjectContextDidSaveNotification object:_mainContext]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onUpdate:) name:NSManagedObjectContextDidSaveNotification object:_backgroudnContext]; Course *c = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .mainContext]; c.id = 1 ; c.name =@"A1" ; [self .mainContext save:nil ]; [_backgroudnContext performBlock:^{ Course *c2 = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .backgroudnContext]; c2.id = 2 ; c2.name =@"A2" ; [self .backgroudnContext save:nil ]; }]; } - (void )onUpdate:(NSNotification *)notification{ NSLog (@"++++通知所在线程:%@" ,[NSThread currentThread]); NSManagedObjectContext *context = notification.object; if ([context isEqual:_mainContext]) { [_backgroudnContext performBlock:^{ NSLog (@"++++_backgroudnContext performBlock 所在线程:%@" ,[NSThread currentThread]); [_backgroudnContext mergeChangesFromContextDidSaveNotification:notification]; }]; } else if ([context isEqual:_backgroudnContext]){ [_mainContext performBlock:^{ NSLog (@"++++_mainContext performBlock 所在线程:%@" ,[NSThread currentThread]); [_mainContext mergeChangesFromContextDidSaveNotification:notification]; }]; } }
不要在一个线程上创建MO再把它传给另一个线程,可通过MOC使用MO的ID
查询对应的MO;或者监听NSManagedObjectContextDidSaveNotification
通知,在回调里合并来自其他MOC的修改。
这种方案的问题在于,需要在不同的MOC间监听通知,手动同步来自其他MOC的修改,稍显麻烦。
1.3.父MOC MOC都有对应的父存储,通过父存储可以返回代表托管对象的数据,也可以提交修改之后的托管对象。在 iOS5 之前,父存储只能是persistent store coordinator
,而 iOS5 之后父存储可以是另一个MOC了。但无论如何,最终MOC的根源必须是一个PSC
,通过PSC
提供MOM并分发增删改查等请求到不同的持久化存储中。
父MOC
这种模式适用于在子线程处理耗时任务的场景。例如,将主线程
对应的MOC设置为子线程
MOC的父存储,那么在子MOC
中保存对MO的修改时,这些修改会被推送到父MOC
中,最终子线程MOC
的 fetch 与 save 操作都会通过主线程MOC
代为执行。与同PSC
方案相比,此方案里某一个MOC的改动不需要用通知去告知其他MOC同步了,省事不少。
注意:保存对子MOC中数据的修改时,切记先在子MOC中执行 save,再在父MOC中执行 save。
示例2:子MOC使用 parent context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - (void )initMulMocWithParentMoc { _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType ]; _mainContext.persistentStoreCoordinator = self .persistentStoreCoordinator; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onContextWillSave:) name:NSManagedObjectContextWillSaveNotification object:_mainContext]; _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.parentContext = _mainContext; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onContextWillSave:) name:NSManagedObjectContextWillSaveNotification object:_backgroudnContext]; Course *c = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .mainContext]; c.id = 1 ; c.name =@"A1" ; [self .mainContext save:nil ]; [self .backgroudnContext performBlock:^{ Course *c2 = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .backgroudnContext]; c2.id = 2 ; c2.name =@"A2" ; [self .backgroudnContext save:nil ]; [self .mainContext performBlock:^{ [self .mainContext save:nil ]; }]; }]; } - (void )onContextWillSave:(NSNotification *)notification{ NSManagedObjectContext *moc = notification.object; NSSet *insertMO = moc.insertedObjects; if (insertMO.count) { BOOL succeed = [moc obtainPermanentIDsForObjects:insertMO.allObjects error:nil ]; if (!succeed) { NSLog (@"Error occured!" ); } } }
必要时可以使用三MOC,其中:
Private MOC
用于执行耗时操作;
Main MOC
用于与UI协作;
Root MOC
用于在后台保存所有子MOC提交的修改。
示例3:使用三MOC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 - (void )init3MocWithParentMoc { _rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _rootContext.persistentStoreCoordinator = self .persistentStoreCoordinator; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onContextWillSave:) name:NSManagedObjectContextWillSaveNotification object:_rootContext]; _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType ]; _mainContext.parentContext = _rootContext; _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.parentContext = _mainContext; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (onContextWillSave:) name:NSManagedObjectContextWillSaveNotification object:_backgroudnContext]; [self .backgroudnContext performBlock:^{ Course *c2 = [NSEntityDescription insertNewObjectForEntityForName:@"Course" inManagedObjectContext:self .backgroudnContext]; c2.id = 2 ; c2.name =@"A2" ; [self .backgroudnContext save:nil ]; [self .mainContext performBlock:^{ [self .mainContext save:nil ]; [self .rootContext performBlock:^{ [self .rootContext save:nil ]; }]; }]; }]; } - (void )onContextWillSave:(NSNotification *)notification{ NSManagedObjectContext *moc = notification.object; NSSet *insertMO = moc.insertedObjects; if (insertMO.count) { BOOL succeed = [moc obtainPermanentIDsForObjects:insertMO.allObjects error:nil ]; if (!succeed) { NSLog (@"Error occured!" ); } } }
使用多MOC时需要注意:MO在实例化时会被赋予一个临时ID,这个ID在当前MOC范围内是唯一的。但在提交对MOC的修改时,要将临时ID转换成全局ID,所以需要监听MOC即将保存的通知,以转换永久ID。
1.4.通知 MOC会在不同的时机发送不同的通知,注册通知时需要区分MOC,一是因为我们自己会定义不同的MOC,二是系统本身也会使用 Core Data 并发送通知。我们只关心从自己定义的特定MOC中收到的通知,所以在注册通知时需要做区分,可参考上面的👆🏻示例1
。
1 2 3 4 5 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(<#Selector name#>) name:NSManagedObjectContextDidSaveNotification object:<#A managed object context#>];
2.批量操作 2.1.一般流程 先来看一次普通查询或修改对象属性值操作的执行流程:
执行查询时,MOC 将fetchRequest
传递给 PSC;
PSC 将 fetch 请求转换成对应的NSPersistentStoreRequest
,并执行自己的excute
方法,将 fetch 与 MOC 发送给持久化存储(NSPersistentStore);
持久化存储将NSPersistentStoreRequest
转换成 SQL 语句,交给 SQLite 执行;
SQLite 将符合条件的数据返回给持久化存储,持久化存储将其保存在行缓存
(row cache)中;
持久化存储将获取到的数据实例化成托管对象,交给PSC。此时 fetch.returnsObjectsAsFaults 的默认值为YES,所以这些对象暂时还是惰值形态(Fault
)的,其属性值尚未填充,而是暂时被保存在了持久化存储的行缓存
中;
PSC将这些Fault
形态的托管对象以数组的形式返回给MOC;
访问或修改这些托管对象时,MOC会检查它们是否为Fault
形态。如果是,则MOC 向 PSC 发起填充请求;
PSC 向持久化存储请求与当前对象关联的数据;
持久化存储在它的行缓存
中查找并返回数据,交给MOC;
MOC将返回的数据填充到Fault
形态的托管对象中,使其成为完全体的托管对象;
执行 save 时 MOC 发送NSManagedObjectContextWillSaveNotification
通知;
创建一个持久化存储请求(NSSaveChangesRequest),调用PSC的 excute 方法,将请求发送给持久化存储;
持久化存储对比请求中的数据与自己行缓存
中的数据,检测是否有冲突并按照设置的合并策略处理冲突;
持久化存储将NSSaveChangesRequest
转换成 SQL 语句交给 SQLite 执行更新;
持久化存储更新自己的行缓存
;
MOC 发送NSManagedObjectContextDidSaveNotification
通知;
可以看到,整个过程所需的步骤还是挺多的~
2.2.批量操作 前面介绍的增、删、改,都是先从持久化存储中读取数据对象到内存中,再通过 MOC 逐一对这些对象执行增删改,最后执行 save 将数据再次保存到持久化存储中。在涉及到大批量数据的操作时,这种方式效率低、费内存,可能会遇到性能问题。为此,Core Data 提供了批量操作
功能。
批量操作
是直接在持久化存储中操作 MO 对象,不需要先查询 MO 对象,效率高;不需查询 MO 也就无需将它们加载到内存中,节省了内存开销。这个功能需要用到NSBatchxxxRequest
系列类,并且这些类只支持 SQLite 类型的持久化存储,因为它是在持久化存储的 SQL 层面直接操作这些对象。也正是因为发生在 SQL 层面,所以 MOC 不会自动合并这种操作,也不会发送相关通知,需要我们自己通过 MOC 的 merge 静态方法,同步增删改内存中对应的 MO 对象。为此,需要将批量操作的返回值设置成NSManagedObjectID
,即被操作对象的ID,再用这些ID更新 MOC 即可。
需要注意的是,在执行批量操作时,最好是放到一个私有 MOC 中进行。
2.3.批量修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 - (void )batchUpdate{ _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.parentContext = _mainContext; [_backgroudnContext performBlock:^{ NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Course" ]; NSPredicate *predicate= [NSPredicate predicateWithFormat:@"id > 0" ]; [request setPredicate:predicate]; request.propertiesToUpdate = @{@"name" :@"课程S" ,@"url" :@"www.xxx.com" }; request.resultType = NSUpdatedObjectIDsResultType ; NSError *error; NSBatchUpdateResult *batchResult = [self .viewContext executeRequest:request error:&error]; NSArray <NSManagedObjectID *> *updatedObjectIDs = batchResult.result; NSDictionary *updatedDict = @{NSUpdatedObjectsKey : updatedObjectIDs}; [NSManagedObjectContext mergeChangesFromRemoteContextSave:updatedDict intoContexts:@[self .viewContext]]; }]; }
2.4.批量删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 - (void )batchDelete{ _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.parentContext = _mainContext; [_backgroudnContext performBlock:^{ NSFetchRequest *fetch = [Course fetchRequest]; fetch.predicate = [NSPredicate predicateWithFormat:@"id > 0" ]; NSBatchDeleteRequest *delReqest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetch]; delReqest.resultType = NSBatchDeleteResultTypeObjectIDs ; NSError *error; NSBatchDeleteResult *deleteResult = [self .viewContext executeRequest:delReqest error:&error]; NSArray <NSManagedObjectID *> *deletedObjectIDs = deleteResult.result; NSDictionary *deletedDict = @{NSDeletedObjectsKey : deletedObjectIDs}; [NSManagedObjectContext mergeChangesFromRemoteContextSave:deletedDict intoContexts:@[self .viewContext]]; }]; }
2.5.批量插入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (void )batchInser{ _backgroudnContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType ]; _backgroudnContext.parentContext = _mainContext; [_backgroudnContext performBlock:^{ NSArray *sectionsArr = @[@{@"name" :@(001 ),@"id" :@(1 ),@"descript" :@"Section 1" }, @{@"name" :@(002 ),@"id" :@(2 ),@"descript" :@"Section 2" }, @{@"name" :@(003 ),@"id" :@(3 ),@"descript" :@"Section 3" }]; NSBatchInsertRequest *request = [[NSBatchInsertRequest alloc] initWithEntityName:@"Section" objects:sectionsArr]; request.resultType = NSUpdatedObjectIDsResultType ; NSError *error; NSBatchInsertResult *batchResult = [self .viewContext executeRequest:request error:&error]; NSArray <NSManagedObjectID *> *insertedObjectIDs = batchResult.result; NSDictionary *updatedDict = @{NSInsertedObjectsKey : insertedObjectIDs}; [NSManagedObjectContext mergeChangesFromRemoteContextSave:updatedDict intoContexts:@[self .viewContext]]; }]; }
需要指出的是,虽然批量操作比普通增删改操作的效率高很多,但它的代价是放弃了很多细节的处理,比如批量操作不支持校验、不会发送通知、无法处理 Entity 间的关系等。
3.减少内存消耗 3.1.分页查询 查询时最好是限制总量和每页的数量,防止所有结果一次性加载到内存中;
1 2 3 4 5 6 7 8 @property (nonatomic ) NSUInteger fetchLimit;@property (nonatomic ) NSUInteger fetchBatchSize;@property (nonatomic ) NSUInteger fetchOffset;@property (nonatomic ) BOOL returnsObjectsAsFaults;
案例演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (void )loadDataAtPage:(NSUInteger )page{ NSFetchRequest *request = [Course fetchRequest]; NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES ]; request.sortDescriptors = @[descriptor]; request.fetchOffset = (page - 1 ) * kLimiteSize; request.fetchLimit = kLimiteSize; NSError *error; NSArray *matchArr = [self .viewContext executeFetchRequest:request error:&error]; }
3.2.fault对象
查询后不会立刻访问其属性值时,可设置 fetch 请求返回fault
对象,以节省内存;
1 2 3 4 5 6 7 8 - (void )faultMO{ NSFetchRequest *request = [Course fetchRequest]; request.returnsObjectsAsFaults = YES ; NSError *error; NSArray *matchArr = [self .viewContext executeFetchRequest:request error:&error]; }
不再需要的托管对象,让 MOC 调用下面的方法,清除对象的属性值,将其还原成fault
状态。
1 2 - (void) refreshObject:(NSManagedObject *) object mergeChanges:(BOOL) flag;
其中flag
参数为NO时,MOC会丢弃未保存的修改,当前托管对象变成fault
对象;
当flag
为YES时,MOC会从持久化存储或缓存中重新加载MO的属性值,再更新成本地修改的值。
3.3.重置所有MO 调用MOC的reset
方法,可以将MOC中所有的MO都清除。此时原来跟MOC关联的MO都会失效,你需要重置它们的引用,并重新执行 fetch。
3.4.遍历与销毁MO 当遍历大量托管对象时,需要使用autorelease pool
来确保临时MO尽快销毁。
3.5.不查询属性值 执行 Fetch 时,Core Data 默认会查询对象的ID和属性值,填充行缓存,用ID创建fault
对象并返回。如果你确定只想查询托管对象,而不会访问其属性,可设置fetch.includesPropertyValues = NO
,此时 Core Data 只会查询对象的 ID 并返回fault
对象,而不会查询属性值,也不会填充行缓存,这样就又省去了一部分内存开销。
当然,如果includesPropertyValues=NO
而你又访问了fault
对象的属性,那么 Core Data 在空的行缓存中查询不到数据,就会去持久化存储中重新查询并填充到fault
对象中。
如果includesPropertyValues=YES
而resultType
是managedObjectIDResultType
类型,那么还是会查询属性,这会造成不必要的性能开销。因为返回值是 ID 类型,没有属性字段,这些被查询出来的属性根本就没有机会被展示到应用里。
4.BLOBs-二进制大文件 Binary Large Data Objects,指二进制大数据对象,例如图片、音频等。使用Core Data 保存这种对象时,需要选择 SQLite 作为持久化存储。因为其他存储要求将整个对象都加载到内存中,并且写入存储是原子性的,这会导致它们处理 BLOBs 的效率相对不高。
BLOB 通常是 Entity 的属性,例如员工
实体的头像
属性。可以为 BLOB 创建一个单独的照片
实体,在它与员工
实体间设置1对1
的关系
,以替代原员工
实体中的头像
属性。这样做可以充分利用Fault
对象节省内存的特点,查询员工的照片关系
字段时,Core Data 暂时不会将关系属性值(即二进制的照片数据)加载到内存中,只有真正访问或修改此属性时,才填充到fault
对象中。
更好的做法是将 BLOBs 保存到文件系统里,将其 URL 或路径保存在数据库中,需要用到时再通过 URL 或路径加载此文件。
十一.合并冲突 应用中一般会存在多个MOC,它们使用同一份持久化存储,执行不同任务以便优化性能。每个 MOC 都能独立执行 save 保存对托管对象的修改,其他 MOC 需要合并这些修改,以保证各处托管对象状态的一致性。当 MOC 合并对托管对象的修改时,如果另一个 MOC 也修改了同一个属性并且值不相同,就产生了冲突。这时就需要根据 MOC 的合并策略属性来处理冲突:
1 @property (strong) id mergePolicy;
以下是各个策略及其对应的处理:
合并策略
说明
NSErrorMergePolicy
默认的合并策略,产生冲突时返回错误;
NSMergeByPropertyStoreTrumpMergePolicy
只合并产生冲突的属性,用外部修改覆盖当前修改,其他未产生冲突的属性保持不变;
NSMergeByPropertyObjectTrumpMergePolicy
只合并产生冲突的属性,用当前修改覆盖外部修改,其他未产生冲突的属性保持不变;
NSOverwriteMergePolicy
将当前MOC的托管对象写入持久化存储;
NSRollbackMergePolicy
丢弃冲突中所有的修改,保持持久化存储中的版本不变;
十二.数据迁移 MOM
用来描述持久化存储中数据的结构,Core Data 中只能使用 MOM 打开持久化存储,改变 MOM 中的任何一部分都会导致它与之前版本的存储产生冲突。例如在.xcdatamodel
中增加新实体,修改实体的名字,增删属性等,那么老版本的持久化存储就不能使用了。需要通过数据迁移
,将老版本存储中的数据迁移到新版本中。为了让 Core Data 知道如何迁移数据,有时我们需要主动提供一些信息,例如,创建一个mapping model
映射文件。对于简单的变化,可以使用轻量级迁移
功能。
使用轻量级迁移功能时,Core Data 会根据前后MOM
的不同,自动推断出一个mapping model
,无需我们自己创建。轻量级迁移在我们项目的初期可能会非常有用,因为这一时期我们可能会经常性的改动模型文件,而又不希望频繁重新生成测试数据。当然,这些小的
改动只能是以下这些操作:
增、删、重命名属性;
给属性设置默认值;
不可选属性变可选;
可选属性变不可选并设置默认值;
增、删、重命关系;
修改关系成对1或对多、排序或不排序;
增、删、重命名实体;
创建新的父实体或子实体;
在实体继承树上将属性移到别的实体中;
将实体移出继承树;
1.建新model 做数据迁移时既需要新版本model文件,也需要新版本model文件,新版model文件设置方法如下:
Xcode 中选中.xcdatamodeld
文件;
Editor
-> add model version
;
设置新版本文件名和它基于哪个版本;
创建新模型文件;
设置新xcdatamodel
作为当前版本;
这样,就可以在新版本的模型文件中修改 Entity 了。
2.修改Entity 重命名属性时,需要在设置面板中将目标版本属性的Renaming ID
字段设置成原属性的名字。例如可以在版本2中将属性name
重命名成name2
,设置name2
的Renaming ID
为name
;然后在版本3中可以继续将name
重命名成name3
,同样将name3
的Renaming ID
设置为name
。这样 Core Data 就能从版本1推断出到版本2,或者从版本1到版本3的mapping model
,以此完成数据迁移。
重命名实体或关系时,也是同样的步骤,设置目标实体/关系的Renaming ID
字段。
3.设置options 修改实体后,还要在添加持久化存储时将options
字典中自动迁移和自动推断的值设置为YES
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (NSPersistentStoreCoordinator * )persistentStoreCoordinator { if (_persistentStoreCoordinator != nil ) { return _persistentStoreCoordinator; } _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSURL * storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent :@"ASDF.sqlite" ]; NSError * error = nil ; NSDictionary * options = @{NSMigratePersistentStoresAutomaticallyOption : @YES , NSInferMappingModelAutomaticallyOption : @YES }; if (! [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:& error]) { } return _persistentStoreCoordinator; }
这样,轻量级迁移就完成了,用户升级版本后启动时就不会因数据版本问题闪退了。
相关参考:
#©Fault #©性能