这是苹果官方文档 Core Data Programming Guide 的渣翻译。
正如之前讨论的,managed object是累NSManagedObject的实例,或者NSManagedObject的子类的实例,它代表了一个Entity的实例。NSManagedObject是一个的、实现了所有managed object基本需求行为的类。你可以创建NSManagedObject的自定义子类,虽然这并不是常需要的。对于一个Entity,如果你不需要任何自定义的逻辑,你不必要为这个Entity创建一个自定义类。在某些时候,你可能需要实现一个自定义类,例如,为了提供一个自定义访问或者验证方法、使用不标准的字段、指定依赖的键、计算结果值、实现一些其他的自定义逻辑。
创建自定义Managed Object子类
在一个Objective-C managed object子类中,你可以在声明文件中声明模型化字段对应的属性,但是你不能声明实例变量。
@interface MyManagedObject : NSManagedObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSDate *date;
@end
注意那些属性被声明为nonatomic何strong。为了性能,Core Data不会复制对象值,甚至虽然这个值的类遵循了NSCopying protocal。
在Objective-C实现文件中,你需要修饰属性为dynamic:
@implementation MyManagedObject
@dynamic title;
@dynamic date;
@end
在Swift中,你需要声明这些属性使用了@NSManaged关键字:
class MyManagedObject: NSManagedObject {
@NSManaged var title: String?
@NSManaged var date: NSDate?
}
Core Data会根据这个managed object的Entity对应的数据模型,动态生成每个属性的高效的公共、私有的gett和setter访问方法、关系访问方法。
覆写方法指南
NSManagedObject它本身自定义了许多NSObject的特性,所以managed object可以很好地集成到Core Data的架构中。Core Data依赖于以下这些NSManagedObject实现的方法,你可能会进行覆写:
primitiveValueForKey:
setPrimitiveValue:forKey:
isEqual:
hash
superclass
class
self
zone
isProxy
isKindOfClass:
isMemberOfClass:
conformsToProtocol:
respondsToSelector:
managedObjectContext
entity
objectID
isInserted
isUpdated
isDeleted
isFault
不提倡覆写 initWithEntity:insertIntoManagedObjectContext:和description方法。如果在debug过程中description被置为fault,可能会导致不可预知的结果。你还应该不要覆写那些KVC方法,例如valueForKey: 和 setValue:forKeyPath:。
另外,在覆写awakeFromInsert、awakeFromFetch和验证方法例如validateForUpdate:之前,先调用父类的实现方法。对于覆写访问方法要十分小心,因为你可能会令性能受到不好的影响。
定义属性和数据存储
在某些方面,一个managed object操作起来就像一个字典——它是一个常见的容器对象,并且提供了NSEntityDescription对象绑定的属性的高效存储功能。NSManagedObject支持特定范围内的一些公共类型的字段值,包括string、date和number(更多细节请看NSAttributeDescription)。因此,你不需要在子类中定义实例变量。然而,如果你需要实现非标准的字段或者时区,你可能就需要。另外,如果你使用大型的二进制数据对象,可能会存在性能降低的问题——参考Binary Large Data Objects (BLOBs)。
使用非标准字段
在默认情况下,NSManagedObject在内部结构中把属性作为对象存储,通常来讲使用Core Data自己控制的存储方式比自定义实例变量要高效。
有些时候你可能需要使用一些不能直接支持的类型,例如color和C结构体。例如,在一个画图应用中你可能想要定义一个矩形Entity,这个Entity拥有一个color字段和一个bounds字段,这里color是NSColor的实例,bounds是NSRect的结构体。在这种情况下,你需要创建一个NSManagedObject的子类。
日期、时间和时区
NSManagedObject使用NSDate对象表示日期字段,内部使用基于格林尼治时间(GMT)的NSTimeInterval值存储时间值。不同时区的时间不是精确区分存储的——一般你应该使用一个基于GMT的Core Data日期来表示,这样在数据库内搜索才能统一标准化。如果你需要根据实际时区操作数据,你需要在数据模型中存储一个时区字段,这样你就需要创建一个NSManagedObject子类。
自定义初始化和释放
Core Data控制着managed object的生命周期。你不能用一个标准的Objective-C对象的理解,来假设一个managed object的生命周期——managed object会被框架在需要的时候实例化、销毁和唤醒。
当一个managed object被创建,会使用它所属的模型对象中的Entity的默认值来初始化。在许多情况下,在数据模型中配置的默认值是齐全的。然而,你可能希望做一些额外的初始化操作——可能使用dynamic值(例如现在的日期和时间)不能在模型中表现出来。
在一个标准的Objective-C类中,你通常覆写指定的初始化器(一般是init方法)。在一个NSManagedObject子类中,有3个不同的方法你可以用来自定义初始化——覆写initWithEntity:insertIntoManagedObjectContext:、awakeFromInsert 或 awakeFromFetch。不要覆写init。不提倡你覆写initWithEntity:insertIntoManagedObjectContext:,因为在这个方法中的状态改变可能不能很好地集成撤销和重做。另外两个方法, awakeFromInsert 和 awakeFromFetch,在以下两种不同的情况中可以区分开来:
- awakeFromInsert在一个对象声明周期内只会被调用一次——第一次创建的时候。
awakeFromInsert在你调用initWithEntity:insertIntoManagedObjectContext:或者insertNewObjectForEntityForName:inManagedObjectContext:之后会被立刻调用。你可以使用awakeFromInsert去初始化特殊的默认属性值,例如一个对象的创建日期,下面的例子会阐述:
OBJECTIVE-C
- (void)awakeFromInsert
{
[super awakeFromInsert];
[self setCreationDate:[NSDate date]];
}
SWIFT
override func awakeFromInsert() {
super.awakeFromInsert()
creationDate = NSDate()
}
- awakeFromFetch在一个对象从持久化存储中重新初始化(在查询操作)的时候调用。
例如,你可能覆写awakeFromFetch用以创建临时值和其他缓存。修改处理在此过程不可用,所以你可以很方便地使用公共的setter访问方法,而不用造成MOC中对象数据的混乱。然而,修改处理不可用表示,你不应该篡改关系,不然修改就不能正确地传递到目标对象上。为了替代覆写awakeFromFetch,你可以覆写awakeFromInsert或采用一些run loop相关的方法,例如performSelector:withObject:afterDelay:。
为了避免dealloc清除掉临时属性和其他变量,当一个对象转变为fault但是并未完全释放之前,覆写的didTurnIntoFault. didTurnIntoFault会被Core Data自动调用。你可以提前转变一个对象为fault,以降低内存消耗(详见Reducing Memory
Overhead),所以确保你在didTurnIntoFault中执行了清理操作是非常重要的。