Core Data编程指南 1-6: 创建和修改数据对象
Core Data中,我们将NSManagedObject
或者其子类的对象称为数据对象,代表着数据表中的数据记录。NSManagedObject
是数据对象的基类,提供了一系列基本操作。通常情况下,我们可以继承这个类来添加自定义的数据校验方法、非标准类型的数据字段、特殊的依赖关系或者各种中间值等。如果不需要自定义这些操作,一般不需要继承NSManagedObject
类。
As discussed previously, managed objects are instances of the NSManagedObject
class, or of a subclass of NSManagedObject
, that represent instances of an entity. NSManagedObject
is a generic class that implements all the basic behavior required of a managed object. You can create custom subclasses of NSManagedObject
, although this is often not required. If you do not need any custom logic for a given entity, you do not need to create a custom class for that entity. You implement a custom class to, for example, provide custom accessor or validation methods, use nonstandard attributes, specify dependent keys, calculate derived values, and implement any other custom logic.
创建自定义的数据对象子类
Creating Custom Managed Object Subclasses
在Objective-C中,我们可以在子类的头文件中声明自定义属性,但不需要声明对应的实体变量。
In an Objective-C managed object subclass, you can declare the properties for modeled attributes in the interface file, but you don’t declare instance variables:
@interface MyManagedObject : NSManagedObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSDate *date;
@end
这些属性被声明成nonatomic
和strong
类型。为了保证运行效率,即使子类实现了NSCopying
protocol
, Core Data在任何时候都不会直接拷贝对象值。
Notice that the properties are declared as nonatomic
and strong
. For performance reasons, Core Data typically does not copy object values, even if the value class adopts the NSCopying
protocol
.
在Objective-C的实现文件中,这些属性被标志为dynamic
In the Objective-C implementation file, you specify the properties as dynamic
:
@implementation MyManagedObject
@dynamic title;
@dynamic date;
@end
在 Swift中,我们可以使用@NSManaged
标签来声明这些属性。
In Swift, you declare the properties using the @NSManaged
keyword:
class MyManagedObject: NSManagedObject {
@NSManaged var title: String?
@NSManaged var date: NSDate?
}
Core Data会根据数据层模型定义的数据结构,为这些属性生成public的get、set方法。一般来说,你并不需要自定义这些访问方法。
Core Data dynamically generates efficient public and primitive get and set attribute accessor methods and relationship accessor methods for properties that are defined in the entity of a managed object’s corresponding managed object model. Therefore, you typically don’t need to write custom accessor methods for modeled properties.
方法重载的原则
Guidelines for Overriding Methods
为了适配Core Data的基础架构,NSManagedObject
重载了很多NSObject
的方法。下面这些NSManagedObject
方法是和Core Data架构紧密相关的,因此尽量不要重载它们。
NSManagedObject
itself customizes many features of NSObject
so that managed objects can be properly integrated into the Core Data infrastructure. Core Data relies on NSManagedObject
’s implementation of the following methods, which you should therefore not override:
primitiveValueForKey:
setPrimitiveValue:forKey:
isEqual:
hash
superclass
class
self
zone
isProxy
isKindOfClass:
isMemberOfClass:
conformsToProtocol:
respondsToSelector:
managedObjectContext
entity
objectID
isInserted
isUpdated
isDeleted
isFault
同样也不推荐重载 initWithEntity:insertIntoManagedObjectContext:
和description
方法。如果在调试的时候description
方法输出错误信息,可能会导致一些不可预测的结果。任何和键值对相关的方法一般也不允许重载,例如valueForKey:
and setValue:forKeyPath:
。
You are discouraged from overriding initWithEntity:insertIntoManagedObjectContext:
and description
. If description fires a fault during a debugging operation, the results may be unpredictable. You should typically not override the key-value coding methods such as valueForKey:
and setValue:forKeyPath:
.
此外,重载awakeFromInsert
, awakeFromFetch
方法,或者数据校验方法 validateForUpdate:
的时候,切记要调用父类的原始方法。重载任何方法都需要特别注意运行时的效率问题。
In addition, before overriding awakeFromInsert
, awakeFromFetch
, and validation methods such as validateForUpdate:
, invoke their superclass implementation. Be careful when overriding accessor methods because you could negatively impact performance.
定义属性和外部数据存储方式
Defining Properties and Data Storage
从某种角度上看,NSManagedObject
类似一个dictionary结构,作为一个基本容器,它为NSEntityDescription
对象定义的各种不同类型的属性提供了存储空间。NSManagedObject
支持一些基本的字段类型,包括字符串、日期,时区和数字等(参见 NSAttributeDescription
)。因此除非是一些非标准的数据字段,我们通常不需要在子类中自定义特殊的变量。一个比较特殊的例子是大块的二进制数据,有些时候我们需要在子类中处理一些和运行效率相关的功能,参见 Binary Large Data Objects (BLOBs)。
In some respects, a managed object acts like a dictionary—it is a generic container object that efficiently provides storage for the properties defined by its associated NSEntityDescription
object. NSManagedObject
supports a range of common types for attribute values, including string, date, and number (see NSAttributeDescription
for full details). Therefore, you typically do not need to define instance variables in subclasses. However, if you need to implement nonstandard attributes or preserve time zones, you may need to do so. In addition, there are some performance considerations that can be mitigated in a subclass if you use large binary data objects—see Binary Large Data Objects (BLOBs).
非标准字段
Using Nonstandard Attributes
NSManagedObject
将属性值以对象的形式存储在内部。一般来说,Core Data在使用内部的存储结构时,比自定义的外部变量更有效率。
By default, NSManagedObject
stores its properties as objects in an internal structure, and in general Core Data is more efficient working with storage under its own control than with using custom instance variables.
有一些时候,我们需要处理一些非标准的数据结构,比如颜色值或者C语音的结构类型。例如在一个图形应用程序中,一个矩形对象会有颜色和形状两个属性,分别是NSColor
和 NSRect
这两种非标准类型。这时,我们需要扩展NSManagedObject
。
Sometimes you need to use types that are not supported directly, such as colors and C structures. For example, in a graphics application you might want to define a Rectangle entity that has attributes color and bounds, which are instances of NSColor
and NSRect
structures respectively. This situation requires you to create a subclass of NSManagedObject
.
日期、时间和时区
Dates, Times, and Preserving Time Zones
NSManagedObject
支持标准的日期(NSDate
)和时间段( NSTimeInterval
)类型。日期属性存储到数据库中时会被变换成GMT标准时间,方便进行统一的查询、对比操作。但其中的时区信息会丢失。如果需要保存时区信息,我们需要定制化自己的NSManagedObject
子类。
NSManagedObject
represents date attributes with NSDate
objects, and stores times internally as an NSTimeInterval
value that is based on GMT. Time zones are not explicitly stored—always represent a Core Data date attribute in GMT, so that searches are normalized in the database. If you need to preserve the time zone information, store a time zone attribute in your model, which may require you to create a subclass of NSManagedObject
.
子类的初始化和销毁
Customizing Initialization and Deallocation
Core Data会管理数据对象的整个生命周期。和Objective-C对象不同,Core Data的数据对象除了初始化和销毁过程外,还可以通过faulting和undo操作进行回溯。
Core Data controls the life cycle of managed objects. With faulting and undo, you cannot make the same assumptions about the life cycle of a managed object that you do with a standard Objective-C object—managed objects can be instantiated, destroyed, and resurrected by the framework as it requires.
一个数据对象创建的时候会被赋予缺省的初始值。也有一些特殊的初始化,用动态的初始值初始化数据对象(比如当前日期和时间)。
When a managed object is created, it is initialized with the default values given for its entity in the managed object model. In many cases the default values set in the model are sufficient. Sometimes, however, you may wish to perform additional initialization—perhaps using dynamic values (such as the current date and time) that cannot be represented in the model.
在传统的Objective-C类中,我们通过重载构造函数(通常是init
方法)来定制化初始化过程,但在NSManagedObject
的子类中,需要用到其他三种不同的方法 -- initWithEntity:insertIntoManagedObjectContext:
, awakeFromInsert
, 或者awakeFromFetch
。而init
方法尽量不要去重载的。也不推荐重载initWithEntity:insertIntoManagedObjectContext:
方法,这个函数中的一些状态迁移可能会影响undo和redo功能。另两个函数awakeFromInsert
, 或者awakeFromFetch
则适用在不同场合:
In a typical Objective-C class, you usually override the designated initializer (often the init
method). In a subclass of NSManagedObject
, there are three different ways you can customize initialization—by overriding initWithEntity:insertIntoManagedObjectContext:
, awakeFromInsert
, or awakeFromFetch
. Do not override init
. It is also recommended that you do not override initWithEntity:insertIntoManagedObjectContext:
, as state changes made in this method may not be properly integrated with undo and redo. The two other methods, awakeFromInsert
and awakeFromFetch
, allow you to differentiate between two different situations:
-
awakeFromInsert
只在对象被创建的时候调用awakeFromInsert
在initWithEntity:insertIntoManagedObjectContext:
或insertNewObjectForEntityForName:inManagedObjectContext:
之后被调用。我们可以在这个函数中做一些特殊的初始化。例如在下面这个例子中,我们用对象的创建日期来初始化相关属性: -
awakeFromInsert
is invoked only once in the lifetime of an object—when it is first createdawakeFromInsert
is invoked immediately after you invokeinitWithEntity:insertIntoManagedObjectContext:
orinsertNewObjectForEntityForName:inManagedObjectContext:
. You can useawakeFromInsert
to initialize special default property values, such as the creation date of an object, as illustrated in the following example.
OBJECTIVE-C
-----------
- (void)awakeFromInsert
{
[super awakeFromInsert];
[self setCreationDate:[NSDate date]];
}
SWIFT
-----
override func awakeFromInsert() {
super.awakeFromInsert()
creationDate = NSDate()
}
-
对象从外部存储器中装载到内存时(比如查询操作)会调用
awakeFromFetch
方法。
你可以重载awakeFromFetch
去初始化一些中间值,或者其他缓存。在awakeFromFetch
中,一些跟修改属性值相关的衍生操作是被关闭的,你可以调用对象的set方法去设置属性值,但这些改动不会被校验,也不回被传播到其他对象中去。因此不应该在awakeFromFetch
中修改任何关系,否则会引起相关数据不一致。awakeFromFetch
的重载效果也可以通过重载awakeFromInsert
,并实现一些和运行线程相关的方法,例如performSelector:withObject:afterDelay:
来实现。 -
awakeFromFetch
is invoked when an object is reinitialized from a persistent store (during a fetch).
you can overrideawakeFromFetch
to, for example, establish transient values and other caches. Change processing is explicitly disabled inawakeFromFetch
so that you can conveniently use public set accessor methods without dirtying the object or its context. This disabling of change processing does mean, however, that you should not manipulate relationships because changes will not be properly propagated to the destination object or objects. Instead of overridingawakeFromFetch
, you can overrideawakeFromInsert
or employ any of the run loop-related methods such asperformSelector:withObject:afterDelay:
.
当我们需要在对象销毁的时候清除一些中间变量的时候,需要重载didTurnIntoFault
方法,而不是 dealloc
方法。Core Data在一个数据对象进入fault状态等待被销毁的时候会自动调用didTurnIntoFault
方法。我们也可以将一个数据对象主动设置成fault状态去减少当前的内存开销(参见 Reducing Memory Overhead)。因此在didTurnIntoFault
方法中进行正确的清理操作是非常重要的。
Avoid overriding dealloc
to clear transient properties and other variables. Instead, override didTurnIntoFault
. didTurnIntoFault
is invoked automatically by Core Data when an object is turned into a fault and immediately prior to actual deallocation. You might turn a managed object into a fault specifically to reduce memory overhead (see Reducing Memory Overhead), so it is important to ensure that you properly perform cleanup operations in didTurnIntoFault
.
使用Xcode来自动生成数据子类
Xcode Generated Subclasses
Xcode 8, iOS 10和macOS 10.12之后的版本,在编辑数据层模型的过程中就可以自动生成NSManagedObject
的子类或者extensions/categories。
Starting with Xcode 8, iOS 10, and macOS 10.12, Xcode can automatically generate NSManagedObject
subclasses or extensions/categories from the Core Data Model.
要使用这个功能,先要正确设置数据层模型。
To enable this feature in an existing project, first ensure that the data model is configured correctly:
-
选择数据层模型文件,打开编辑器。
Select the Core Data Model file, and open the File inspector. -
确认工具的版本大于Xcode 8.0
Confirm that the Tools Version is set to Xcode 8.0 or later. -
确认代码生产工具被设置成你正在使用的编程语言。
Confirm that the Code Generation is set to the language you are currently using.
然后你可以开始配置数据表。
After the data model is configured, you can then configure each entity:
-
选择你希望配置的数据表。
Select the entity you want to configure. -
属性面板中会显示数据表配置页面。
Open the Data Model inspector. -
选择是否要生成子类,或是Category/Extension。
Set the code generator to either None, Class Definition, or Category/Extension.
当配置完成后,Xcode会自动生成我们需要的子类或者categories/extensions。
After the data model is configured, Xcode regenerates the subclasses or categories/extensions whenever the related entity has changed in the data model.
备注 NOTE
自动生成的代码并不会包含在工程中,而是作为编译流程的临时文件。我们可以在编译目录中找到这些文件。这些文件会被反复生成,手动修改这些文件不会起到任何作用。
The generated source code is not included in your project and is intended to be a part of the build process. You will not see the files in your project’s source list but the files can be reviewed in the build directory. These files can be regenerated often so there is no value in editing them manually.
我们可以在单独的代码文件中通过category(在Objective-C中)或者extension (在Swift中),向生成的子类添加各种辅助功能和事务逻辑。
If you wish to add additional convenience methods or business logic to your NSManagedObject
subclasses, you can create a category (in Objective-C) or an extension (in Swift) and place the additional logic there.