Core data

转载 2015年11月20日 20:56:45

Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂。Core Data 相关的类比较多,初学者往往不太容易弄懂。计划用三个教程来讲解这一部分:

框架详解:讲解  Core data 框架,运作过程,设计的类;
Core data应用程序示例:通过生成一个使用 Core data 的应用程序来讲解如何 在  XCode 4 中使用 Core data。
手动创建Core data示例:不利用框架自动生成代码,完全自己编写所有的 Core data 相关代码的命令行应用程序来深入讲解 Core data的使用。

本文为第一部份:框架详解

一,概观
下面先给出一张类关系图,让我们对它有个总体的认识。



在上图中,我们可以看到有五个相关模块:
1, Managed Object Model
Managed Object Model 是描述应用程序的数据模型,这个模型包含实体(Entity),特性(Property),读取请求(Fetch Request)等。(下文都使用英文术语。)

2,Managed Object Context
Managed Object Context 参与对数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。

3,Persistent Store Coordinator
Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。

4,Managed Object
Managed Object 数据对象,与 Managed Object Context 相关联。

5,Controller
图中绿色的 Array Controller, Object Controller, Tree Controller 这些控制器,一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据。

这写模块是怎样运作的呢?

1,应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为 xcdatamodeld)读取。
2,然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,前者对用户透明地调用后者对数据文件进行读写。
3,NSPersistentStoreCoordinator 负责从数据文件(xml, sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件。
4,NSManagedObjectContext 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的。
5,Array Controller, Object Controller, Tree Controller 这些控制器一般与 NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象。

二, Model class

模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。

主要的 Model class 有:

Model Classes
Managed Object Model NSManagedObjectModel 数据模型
Entity NSEntityDescription 抽象数据类型,相当于数据库中的表
Property NSPropertyDescription Entity 特性,相当于数据库表中的一列
  > Attribute NSAttributeDescription 基本数值型属性(如Int16, BOOL, Date等类型的属性)
  > Relationship NSRelationshipDescription 属性之间的关系
  > Fetched Property NSFetchedPropertyDescription 查询属性(相当于数据库中的查询语句)


1)Entity - NSEntityDescription
Entity 相当于数据库中的一个表,它描述一种抽象数据类型,其对应的类为 NSManagedObject 或其子类。

NSEntityDescription 常用方法:
+insertNewObjectForEntityForName:inManagedObjectContext: 工厂方法,根据给定的 Entity 描述,生成相应的 NSManagedObject 对象,并插入 ManagedObjectContext 中。
-managedObjectClassName 返回映射到 Entity 的 NSManagedObject 类名
-attributesByName 以名字为 key, 返回 Entity 中对应的 Attributes
-relationshipsByName 以名字为 key, 返回 Entity 中对应的 Relationships

2)Property - NSPropertyDescription
Property 为 Entity 的特性,它相当于数据库表中的一列,或者 XML 文件中的 value-key 对中的 key。它可以描述实体数据(Attribute),Entity之间的关系(RelationShip),或查询属性(Fetched Property)。

 > Attribute - NSAttributeDescription
Attribute 存储基本数据,如 NSString, NSNumber or NSDate 等。它可以有默认值,也可以使用正则表达式或其他条件对其值进行限定。一个属性可以是 optional 的。
 
 > Relationship - NSRelationshipDescription 
Relationship 描述 Entity,Property 之间的关系,可以是一对一,也可以是一对多的关系。 

 > Fetched Property - NSFetchedPropertyDescription
Fetched Property 根据查询谓词返回指定 Entity 的符合条件的数据对象。

上面说的比较抽象,举个例子来说,见图:



我们有一个 CocoaDataDemo.xcdatamodeld 模型文件,应用程序根据它生成一个 NSManagedObjectModel 对象,这个模型有三个 Entity,每个 Entity 又可包含 Attribute Relationship, Feteched Property 三种类型的 Property。在本例中, Author Entity 包含两个Attribute : name 和 email,它们对于的运行时类均为 NSManagedObject;还包含一个与 Post 的 Relationship;没有设置  Feteched Property。

我们通常使用 KVC 机制来访问 Property。下面来看代码:

  1. NSManagedObjectContext * context = [[NSApp delegate] managedObjectContext];  
  2. NSManagedObject        * author  = nil;  
  3.       
  4. author = [NSEntityDescription insertNewObjectForEntityForName: @"Author" inManagedObjectContext: context];  
  5. [author setValue: @"nemo@pixar.com" forKey: @"email"];  
  6.   
  7. NSLog (@"The Author's email is: %@", [author valueForKey:@"email"]);  

在上面代码中,我们先取得 NSManagedObjectContext, 然后调用 NSEntityDescription 的方法,以 Author 为实体模型,生成对应的 NSManagedObject 对象,插入 NSManagedObjectContext 中,然后给这个对象设置特性 email 的值。

三,运行时类与对象
> Managed Object - NSManagedObject
Managed Object 表示数据文件中的一条记录,每一个 Managed Object 在内存中对应 Entity 的一个数据表示。Managed Object 的成员为 Entity 的 Property 所描述。
比如在上面的代码,author 这个 NSManagedObject,对应名为 Author 的 Entity。

每一个 Managed Object 都有一个全局 ID(类型为:NSManagedObjectID)。Managed Object 会附加到一个 Managed Object Context,我们可以通过这个全局 ID 在 Managed Object Context 查询对应的 Managed Object。

NSManagedObject 常用方法
-entity 获取其 Entity
-objectID 获取其 Managed Object ID
-valueForKey: 获取指定 Property 的值
-setValue: forKey: 设定指定 Property 的值

> Managed Object Context - NSManagedObjectContext
Managed Object Context 的作用相当重要,对数据对象进行的操作都与它有关。当创建一个数据对象并插入 Managed Object Context 中,Managed Object Context 就开始跟踪这个数据对象的一切变动,并在合适的时候提供对 undo/redo 的支持,或调用 Persistent Store Coordinato 将变化保存到数据文件中去。

通常我们将 controller 类(如:NSArrayController,NSTreeController)或其子类与 Managed Object Context 绑定,这样就方便我们动态地生成,获取数据对象等。

NSManagedObjectContext 常用方法
-save: 将数据对象保存到数据文件
-objectWithID: 查询指定 Managed Object ID 的数据对象
-deleteObject: 将一个数据对象标记为删除,但是要等到 Context 提交更改时才真正删除数据对象
-undo 回滚最后一步操作,这是都 undo/redo 的支持
-lock 加锁,常用于多线程以及创建事务。同类接口还有:-unlock and -tryLock
-rollback 还原数据文件内容
-reset 清除缓存的 Managed Objects。只应当在添加或删除 Persistent Stores 时使用
-undoManager 返回当前 Context 所使用的 NSUndoManager
-assignObject: toPersistantStore: 由于 Context 可以管理从不同数据文件而来的数据对象,
这个接口的作用就是指定数据对象的存储数据文件(通过指定 PersistantStore 实现)
-executeFetchRequest: error: 执行 Fetch Request 并返回所有匹配的数据对象

> Persistent Store Coordinator - NSPersistentStoreCoordinator
使用 Core Data document 类型的应用程序,通常会从磁盘上的数据文中中读取或存储数据,这写底层的读写就由 Persistent Store Coordinator 来处理。一般我们无需与它直接打交道来读写文件,Managed Object Context 在背后已经为我们调用 Persistent Store Coordinator 做了这部分工作。

NSPersistentStoreCoordinator 常用方法
-addPersistentStoreForURL:configuration:URL:options:error: 装载数据存储,对应的卸载数据存储的接口为 -removePersistentStore:error:
-migratePersistentStore:toURL:options:withType:error: 迁移数据存储,效果与 "save as"相似,但是操作成功后,
迁移前的数据存储不可再使用
-managedObjectIDForURIRepresentation: 返回给定 URL所指示的数据存储的 object id,如果找不到匹配的数据存储则返回 nil
-persistentStoreForURL: 返回指定路径的 Persistent Store
-URLForPersistentStore: 返回指定 Persistent Store 的存储路径

> Persistent Document - NSPersistentDocument
NSPersistentDocument 是 NSDocument 的子类。 multi-document Core Data 应用程序使用它来简化对 Core Data 的操作。通常使用 NSPersistentDocument 的默认实现就足够了,它从 Info.plist 中读取 Document types 信息来决定数据的存储格式(xml,sqlite, binary)。

NSPersistentDocument 常用方法
-managedObjectContext 返回文档的 Managed Object Context,在多文档应用程序中,每个文档都有自己的 Context。
-managedObjectModel 返回文档的 Managed Object Model

四,Fetch Requests
Fetch Requests 相当于一个查询语句,你必须指定要查询的 Entity。我们通过 Fetch Requests 向 Managed Object Context 查询符合条件的数据对象,以 NSArray 形式返回查询结果,如果我们没有设置任何查询条件,则返回该 Entity 的所有数据对象。我们可以使用谓词来设置查询条件,通常会将常用的 Fetch Requests 保存到 dictionary 以重复利用。

示例:

  1. NSManagedObjectContext * context  = [[NSApp delegate] managedObjectContext];  
  2. NSManagedObjectModel   * model    = [[NSApp delegate] managedObjectModel];  
  3. NSDictionary           * entities = [model entitiesByName];  
  4. NSEntityDescription    * entity   = [entities valueForKey:@"Post"];  
  5.   
  6. NSPredicate * predicate;  
  7. predicate = [NSPredicate predicateWithFormat:@"creationDate > %@", date];  
  8.                            
  9. NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey:@"title"];  
  10. NSArray * sortDescriptors = [NSArray arrayWithObject: sort];  
  11.   
  12. NSFetchRequest * fetch = [[NSFetchRequest alloc] init];  
  13. [fetch setEntity: entity];  
  14. [fetch setPredicate: predicate];  
  15. [fetch setSortDescriptors: sortDescriptors];  
  16.   
  17. NSArray * results = [context executeFetchRequest:fetch error:nil];  
  18. [sort release];  
  19. [fetch release];  

在上面代码中,我们查询在指定日期之后创建的 post,并将查询结果按照 title 排序返回。

NSFetchRequest 常用方法
-setEntity: 设置你要查询的数据对象的类型(Entity)
-setPredicate: 设置查询条件
-setFetchLimit: 设置最大查询对象数目
-setSortDescriptors: 设置查询结果的排序方法
-setAffectedStores: 设置可以在哪些数据存储中查询

参考资料:
Core Data Reference API listing for the Core Data classes
http://developer.apple.com/documentation/Cocoa/Reference/CoreData_ObjC/index.html

NSPredicate Reference API listing for NSPredicate
http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSPredicate.html



前面详细讲解了 Core Data 的框架以及设计的类,下面我们来讲解一个完全手动编写代码使用这些类的示例,这个例子来自苹果官方示例。在这个例子里面,我们打算做这样一件事情:记录程序运行记录(时间与 process id),并保存到xml文件中。我们使用 Core Data 来做这个事情。

示例代码下载:点击这里


一,建立一个新的 Mac command-line tool application 工程,命名为 CoreDataTutorial。为支持垃圾主动回收机制,点击项目名称,在右边的 Build Setting 中查找 garbage 关键字,将找到的 Objective-C Garbage Collection 设置为 Required [-fobj-gc-only]。并将  main.m 中 的 main() 方法修改为如下:

  1. int main (int argc, const char * argv[])  
  2. {  
  3.     NSLog(@" === Core Data Tutorial ===");  
  4.   
  5.     // Enable GC  
  6.     //  
  7.     objc_startCollectorThread();  
  8.       
  9.     return 0;  
  10. }  

二,创建并设置模型类

在 main() 之前添加如下方法:

  1. NSManagedObjectModel *managedObjectModel()  
  2. {  
  3.     static NSManagedObjectModel *moModel = nil;  
  4.   
  5.     if (moModel != nil) {  
  6.         return moModel;  
  7.     }  
  8.       
  9.     moModel = [[NSManagedObjectModel alloc] init];  
  10.       
  11.     // Create the entity  
  12.     //  
  13.     NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];  
  14.     [runEntity setName:@"Run"];  
  15.     [runEntity setManagedObjectClassName:@"Run"];  
  16.       
  17.     [moModel setEntities:[NSArray arrayWithObject:runEntity]];  
  18.       
  19.     // Add the Attributes  
  20.     //  
  21.     NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];  
  22.     [dateAttribute setName:@"date"];  
  23.     [dateAttribute setAttributeType:NSDateAttributeType];  
  24.     [dateAttribute setOptional:NO];  
  25.       
  26.     NSAttributeDescription *idAttribute = [[NSAttributeDescription alloc] init];  
  27.     [idAttribute setName:@"processID"];  
  28.     [idAttribute setAttributeType:NSInteger32AttributeType];  
  29.     [idAttribute setOptional:NO];  
  30.     [idAttribute setDefaultValue:[NSNumber numberWithInteger:-1]];  
  31.   
  32.     // Create the validation predicate for the process ID.  
  33.     // The following code is equivalent to validationPredicate = [NSPredicate predicateWithFormat:@"SELF > 0"]  
  34.     //  
  35.     NSExpression *lhs = [NSExpression expressionForEvaluatedObject];  
  36.     NSExpression *rhs = [NSExpression expressionForConstantValue:[NSNumber numberWithInteger:0]];  
  37.       
  38.     NSPredicate *validationPredicate = [NSComparisonPredicate  
  39.                                         predicateWithLeftExpression:lhs  
  40.                                         rightExpression:rhs  
  41.                                         modifier:NSDirectPredicateModifier  
  42.                                         type:NSGreaterThanPredicateOperatorType  
  43.                                         options:0];  
  44.       
  45.     NSString *validationWarning = @"Process ID < 1";  
  46.     [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]  
  47.                   withValidationWarnings:[NSArray arrayWithObject:validationWarning]];  
  48.       
  49.     // set the properties for the entity.  
  50.     //  
  51.     NSArray *properties = [NSArray arrayWithObjects: dateAttribute, idAttribute, nil];  
  52.     [runEntity setProperties:properties];  
  53.       
  54.     // Add a Localization Dictionary  
  55.     //  
  56.     NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];  
  57.     [localizationDictionary setObject:@"Date" forKey:@"Property/date/Entity/Run"];  
  58.     [localizationDictionary setObject:@"Process ID" forKey:@"Property/processID/Entity/Run"];  
  59.     [localizationDictionary setObject:@"Process ID must not be less than 1" forKey:@"ErrorString/Process ID < 1"];  
  60.       
  61.     [moModel setLocalizationDictionary:localizationDictionary];  
  62.       
  63.     return moModel;  
  64. }  


在上面的代码中:

1)我们创建了一个全局模型 moModel;
2)并在其中创建一个名为 Run 的 Entity,这个 Entity 对应的 ManagedObject 类名为 Run(很快我们将创建这样一个类);
3)给 Run Entity 添加了两个必须的 Property:date 和 processID,分别表示运行时间以及进程 ID;并设置默认的进程 ID 为 -1;
4)给 processID 特性设置检验条件:必须大于 0;
5)给模型设置本地化描述词典;

本地化描述提供对 Entity,Property,Error信息等的便于理解的描述,其可用的键值对如下表:

Key

Value


"Entity/NonLocalizedEntityName"

"LocalizedEntityName"


"Property/NonLocalizedPropertyName/Entity/EntityName"

"LocalizedPropertyName"


"Property/NonLocalizedPropertyName"

"LocalizedPropertyName"


"ErrorString/NonLocalizedErrorString"

"LocalizedErrorString"



三,创建并设置运行时类和对象

由于要用到存储功能,所以我们必须定义持久化数据的存储路径。我们在 main() 之前添加如下方法设置存储路径:

  1. NSURL *applicationLogDirectory()  
  2. {  
  3.     NSString *LOG_DIRECTORY = @"CoreDataTutorial";  
  4.     static NSURL *ald = nil;  
  5.       
  6.     if (ald == nil)  
  7.     {  
  8.         NSFileManager *fileManager = [[NSFileManager alloc] init];  
  9.         NSError *error = nil;  
  10.         NSURL *libraryURL = [fileManager URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask  
  11.                                        appropriateForURL:nil create:YES error:&error];  
  12.         if (libraryURL == nil) {  
  13.             NSLog(@"Could not access Library directory\n%@", [error localizedDescription]);  
  14.         }  
  15.         else  
  16.         {  
  17.             ald = [libraryURL URLByAppendingPathComponent:@"Logs"];  
  18.             ald = [ald URLByAppendingPathComponent:LOG_DIRECTORY];  
  19.               
  20.             NSLog(@" >> log path %@", [ald path]);  
  21.               
  22.             NSDictionary *properties = [ald resourceValuesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] error:&error];  
  23.             if (properties == nil)  
  24.             {  
  25.                 if (![fileManager createDirectoryAtPath:[ald path] withIntermediateDirectories:YES attributes:nil error:&error])  
  26.                 {  
  27.                     NSLog(@"Could not create directory %@\n%@",  
  28.                           [ald path], [error localizedDescription]);  
  29.                     ald = nil;  
  30.                 }  
  31.             }  
  32.         }  
  33.     }  
  34.       
  35.     return ald;  
  36. }  

在上面的代码中,我们将持久化数据文件保存到路径:/Users/kesalin/Library/Logs/CoreDataTutorial 下。

下面,我们来创建运行时对象:ManagedObjectContext 和 PersistentStoreCoordinator。

  1. NSManagedObjectContext *managedObjectContext()  
  2. {  
  3.     static NSManagedObjectContext *moContext = nil;  
  4.     if (moContext != nil) {  
  5.         return moContext;  
  6.     }  
  7.       
  8.     moContext = [[NSManagedObjectContext alloc] init];  
  9.       
  10.     // Create a persistent store coordinator, then set the coordinator for the context.  
  11.     //  
  12.     NSManagedObjectModel *moModel = managedObjectModel();  
  13.     NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:moModel];  
  14.     [moContext setPersistentStoreCoordinator: coordinator];  
  15.       
  16.     // Create a new persistent store of the appropriate type.   
  17.     //  
  18.     NSString *STORE_TYPE = NSXMLStoreType;  
  19.     NSString *STORE_FILENAME = @"CoreDataTutorial.xml";  
  20.       
  21.     NSError *error = nil;  
  22.     NSURL *url = [applicationLogDirectory() URLByAppendingPathComponent:STORE_FILENAME];  
  23.       
  24.     NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE  
  25.                                                             configuration:nil  
  26.                                                                       URL:url  
  27.                                                                   options:nil  
  28.                                                                     error:&error];  
  29.       
  30.     if (newStore == nil) {  
  31.         NSLog(@"Store Configuration Failure\n%@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");  
  32.     }  
  33.   
  34.     return moContext;  
  35. }  

在上面的代码中:
1)我们创建了一个全局 ManagedObjectContext 对象 moContext;
2)并在设置其 persistent store coordinator,存储类型为 xml,保存文件名为:CoreDataTutorial.xml,并将其放到前面定义的存储路径下。

好,至此万事具备,只欠 ManagedObject 了!下面我们就来定义这个数据对象类。向工程添加 Core Data->NSManagedObject subclass 的类,名为 Run (模型中 Entity 定义的类名) 。

Run.h

  1. #import <CoreData/NSManagedObject.h>  
  2.   
  3. @interface Run : NSManagedObject  
  4. {  
  5.     NSInteger processID;  
  6. }  
  7.   
  8. @property (retain) NSDate *date;  
  9. @property (retain) NSDate *primitiveDate;  
  10. @property NSInteger processID;  
  11.   
  12. @end  

Run.m
  1. //  
  2. //  Run.m  
  3. //  CoreDataTutorial  
  4. //  
  5. //  Created by kesalin on 8/29/11.  
  6. //  Copyright 2011 kesalin@gmail.com. All rights reserved.  
  7. //  
  8.   
  9. #import "Run.h"  
  10.   
  11. @implementation Run  
  12.   
  13. @dynamic date;  
  14. @dynamic primitiveDate;  
  15.   
  16. - (void) awakeFromInsert  
  17. {  
  18.     [super awakeFromInsert];  
  19.   
  20.     self.primitiveDate = [NSDate date];  
  21. }  
  22.   
  23. #pragma mark -  
  24. #pragma mark Getter and setter  
  25.   
  26. - (NSInteger)processID   
  27. {  
  28.     [self willAccessValueForKey:@"processID"];  
  29.     NSInteger pid = processID;  
  30.     [self didAccessValueForKey:@"processID"];  
  31.     return pid;  
  32. }  
  33.   
  34. - (void)setProcessID:(NSInteger)newProcessID  
  35. {  
  36.     [self willChangeValueForKey:@"processID"];  
  37.     processID = newProcessID;  
  38.     [self didChangeValueForKey:@"processID"];  
  39. }  
  40.   
  41. // Implement a setNilValueForKey: method. If the key is “processID” then set processID to 0.  
  42. //  
  43. - (void)setNilValueForKey:(NSString *)key {  
  44.       
  45.     if ([key isEqualToString:@"processID"]) {  
  46.         self.processID = 0;  
  47.     }  
  48.     else {  
  49.         [super setNilValueForKey:key];  
  50.     }  
  51. }  
  52.   
  53. @end  

注意:
1)这个类中的 date 和 primitiveDate 的访问属性为 @dynamic,这表明在运行期会动态生成对应的 setter 和 getter;
2)在这里我们演示了如何正确地手动实现 processID 的 setter 和 getter:为了让 ManagedObjecContext  能够检测 processID的变化,以及自动支持 undo/redo,我们需要在访问和更改数据对象时告之系统,will/didAccessValueForKey 以及 will/didChangeValueForKey 就是起这个作用的。
3)当我们设置 nil 给数据对象 processID 时,我们可以在 setNilValueForKey 捕获这个情况,并将 processID  置 0;
4)当数据对象被插入到 ManagedObjectContext 时,我们在 awakeFromInsert 将时间设置为当前时间。

三,创建或读取数据对象,设置其值,保存
好,至此真正的万事具备,我们可以创建或从持久化文件中读取数据对象,设置其值,并将其保存到持久化文件中。本例中持久化文件为 xml 文件。修改 main() 中代码如下:

  1. int main (int argc, const char * argv[])  
  2. {  
  3.     NSLog(@" === Core Data Tutorial ===");  
  4.   
  5.     // Enable GC  
  6.     //  
  7.     objc_startCollectorThread();  
  8.   
  9.     NSError *error = nil;  
  10.       
  11.     NSManagedObjectModel *moModel = managedObjectModel();  
  12.     NSLog(@"The managed object model is defined as follows:\n%@", moModel);  
  13.       
  14.     if (applicationLogDirectory() == nil) {  
  15.         exit(1);  
  16.     }  
  17.       
  18.     NSManagedObjectContext *moContext = managedObjectContext();  
  19.       
  20.     // Create an Instance of the Run Entity  
  21.     //  
  22.     NSEntityDescription *runEntity = [[moModel entitiesByName] objectForKey:@"Run"];  
  23.     Run *run = [[Run alloc] initWithEntity:runEntity insertIntoManagedObjectContext:moContext];  
  24.     NSProcessInfo *processInfo = [NSProcessInfo processInfo];  
  25.     run.processID = [processInfo processIdentifier];  
  26.       
  27.     if (![moContext save: &error]) {  
  28.         NSLog(@"Error while saving\n%@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");  
  29.         exit(1);  
  30.     }  
  31.       
  32.     // Fetching Run Objects  
  33.     //  
  34.     NSFetchRequest *request = [[NSFetchRequest alloc] init];  
  35.     [request setEntity:runEntity];  
  36.   
  37.     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];  
  38.     [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];  
  39.       
  40.     error = nil;  
  41.     NSArray *array = [moContext executeFetchRequest:request error:&error];  
  42.     if ((error != nil) || (array == nil))  
  43.     {  
  44.         NSLog(@"Error while fetching\n%@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");  
  45.         exit(1);  
  46.     }  
  47.       
  48.     // Display the Results  
  49.     //  
  50.     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];  
  51.     [formatter setDateStyle:NSDateFormatterMediumStyle];  
  52.     [formatter setTimeStyle:NSDateFormatterMediumStyle];  
  53.       
  54.     NSLog(@"%@ run history:", [processInfo processName]);  
  55.       
  56.     for (run in array)  
  57.     {  
  58.         NSLog(@"On %@ as process ID %ld", [formatter stringForObjectValue:run.date], run.processID);  
  59.     }  
  60.       
  61.     return 0;  
  62. }  

在上面的代码中:
1)我们先获得全局的 NSManagedObjectModel 和 NSManagedObjectContext 对象:moModel 和 moContext;
2)并创建一个Run Entity,设置其 Property processID 为当前进程的 ID;
3)将该数据对象保存到持久化文件中:[moContext save: &error]。我们无需与 PersistentStoreCoordinator 打交道,只需要给 ManagedObjectContext 发送 save 消息即可,NSManagedObjectContext 会透明地在后面处理对持久化数据文件的读写;
4)然后我们创建一个 FetchRequest 来查询持久化数据文件中保存的数据记录,并将结果按照日期升序排列。查询操作也是由 ManagedObjectContext 来处理的:[moContext executeFetchRequest:request error:&error];
5)将查询结果打印输出;

大功告成!编译运行,我们可以得到如下显示:

  1. 2011-09-03 21:42:47.556 CoreDataTutorial[992:903] CoreDataTutorial run history:  
  2. 2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:41:56 as process ID 940  
  3. 2011-09-03 21:42:47.557 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:16 as process ID 955  
  4. 2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:20 as process ID 965  
  5. 2011-09-03 21:42:47.558 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:24 as process ID 978  
  6. 2011-09-03 21:42:47.559 CoreDataTutorial[992:903] On 2011-9-3 下午09:42:47 as process ID 992  

通过这个例子,我们可以更好理解 Core Data  的运作机制。在 Core Data 中我们最常用的就是 ManagedObjectContext,它几乎参与对数据对象的所有操作,包括对 undo/redo 的支持;而 Entity 对应的运行时类为 ManagedObject,我们可以理解为抽象数据结构 Entity 在内存中由 ManagedObject 来体现,而 Perproty 数据类型在内存中则由 ManagedObject 类的成员属性来体现。一般我们不需要与 PersistentStoreCoordinator 打交道,对数据文件的读写操作都由 ManagedObjectContext 为我们代劳了。





前面讲解了 Core Data 的框架,并完全手动编写代码演示了 Core Data 的运作过程。下面我们来演示如何结合 XCode 强大的可视化编辑以及 Cocoa 键值编码,绑定机制来使用 Core Data。有了上面提到的哪些利器,在这个示例中,我们无需编写 NSManagedObjectModel 代码,也无需编写 NSManagedObjectContext,工程模版在背后为我们做了这些事情。

今天要完成的这个示例,有两个 Entity:StudentEntity 与 ClassEntity,各自有一个名为 name 的 Attribute。其中 StudentEntity 通过一个名为 inClass 的 relationship 与 ClassEntity关联,而 ClassEntity 也有一个名为 students 的 relationship 与 StudentEntity 关联,这是一个一对多的关系。此外 ClassEntity 还有一个名为 monitor 的 relationship 关联到 StudentEntity,表示该班的班长。

代码下载:点此下载

最终的效果图如下:


下面我们一步一步来完成这个示例:
1,创建工程:
创建一个 Cocoa Application,工程名为:MacCoreData,并勾选 Create Document-Based Application 和 Use Core Data,在这里要用到 Core Data 和 Document 工程模版,以简化代码的编写。


2,分类文件:
在 MacCoreData 下新建 Src 和 Res 两个 Group,并将 MyDocument.h 和 MyDocument 拖到 Src 下,将其他 xib 和 xcdatamodeld 拖到 Res 中。将文件分类是个好习惯,尤其是对大项目来说。后面请自觉将文件分类~~


3,创建 Entity:
在工程中,我们可以看到名为 MyDocument.xcdatamodeld 的文件,其后缀表明这是一个 core data model文件,框架就是读取该模型文件生成模型的。下面我们选中这个文件,向其中添加两个实体。点击下方的 Add Entity 增加两个新 Entity: ClassEntity 和 StudentEntity。

向 StudentEntity 中添加名为 name 的 string 类型的 Attribute,并设置其 Default Value 为学生甲,去除 Optional 前勾选状态;
向 ClassEntity 中添加名为 name 的 string 类型的 Attribute,并设置其 Default Value 为XX班,去除 Optional 前勾选状态;
选项 Optional 是表示该  Attribute 可选与否的,在这里 name 都是必须的。



向 StudentEntity 中添加名为 inClass 指向 ClassEntity 的 Relationship,其 Inverse 栏要等 ClassEntity 添加了反向关系才能选择,后面回提到;
向 ClassEntity 中添加名为 students 指向 StudentEntity 的 Relationship,其 Inverse 栏选择 inClass,表明这是一个双向关系,勾选 To-Many Relationship,因为一个班级可以有多名学生,这是一对多关系。设定之后,我们可以可以将 StudentEntity 的 inClass 关系的 Inverse 设置为 students了。
再向 ClassEntity 中添加名为 monitor 指向 StudentEntity 的 Relationship,表示该班的班长。

4,生成 NSManagedObject 类:
选中 StudentEntity,然后点击菜单 File-> New -> New file…,添加 Core Data -> NSManagerObject subclass, XCode 就会自动为我们生成 StudentEntity.h 和 StudentEntity.m 文件,记得将这两个文件拖放到 Src Group 下。下面我们来看看这两个文件中有什么内容:
StudentEntity.h

  1. #import <Foundation/Foundation.h>  
  2. #import <CoreData/CoreData.h>  
  3.   
  4. @class ClassEntity;  
  5.   
  6. @interface StudentEntity : NSManagedObject {  
  7. @private  
  8. }  
  9. @property (nonatomic, retain) NSString * name;  
  10. @property (nonatomic, retain) ClassEntity * inClass;  
  11.   
  12. @end  


StudentEntity.m

  1. #import "StudentEntity.h"  
  2. #import "ClassEntity.h"  
  3.   
  4. @implementation StudentEntity  
  5. @dynamic name;  
  6. @dynamic inClass;  
  7.   
  8. @end  


在前面手动代码的示例中,我们是自己编写 Run NSManagedObject的代码,而现在,XCode 已经根据模型文件的描述,自动为我们生成了,方便吧。有时候自动生成的代码不一定满足我们的需要,我们就得对代码进行修改,比如对 ClassEntity 来说,班长只能是其 students 中的一员,如果我们在 students 中移除了班长那个学生,那么该班级的班长就应该置空。

选中 ClassEntity,重复上面的步骤,自动生成 ClassEntity.h 和 ClassEntity.m,下面我们根据需求来修改 ClassEntity.m。
在 - (void)removeStudentsObject:(StudentEntity *)value 的开头添加如下代码:

  1. if (value == [self monitor])  
  2.     [self setMonitor:nil];  


在 - (void)removeStudents:(NSSet *)value 的开头添加如下代码:

  1. if ([value containsObject:[self monitor]])  
  2.     [self setMonitor:nil];  


这样当我们在 students 中删除一个学生时,就会检测该学生是不是班长,如果是,就将该班的班长置空。

5,下面来生成 UI 界面:
在这里,我们是通过切换 view 的方法来显现学生与班级两个界面,因此我们需要主界面,班级以及学生共三个界面。

向 MyDocument.xib 中添加如下一个 popup button 和一个 NSBox。并删除 popup 控件中的 menu item,因为我们要通过代码来添加班级,学生项的。


然后在 Res 中添加两个新 Empty xib 文件:StudentView.xib 和 ClassView.xib,分别向这两个 xib 文件中拖入一个 Custom View,然后在这个 view 添加相关控件构成 UI。记得设置 ClassView 中两个 tableView 的列数为 1,拖入一个 PopupButtonCell 到 StudentView 中班级那一列。效果如下:
 


6,添加 ViewController:
下面我们创建 ViewController 来在程序中转载 xib 文件,显示和切换 view。为了便于切换 view,我们创建一个继承自 NSViewController 的名为:ManagedViewController的类(记得不要创建该类对应的 xib 文件!创建一个 NSObject子类,然后修改其父类为 NSViewController),然后让 StudentViewController 和 ClassViewController 从它继承。ManagedViewController 类的代码如下:
ManagedViewController.h

  1. #import <Cocoa/Cocoa.h>  
  2.   
  3. @interface ManagedViewController : NSViewController {  
  4. @private  
  5.     NSManagedObjectContext * managedObjectContext;  
  6.     NSArrayController * contentArrayController;  
  7. }  
  8.   
  9. @property (nonatomic, retain) NSManagedObjectContext * managedObjectContext;  
  10. @property (nonatomic, retain) IBOutlet NSArrayController *contentArrayController;  
  11.   
  12. @end  


ManagedViewController.m

  1. #import "ManagedViewController.h"  
  2.   
  3. @implementation ManagedViewController  
  4.   
  5. @synthesize managedObjectContext;  
  6. @synthesize contentArrayController;  
  7.   
  8. - (void)dealloc  
  9. {  
  10.     self.contentArrayController = nil;  
  11.     self.managedObjectContext = nil;  
  12.   
  13.     [super dealloc];  
  14. }  
  15.   
  16. // deal with "Delete" key event.  
  17. //  
  18. - (void) keyDown:(NSEvent *)theEvent  
  19. {  
  20.     if (contentArrayController) {  
  21.         if ([theEvent keyCode] == 51) {  
  22.             [contentArrayController remove:nil];  
  23.         }  
  24.         else {  
  25.             [super keyDown:theEvent];  
  26.         }  
  27.     }  
  28.     else {  
  29.         [super keyDown:theEvent];  
  30.     }  
  31. }  
  32.   
  33. @end  


在上面代码中,我们有一个 NSManagedObjectContext * managedObjectContext 指针,它指向 MyDocument 框架中的NSManagedObjectContext对象,后面我们会说到,至于 NSArrayController * contentArrayController,它是一个 IBOutlet,将与xib 中创建的 NSArrayController关联,后面也会说到。在这里引入 contentArrayController 是为了让 delete 能够删除记录。

ClassViewController 类的代码如下:

  1. #import "ManagedViewController.h"  
  2.   
  3. @interface ClassViewController : ManagedViewController {  
  4. @private  
  5. }  
  6.   
  7. @end  
  8.   
  9. #import "ClassViewController.h"  
  10.   
  11. @implementation ClassViewController  
  12.   
  13. - (id)init  
  14. {  
  15.     self = [super initWithNibName:@"ClassView" bundle:nil];  
  16.     if (self) {  
  17.         [self setTitle:@"班级"];  
  18.     }  
  19.       
  20.     return self;  
  21. }  
  22.   
  23. - (void)dealloc  
  24. {  
  25.     [super dealloc];  
  26. }  
  27.   
  28. @end  


StudentViewController 类的代码如下:

  1. #import "ManagedViewController.h"  
  2.   
  3. @interface StudentViewController : ManagedViewController {  
  4. @private  
  5. }  
  6.   
  7. @end  
  8.   
  9. #import "StudentViewController.h"  
  10.   
  11. @implementation StudentViewController  
  12.   
  13. - (id)init  
  14. {  
  15.     self = [super initWithNibName:@"StudentView" bundle:nil];  
  16.     if (self) {  
  17.         [self setTitle:@"学生"];  
  18.     }  
  19.       
  20.     return self;  
  21. }  
  22.   
  23. - (void)dealloc  
  24. {  
  25.     [super dealloc];  
  26. }  
  27.   
  28. @end  


在这两个子类中,我们在 init 方法中载入 xib 文件,然后设置其 title。


前面讲解了 Core Data 的框架,并完全手动编写代码演示了 Core Data 的运作过程。下面我们来演示如何结合 XCode 强大的可视化编辑以及 Cocoa 键值编码,绑定机制来使用 Core Data。有了上面提到的哪些利器,在这个示例中,我们无需编写 NSManagedObjectModel 代码,也无需编写 NSManagedObjectContext,工程模版在背后为我们做了这些事情。

今天要完成的这个示例,有两个 Entity:StudentEntity 与 ClassEntity,各自有一个名为 name 的 Attribute。其中 StudentEntity 通过一个名为 inClass 的 relationship 与 ClassEntity关联,而 ClassEntity 也有一个名为 students 的 relationship 与 StudentEntity 关联,这是一个一对多的关系。此外 ClassEntity 还有一个名为 monitor 的 relationship 关联到 StudentEntity,表示该班的班长。

代码下载:点此下载

=========================================================================

接前半部分

7,创建 NSArrayController,关联对象
现在回到 xib 中来,选中 StudentView.xib,设置StudentView 的 File's Owner 的类为 StudentViewController;使用 Control-Drag 将 File's Owner 的 view 指向 custom view。

向其中拖入两个 NSArrayController:ClassPopup 和 Students。
设置 ClassPopup 的 Object Controller Mode 为 Entity Name,实体名为:ClassEntity,并勾选 Prepare Content。
设置 Students 的 Object Controller Mode 为 Entity Name,实体名为:StudentEntity,并勾选 Prepare Content。

上面的这些操作,ClassPopup ArrayController 管理 ClassEntity 的数据,Students ArrayController 管理 StudentEntity 的数据,后面我们就要将控件与这些 ArrayController 绑定起来。下面我们将这两个 NSArrayController 的 ManagedObjectContext 参数与 ManagedViewController(File's Owner) 中的 managedObjectContext 绑定起来,这样 NSDocuments 的 NSManagedObjectContext 就作用到的 ArrayController 中来了。下面只演示了 ClassPopup,请自行完成 Students 的绑定:

前面我们在 ManagedViewController 创建了一个 IBOutlet contentArrayController,现在是将它关联的时候了,使用 Control-Drag 将 File's Owner 的 contentArrayController 关联到 Students。

重复上面的过程,选中 ClassView.xib,将 File's Owner 的类为 ClassViewController,并将其 view 指向 custom view。
向其中拖入三个 NSArrayController:Classes,MonitorPopup 和 Students。
设置 Classes 的 Object Controller Mode 为 Entity Name,实体名为:ClassEntity,并勾选 Prepare Content。
将 Classes 的 ManagedObjectContext 参数与 ManagedViewController(File's Owner) 中的 managedObjectContext 绑定起来。
注意:这里没有对 MonitorPopup 和 Students 进行修改。
使用 Control-Drag 将 File's Owner 的 contentArrayController 关联到 Classes。

将 Students 和 MonitorPopup 的 Content set 绑定到 Classes 的  Model key path: students,表示这两个 ArrayController  是管理对应 ClassEntity 的 students 的数据。

至此,模型, ArrayController 都准备好了,下面我们将控件绑定到这些对象上。上面已经够繁琐的了,下面我们得更加仔细,很容易出错的。

选中 StudentView.xib,展开 Custom View 中的 TableView,直到我们看到名称和班级两个 Table Column。
选中名称列,将其 value 绑定到 Students,model key path 为:name,表明第一列显示学生的名称;
选择班级列,注意这一列是popup button cell,
将其 Content 绑定到 ClassPopup;
将其 ContentValues 绑定到 ClassPopup,model key path 为:name,表明第二列的选项为班级的名称;
将其 Selected Object 绑定到 Students,model key path 为:inClass;表明将学生添加为选中班级的一员;

选中 + button,使用 Control+Drag将其托拽到 Students 上,选择 add: 动作关联;
选中 - button,使用 Control+Drag将其托拽到 Students 上,选择 remove: 动作关联;
选中 - button,将其 Eanbled 绑定到 Students, ctroller key 为:canRemove;
以上操作是将添加,删除学生的操作直接与 Students ArrayController 绑定,无需编写一点儿代码!


选中 ClassView.xib
展开 Custom View 中的班级表,,直到我们看到班级 Table Column:选择班级列,将其 value 绑定到 Classes,model key path 为:name,表明这一列显示班级的名称;
选中 Box,将其 Title 绑定到 Classed,model key path 为:name,并设置下方的 No Selection Placeholder 为:No Selection,Null Placeholder 为:Unnamed Class。 表明 box 显示的信息为选中班级的信息,如果没有选中任何班级,则显示 No Selection。

展开 Box
选中 Pop up button
将其 Content 绑定到 MonitorPopup;
将其 ContentValues 绑定到 MonitorPopup,model key path 为:name,表明其选项为班级中的学生的名称;
将其 Selected Object 绑定到 Classes,model key path 为:monitor;表明将选中的学生当作该班级的班长;

展开学生 tabel view,直到我们看到学生这个 Table Column。
选择学生列,将其 Value 绑定到 Students,Model key path 为:name,表明学生列表显示该班级中所有学生的名称。

选中 + button,使用 Control+Drag 将其托拽到 Classes 上,选择 add: 动作关联;
选中 - button,使用 Control+Drag 将其托拽到 Classes 上,选择 remove: 动作关联;
选中 - button,将其 Eanbled 绑定到 Classes, ctroller key 为:canRemove;
以上操作是将添加,删除班级的操作直接与 Classes ArrayController 绑定。

至此,绑定也大功告成,如果你的程序运行不正确,多半是这地方的关联与绑定错了,请回到这部分,仔细检查每一项。

8,显示,切换 view。
现在到了设置主界面的时候,修改 MyDocument.h 中的代码如下:

  1. #import <Cocoa/Cocoa.h>  
  2.   
  3. @class ManagedViewController;  
  4.   
  5. @interface MyDocument : NSPersistentDocument {  
  6. @private  
  7.     NSBox *         box;  
  8.     NSPopUpButton * popup;  
  9.       
  10.     NSMutableArray *viewControllers;  
  11.     NSInteger       currentIndex;  
  12. }  
  13.   
  14. @property (nonatomic, retain) IBOutlet NSBox *          box;  
  15. @property (nonatomic, retain) IBOutlet NSPopUpButton *  popup;  
  16.   
  17. - (IBAction) changeViewController:(id)sender;  
  18. - (void) displayViewController:(ManagedViewController *)mvc;  
  19.   
  20. @end  


修改 MyDocument.m  中的代码如下:

  1. #import "MyDocument.h"  
  2. #import "ClassViewController.h"  
  3. #import "StudentViewController.h"  
  4.   
  5. @implementation MyDocument  
  6.   
  7. @synthesize popup;  
  8. @synthesize box;  
  9.   
  10. - (id)init  
  11. {  
  12.     self = [super init];  
  13.     if (self) {  
  14.         // create view controllers  
  15.         //  
  16.         viewControllers = [[NSMutableArray alloc] init];  
  17.           
  18.         ManagedViewController * mvc;  
  19.         mvc = [[ClassViewController alloc] init];  
  20.         [mvc setManagedObjectContext:[self managedObjectContext]];  
  21.         [viewControllers addObject:mvc];  
  22.         [mvc release];  
  23.           
  24.         mvc = [[StudentViewController alloc] init];  
  25.         [mvc setManagedObjectContext:[self managedObjectContext]];  
  26.         [viewControllers addObject:mvc];  
  27.         [mvc release];  
  28.     }  
  29.     return self;  
  30. }  
  31.   
  32. - (void) dealloc  
  33. {  
  34.     self.box = nil;  
  35.     self.popup = nil;  
  36.     [viewControllers release];  
  37.       
  38.     [super dealloc];  
  39. }  
  40.   
  41. - (NSString *)windowNibName  
  42. {  
  43.     // Override returning the nib file name of the document  
  44.     // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.  
  45.     return @"MyDocument";  
  46. }  
  47.   
  48. - (void)windowControllerDidLoadNib:(NSWindowController *)aController  
  49. {  
  50.     [super windowControllerDidLoadNib:aController];  
  51.   
  52.     // init popup  
  53.     //  
  54.     NSMenu *menu = [popup menu];  
  55.     NSInteger itemCount = [viewControllers count];  
  56.       
  57.     for (NSInteger i = 0; i < itemCount; i++) {  
  58.         NSViewController *vc = [viewControllers objectAtIndex:i];  
  59.         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[vc title]  
  60.                                                       action:@selector(changeViewController:)  
  61.                                                keyEquivalent:@""];  
  62.         [item setTag:i];  
  63.         [menu addItem:item];  
  64.         [item release];  
  65.     }  
  66.       
  67.     // display the first controller  
  68.     //  
  69.     currentIndex = 0;  
  70.     [self displayViewController:[viewControllers objectAtIndex:currentIndex]];  
  71.     [popup selectItemAtIndex:currentIndex];  
  72. }  
  73.   
  74. #pragma mark -  
  75. #pragma mark Change Views  
  76.   
  77. - (IBAction) changeViewController:(id)sender  
  78. {  
  79.     NSInteger tag = [sender tag];  
  80.     if (tag == currentIndex) {  
  81.         return;  
  82.     }  
  83.       
  84.     currentIndex = tag;  
  85.     ManagedViewController *mvc = [viewControllers objectAtIndex:currentIndex];  
  86.     [self displayViewController:mvc];  
  87. }  
  88.   
  89. - (void) displayViewController:(ManagedViewController *)mvc  
  90. {  
  91.     NSWindow *window = [box window];  
  92.     BOOL ended = [window makeFirstResponder:window];  
  93.     if (!ended) {  
  94.         NSBeep();  
  95.         return;  
  96.     }  
  97.       
  98.     NSView *mvcView = [mvc view];  
  99.       
  100.     // Adjust window's size and position  
  101.     //  
  102.     NSSize currentSize      = [[box contentView] frame].size;  
  103.     NSSize newSize          =  [mvcView frame].size;  
  104.     float deltaWidth        = newSize.width - currentSize.width;  
  105.     float deltaHeight       = newSize.height - currentSize.height;  
  106.       
  107.     NSRect windowFrame      = [window frame];  
  108.     windowFrame.size.width  += deltaWidth;  
  109.     windowFrame.size.height += deltaHeight;  
  110.     windowFrame.origin.y    -= deltaHeight;  
  111.       
  112.     [box setContentView:nil];  
  113.     [window setFrame:windowFrame display:YES animate:YES];  
  114.       
  115.     [box setContentView:mvcView];  
  116.       
  117.     // add viewController to the responder-chain  
  118.     //  
  119.     [mvcView setNextResponder:mvc];  
  120.     [mvc setNextResponder:box];  
  121. }  
  122.   
  123. @end  


在 MyDocument 中,我们创建了两个 ManagedViewController,并将 managedObjectContext 传入其中。这两个ViewController分别代表班级与学生两个界面,然后通过 popup button 的选择在他们之间切换显示;在 displayViewController 中,我们还根据当前界面的大小来调整主界面的大小。这需要我们设置主界面中 box 的自动大小。打开 MyDocument.xib,作如下设置:


然后,使用 Control+Drag,将 File's Owner的 popup 和 popup button相联,box 与 box相联,并将 popup button 的 action 设置为 File's Owner 的 - (IBAction) changeViewController:(id)sender。

至此,所有的工作都完成了。编译运行程序,如果不出意外的话,我们应该可以添加学生,班级,并设置学生的班级,班级的班长等信息了。


相关文章推荐

Core Data Updated for Swift 3 无水印pdf

  • 2017年09月25日 15:25
  • 6.71MB
  • 下载

Core Data的理解

转载自: http://justsee.iteye.com/blog/1881110 一、基础概念深入 1.NSManagedObjectContext 被管理数据上下文...

Core Data数据验证

  • 2015年06月22日 20:22
  • 53KB
  • 下载

Core Data 编程指南

一、技术概览 1. Core Data 功能初窥   对于处理诸如对象生命周期管理、对象图管理等日常任务,Core Data框架提供了广泛且自动化的解决方案。它有以下特性。   (注:对象图-O...
  • kkaxiao
  • kkaxiao
  • 2012年05月23日 15:20
  • 590

列表显示 core data 数据

  • 2014年07月24日 14:33
  • 51KB
  • 下载

Learning Core Data for iOS

  • 2015年09月21日 10:21
  • 7.03MB
  • 下载

谈谈用SQLite和FMDB而不用Core Data

来源:伯乐在线 凭良心讲,我不能告诉你不去使用Core Data。它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要。   ...

Mastering Core Data With Swift mobi

  • 2017年09月25日 19:34
  • 9.71MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Core data
举报原因:
原因补充:

(最多只允许输入30个字)