(转载)Core Data Tutorial for iOS: Getting Started
所有坚持在iPhone上的数据的方式中,核心数据是最好的用于非平凡的数据存储。它可以减少你的应用程序的内存开销,提高响应能力,并节省您写了很多的样板代码。
然而,对于核心数据的学习曲线是相当大的。这就是这个核心数据教程系列有 - 的目标是让您迅速与核心数据的基础知识,以加快。
在本系列的第一部分,我们要为我们的对象的可视化数据模型,运行快速和肮脏的测试,以确保它的工作原理,然后把它挂到一个表视图,这样我们就可以看到列表我们的对象。
在本系列的第二部分,我们将讨论如何让我们有一些很好的默认数据时,我们的应用程序启动时或导入现有的预加载数据到核心数据。
在本系列的最后一部分,我们将讨论如何通过使用NSFetchedResultsController优化我们的应用程序,以减少内存开销,提高响应时间。
在继续这个核心数据教程中,我建议检查出的系列教程上的SQLite为iPhone开发第一。默认情况下,备份存储核心数据是SQLite的,所以它有助于有怎样的作品第一次的理解。另外,我们正在做的程序是相同的应用程序在该教程-刚刚与核心数据这一次!
创建一个核心数据项目
所以,让我们开始吧!破解打开Xcode和创建主从应用程序模板创建一个新的项目。
在下一个屏幕上,输入FailedBankCD作为产品名称,你想作为公司标识什么,FBCD作为类前缀。
选择iPhone作为设备的家庭,并确保使用故事板,使用核心数据,并使用自动引用计数被选中。选择Next,然后创建。
在我们开始之前,我们还要删除一些由模板生成的样板代码。在项目导航器中选择以下文件:
- FBCDMasterViewController.h
- FBCDMasterViewController.m
- FBCDDetailViewController.h
- FBCDDetailViewController.m
并删除它们。当它askes“移除引用”或“移动到回收站”,选择“移动到回收站”。
现在,在FailedBankCD组文件夹上右击并选择新建文件。选择的iOS \ Objective-C类模板,然后选择下一步。将该类命名为FBCDMasterViewController并使其成为一个子类的UITableViewController。确保两个复选框都未选中。单击下一步,然后创建。
选择FBCDMasterViewController.h在项目导航和@端线前右侧添加以下行:
@属性 (非原子,强) 的NSManagedObjectContext * managedObjectContext; |
现在切换到m文件,并添加@执行行之后合成的财产。:
@合成 managedObjectContext; |
不要担心,如果你不知道什么是“的NSManagedObjectContext”是,我们将讨论,在一个时刻。
但首先,我们需要来配置我们的故事板不再使用详细信息视图控制器(因为我们之前删除了它)。所以请您打开MainStoryboard.storyboard和删除详细信息视图控制器:
我们想要做的最后一件事就是点击FailedBanksCD.xcdatamodel。你会看到一个可视化编辑器会弹出 - 这是我们将使用在一分钟内什么图我们的模型对象。继续在中间的“实体”,说选择泡(或“事件”,这取决于你的Xcode版本),并将其删除。
如果您的屏幕看起来并不像这样,请尝试切换编辑器样式(在此屏幕的右下角)的视觉观点。
好,太好了!现在,让我们来快速浏览一下该项目。如果你给它一个运行它应该只是一个空白的应用程序。
打开FailedBanksCDAppDelegate.m。你会看到,有一些正在实施,我们在这里几个新的功能,建立了核心数据“堆栈”。一个创建了一个管理对象上下文,一是创建一个托管对象模型,以及一个创建一个持久性存储协调员。咦??
不要担心。名字听起来混乱在第一,但一旦你得到一个“心理捷径”因为他们是怎么一回事,他们很容易理解。
- 管理对象模型:你可以认为这是数据库模式。它是一个包含每个对象(也称为“实体”),您都存储在数据库中定义的类。通常情况下,您将使用可视化编辑器你只偷看设立哪些对象在数据库中,他们的属性,以及它们如何相互关联的。然而,你可以做到这一点的代码呢!
- 持久性存储协调员:你可以认为这是数据库连接。在这里你设置的实际名称和什么数据库将被用于存储对象的位置,任何时间被管理对象的上下文需要保存一些它通过这个单一的协调员。
- 管理对象上下文:你可以认为这是一个“便笺”对于来自数据库对象。这也是最重要的三个对于我们的,因为我们将这个工作最。基本上,只要你需要得到对象,插入对象或删除对象时,可以调用托管对象的上下文上的方法(或至少大部分的时间!)
不要太担心这些方法 - 你不会有惹他们了。然而,这是很好的知道,他们在那里和他们代表。
定义我们的模型
当我们创建了数据库表中的SQLite的教程中,我们有一个包含所有数据的倒闭银行的单个表。为了减少数据的内存量一次(学习目的),我们只是拿出我们在第一个表视图需要显示的字段的子集。
因此,我们也许会建立我们的模型以同样的方式与核心数据。然而,随着核心数据,你无法检索对象的唯一的某些属性 - 你要检索整个对象。但是,如果我们因素的对象分为两部分 - 在FailedBankInfo片和FailedBankDetails片 - 我们可以完成同样的事情。
因此,让我们看看这是如何将工作。打开可视化模型编辑器(点击FailedBanksCD.xcodedatamodel)。
让我们开始在我们的模型中创建一个对象 - 被称为核心数据“实体”说话。在底部的工具栏中,单击加号按钮添加一个新的实体。
当点击加号,它会创建一个新的实体,并显示在可视化编辑器的实体的属性。
命名实体FailedBankInfo。然后单击该实体,并确保第三个选项卡是在公用部分(数据模型检查器)中选择。你会看到它目前列出的类作为的NSManagedObject的一个子类。这是默认的类的实体,我们将使用现在 - 以后我们会回来并设置自定义对象。
因此,让我们添加一些属性。首先,请确保您的实体被选中通过点击在实体名称在左边的面板,或图表在图表视图的实体。在中间面板中,单击并按住加号按钮,然后单击“添加属性”,从类似下面的弹出:
在数据模型检查器右侧,将属性命名为“名”,并设置类型为“字符串”像下面这样:
现在,重复此再添加两个属性,“城市”和“国家”,也是两个字符串。
接下来,我们需要创建FailedBankDetails的实体。创建一个Entity你之前做了同样的方式,它FailedBankDetails名称,并添加下面的属性是:压缩类型整数32,Date类型closeDate,和Date类型updateDate。
最后,我们需要这两种类型的关联。选择FailedBankInfo,然后单击并按住加号按钮在中间窗格中,但这次选择“添加关系”:
命名关系“详细信息”,并设定目的地为“FailedBankDetails。”
好了,没有什么,我们只是在这里做?我们只建立在核心数据,其中一个实体连接到另一个实体的关系。在这种情况下,我们正在建立一个one-to-one关系 - 每FailedBankInfo将有完全相同1 FailedBankDetails。在幕后,核心数据将设立我们的数据库,以便我们FailedBankInfo表有相应的FailedBankDetails的ID对象的字段。
苹果建议,每当你创建一个对象链接到另一个,创建从其他物体回去还有一个链接。因此,让我们做到这一点。
添加关系“FailedBankDetails”命名的“信息”,设置目标,以“FailedBankInfo”,并设置逆“详细信息”。
此外,设置删除规则为关系到“级联”。这意味着,如果你删除与核心数据的一个对象,核心数据将删除相关联的对象也是如此。这是有道理的在这种情况下,因为没有相应的FailedBankInfo一个FailedBankDetails就没有任何意义。
给它一个运行!那是什么?崩溃?
好了,这些事情有时会发生。看...当你改变你的存储模式(托管对象模型)和有已经创建了一个持久性存储,它不知道如何读它。这就像使用一个秘密的解码器环写一个秘密消息,然后更改解码环。当你试图改变它之后读取消息,这纯粹是jibberish。
您可以执行模式的升级,但是这是一个不同的一天转换。现在只删除现有的持久性存储(写下来的消息)。最容易做到这一点的方法是直接删除应用程序从您的iPhone或模拟器,它曾经一个你正在使用。点击并按住应用程序,就像删除您的手机上。给它另一个运行,所有应该很好。
我们的测试模型
信不信由你,这可能是我们需要做的最重要的事情。现在它只是测试了使用核心数据,并确保它的工作原理的问题!
首先,让我们来测试一下添加一个测试对象到我们的数据库。打开FailedBanksCDAppDelegate.m并添加以下应用的顶部:didFinishLaunchingWithOptions:
的NSManagedObjectContext *上下文= [自我 setValue方法:@ “试题库” forKey :@ “名” ] ; [ failedBankInfo的setValue :@ “Testville” forKey :@ “城市” ] ; [ failedBankInfo setValue方法:[ NSDate的日期] forKey :@ “closeDate” ] ; [ failedBankDetails的setValue :[ NSDate的日期] forKey :@ “updateDate” ] ; [ failedBankDetails的setValue :[ NSNumber的 numberWithInt :12345 ] forKey :@ “压缩” ] ; [ failedBankDetails setValue方法: failedBankInfo forKey :@ “信息” ], [ failedBankInfo的setValue : failedBankDetails forKey :@ “细节” ] ; NSError *错误; 如果(![现场保护:&错误] ){ 的NSLog (@ “哎呦,也救不了:%@“ ,[错误localizedDescription ] ) ; } |
在第一行中,我们抓住一个指向我们使用自带包含在模板中的辅助函数被管理对象的上下文。
然后我们创建一个的NSManagedObject的新实例为我们FailedBankInfo实体,通过调用insertNewObjectForEntityForName。每一个对象的核心数据存储源自的NSManagedObject。一旦你的对象的实例,就可以调用setValue方法为您在可视化编辑器中定义设置该对象的任何属性。
因此,我们继续前进,建立了一个测试的银行来说,无论FailedBankInfo和FailedBankDetails。此时的对象只是在内存中修改 - 保存他们回到我们需要调用保存在managedObjectContext数据库。
这是所有有给它插入对象 - 没有SQL代码的必要!
在我们尝试了这一点,让我们添加一些更多的代码在那里列出了所有的数据库中的当前对象:
setEntity :实体] ; 的NSArray * fetchedObjects = [背景executeFetchRequest : fetchRequest错误:与错误] ; 为(的NSManagedObject *信息在 fetchedObjects ){ 的NSLog (@ “姓名:%@” ,[信息valueForKey :@ “名” ] ) ; 的NSManagedObject *详细= [资讯valueForKey :@ “细节” ] ; 的NSLog (@ “邮编:%@” ,[详细valueForKey :@ “压缩” ] ) ; } |
在这里,我们创建一个名为的新对象获取请求。你能想到一个读取请求作为SELECT子句。我们称entityForName得到一个指针,我们要检索的FailedBankInfo实体,然后用setEntity告诉我们读取请求这就是我们想要的那种实体。
然后,我们调用托管对象上下文executeFetchRequest拉都在FailedBankInfo表对象到我们的“便笺”。然后,我们通过每个迭代的NSManagedObject,并使用valueForKey拉出多条。
请注意,即使我们拿出刚刚从FailedBankInfo表中的对象,我们仍然可以在存取细节财产上的FAiledBankInfo实体访问相关FailedBankDetails对象。
这是如何工作的?在幕后,当你访问该属性的核心数据注意到,它并没有在上下文中的数据,而“故障”,这基本上意味着它运行在数据库并在拉该数据为你的权利,你需要它。很方便!
给这段代码运行,看一看在你的输出窗口(请注意,你不会看到任何视觉上,只是在输出窗口!),你应该会看到一个测试的银行在你的数据库每次运行程序时。
眼看着原始SQL语句
我不知道你,但是当我工作的这些东西我真的很喜欢看到实际的SQL语句怎么回事,了解如何工作(并确保它做什么,我期待!)
再次苹果已经提供了一个简单的解决这个。在Xcode中打开该计划下拉,然后选择“编辑计划...”。选择“运行”计划,并选择“参数”选项卡。添加以下参数:“-com.apple.CoreData.SQLDebug 1”。当你完成它应该看起来像下面这样:
现在,当你运行你的代码,在调试输出,你应该看到有用的跟踪语句来显示你是怎么回事:
所以在这里我们看到的东西,因为我们期望都在工作。前两个选择和更新的核心数据做什么实体的下一个ID应该有一些簿记工作,保持跟踪。
然后,我们有我们的镶入的细节和信息的表。在那之后,我们在查询中选择了全行信息表。然后,我们通过迭代的结果,每次我们访问的细节变量,幕后的核心数据错误和问题的另一个select语句从ZFAILEDBANKDETAILS表中获取数据。
自动生成模型文件
到目前为止,我们一直在使用的NSManagedObject与我们的实体合作。这不是做事情的最好方法,因为的NSManagedObject是不是一个强类型的类。正如你所看到的,您是通过字符串访问的实体的属性,它是很容易犯了一个错误,并错误地键入一个属性名称,或者使用了错误的类型等设定数据
更好的方式做事情是创建为每个实体模型文件,所以我们有强大的类型化的优点,可避免犯错误,甚至可以继承这个额外的方法/属性。你可以自己动手完成这一点,但是的XCode使这相当与一个类生成容易。
让我们试试吧。打开FailedBanksCD.xcdatamodel,点击FailedBankInfo实体,并转到文件\新建\文件。选择核心数据\的NSManagedObject子类,并单击下一步,然后单击下面的视图中再次创建。
您应该看到添加到项目中一些新的文件:FailedBankInfo.h / m和FailedBankDetails.h /米。这些类是非常简单的,只是宣布根据您添加到实体的属性的属性。你会发现,属性声明为动态里面的m文件 - 这是因为核心数据处理自动布线的属性了。
做的FailedBankDetails实体相同的步骤。
有一个问题与自动生成的类,我们必须要修好。如果你看看FailedBankDetails.h,你会发现,信息变量作为FailedBankInfo类正确的声明,但在FailedBankInfo.h细节变量被定义为的NSManagedObject(但它应该是一个FailedBankDetails对象)。
这是因为你运行的发电机FailedBankInfo你跑了发电机FailedBankDetails之前,所以它不知道会有FailedBankDetails类可用。
您可以手动修复这个问题,但有一个更简单的方法。只是再次FailedBankInfo重新运行生成器,然后选择“替换”覆盖现有文件。中提琴,固定!
此外,采取偷看早在FailedBanksCD.xcdatamodel。当你看看属性的实体,你会发现,类现在已经被自动设置为自动生成的类的名称:
现在,让我们调整我们的测试代码在FBCDAppDelegate.m使用我们新的NSManagedObject的子类。首先添加我们需要往上顶标题:
#导入“FailedBankInfo.h” 的#import“FailedBankDetails.h” |
然后更改代码如下:
的NSManagedObjectContext *上下文= [自我managedObjectContext ] ; FailedBankInfo *failedBankInfo = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankInfo" inManagedObjectContext:context]; failedBankInfo.name = @ “试题库” ; failedBankInfo.city = @ “Testville” ; failedBankInfo.state = @ “Testland” ; FailedBankDetails *failedBankDetails = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankDetails" inManagedObjectContext:context]; failedBankDetails.closeDate = [ NSDate的日期] ; failedBankDetails.updateDate = [ NSDate的日期] ; failedBankDetails.zip = [ NSNumber的 numberWithInt :12345 ] ; failedBankDetails.info = failedBankInfo; failedBankInfo.details = failedBankDetails; NSError *错误; 如果 (![现场保护:&错误] ) { 的NSLog (@ “哎呦,无法保存:%@” ,[错误localizedDescription ] ) ; } / /测试列出所有FailedBankInfos从 setEntity :实体] ; 的NSArray * fetchedObjects = [背景executeFetchRequest : fetchRequest错误:与错误] ; 为( FailedBankInfo *信息在 fetchedObjects ){ 的NSLog (@ “姓名:%@”,info.name ) ; FailedBankDetails *细节= info.details; 的NSLog (@ “邮编:%@”,details.zip ) ; } |
这是一个很值得我们面前,只不过不是直接使用的NSManagedObject,我们使用新的子类相同的代码。现在我们有类型安全和更干净的代码!
创建表视图
打开FBCDMasterViewController.h并添加我们将在表视图中显示failedBankInfos一个成员变量。
@属性 (非原子,强) 的NSArray * failedBankInfos; |
注意它已经拥有我们将使用它来检索这个名单,我们成立较早的被管理对象的上下文。如果你看一下应用程序的底部:在FBCDAppDelegate.m didFinishLaunchingWithOptions,你会看到它设置管理对象范畴内。
切换到FBCDMasterViewController.m并进行以下更改:
/在极顶/,进口部分 的#import“FailedBankInfo.h” / /在顶部,在执行@ @合成 failedBankInfos; |
然后,修改viewDidLoad中看起来如下所示:
- (无效) viewDidLoad中{ [超级viewDidLoad中] ; setEntity :实体] ; NSError *错误; self.failedBankInfos = [ managedObjectContext executeFetchRequest : fetchRequest错误:与错误] ; self.title = @ “失败银行” ; } |
这段代码看起来应该非常熟悉我们的测试代码从较早。我们简单地创建一个获取请求来获取所有数据库的FailedBankInfos的,并将其存储在我们的成员变量。
MODS的其余都酷似我们在SQLite的教程一样。对于快速参考,我会在这里列出的其余步骤再次:
返回1 numberOfSectionsInTableView:
- ( NSInteger的) numberOfSectionsInTableView :( UITableView中* )实现代码如下{ 返回 1 ; } |
更换numberOfRowsInSection以下:
- ( NSInteger的)的tableView :( UITableView中* )实现代码如下 numberOfRowsInSection :( NSInteger的)部分{ 返回 [ failedBankInfos计数] ; } |
修改的cellForRowAtIndexPath看起来像下面这样:
- (的UITableViewCell * )的tableView :( UITableView中* )实现代码如下 的cellForRowAtIndexPath :(NSIndexPath * ) indexPath { 静态 的NSString * CellIdentifier = @ “细胞” ; 的UITableViewCell *电池= [实现代码如下dequeueReusableCellWithIdentifier : CellIdentifier ] ; / /设置单元格... FailedBankInfo *资讯= [ failedBankInfos objectAtIndex : indexPath.row ] ; cell.textLabel.text = info.name; cell.detailTextLabel.text = [ NSString的 stringWithFormat :@ “%@,%@” , info.city,info.state ] ; 回报细胞; } |
最后,选择 MainStoryboard.storyboard
并选择主视图控制器。在表中选择的tableview单元格并设置样式为副标题。
编译并运行该项目,如果一切看起来很好,你应该看到我们先前添加的样本银行!