大宝剑之CoreData(三)

我把CoreData叫做大宝剑,为什么呢,因为CoreData用起来着实让人感到舒爽~~这一篇让我们来详细的了解一下大宝剑~

  • 参考书籍:CORE DATA by Tutorials
  • 默认有swift基础。
  • 默认已阅读上一篇内容。

这一篇主要内容:

  • 深入了解CoreData对象;
  • 创建自己的栈;
  • data model中的Relationships属性;
  • 删除数据;

(1)、深入了解CoreData对象

之前在我们创建工程的时候勾选了Use CoreData的选择框,勾选这个选择框以后在AppDelegate.swift中自动生成了以下四个对象:

  • NSManagedObjectModel
  • NSPersistentStore
  • NSPersistentStoreCoordinator
  • NSManagedObjectContext

在这一篇中我们不勾选这个选择框,自己添加这四个类以便于详细学习“大宝剑”。像这样:


不勾选

NSManagedObjectModel是什么?
这个对象就是你的data model,代表了里面的每一个对象类型。
你可以把他当作是你的数据库的图形化显示。呼呼~~和“度娘”的解释差不多。

Note:如何将该对象与data model联系起来?
来看这个方法:

        let modelURL = NSBundle.mainBundle().URLForResource("CoreDataTest3", withExtension: "momd")!

通过这个方法,编译器将data model文件编译以后放入了一个.momd文件夹,并返回了这个文件夹的地址,通过这个地址我们初始化了我们的NSManagedObjectModel对象。


NSPersistentStore是什么?
无论你决定使用哪种方法来进行存储你都得使用NSPersistentStore来进行存储或者读写数据。CoreData提供了三种原子操作对象和一种非原子操作对象。
原子操作对象在你操作任何读写操作之前将数据全部读写到内存中,非原子操作对象则相反,在需要的时候加载数据。

来看看这四种NSPersistentStore对象

  • NSQLiteStoreType支持SQLite数据库。他是CoreData中唯一的非原子操作存储类型。这基本是绝大多数应用最好的选择了,xcode在默认状态下使用的就是这种存储类型。
  • NSXMLStoreType支持XML文件,这是一种可读的存储类型。这是一种原子操作类型,只在OS X上使用
  • NSBinaryStoreType支持二进制文件,原子操作类型,很少在项目中用到。
  • NSInMemoryStoreType是一个对内存中的数据进行存储的类型,所以这并不是一个正真的“持久化”存储类型。存储在内存中有助于测试,但并没有做到数据持久化。

NSPersistentStoreCoordinator是什么?
这是NSManagedObjectModel和NSPersistentStore之间的桥梁。他能够向NSManagedObjectModel发送信息并存储,也能够从NSPersistentStore中读取信息。


NSManagedObjectContext是什么?
这是我们之前唯一见到过的一个类型了。我们已经提到过这些特性(更多特性后面的篇章会提到):

  • 在内存中也就是我们一直说的‘暂存器’中工作。
  • 如同前面所说CoreData的任何操作的第一步就是创建NSManagedObjectContext对象。
  • 在你使用save()方法之前,你的任何改变都不会影响我们磁盘中的数据

以上内容不了解也没事,因为我们会一个一个用到的。

(2)、自己来创建一个栈

创建一个新文件CoreDataStack.swift(在这个文件中,我们将自己添加全部之前xcode自动生成的代码,以便于对那四个对象的理解),添加以下代码:
<pre><code>
import CoreData
class CoreDataStack {
let context:NSManagedObjectContext!
let psc:NSPersistentStoreCoordinator!
let model:NSManagedObjectModel!
let store:NSPersistentStore!

init() {
    //1
    let bundle = NSBundle.mainBundle()
    let modelURL = bundle.URLForResource("CoreDataTest3", withExtension:"momd")
    model = NSManagedObjectModel(contentsOfURL: modelURL!)
    //2
    psc = NSPersistentStoreCoordinator(managedObjectModel:model)
    //3
    context = NSManagedObjectContext()
    context.persistentStoreCoordinator = psc
    //4
    let fileManager = NSFileManager.defaultManager()
    let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
    let documentsURL = urls[0]
    let storeURL = documentsURL.URLByAppendingPathComponent("Dog Walk")
    let options = [NSMigratePersistentStoresAutomaticallyOption: true]
    var error: NSError? = nil
    store = psc.addPersistentStoreWithType(NSSQLiteStoreType,
        configuration: nil, URL: storeURL, options: options, error:&error)
    if store == nil {
        println("Error adding persistent store: \(error)")
        abort()
    }
}
func saveContext() {
    var error: NSError? = nil
    if context.hasChanges && !context.save(&error) {
        println("Could not save: \(error), \(error?.userInfo)") }
}

}

}
</code></pre>

一下子添加了这么一大块代码,一眼看过去一定是晕晕的,好的,一句一句来解释一下吧。

  • 首先添加了四个常量,这四个常量就是我们之前讲了很久的那四个CoreData的对象,而接下来的初始化方法就是对这四个常量进行初始化。
  • 初始化函数。
  • //1 前面已经说过了NSManagedObjectModel代表着我们的data model。
    let modelURL = bundle.URLForResource("CoreDataTest3", withExtension:"momd")
    这个方法将data model编译成一个‘momd’文件,并返回他的地址,NSManagedObjectModel对象则是通过这个地址来进行初始化。
  • //2 对NSPersistentStoreCoordinator进行初始化,他是data model与‘暂存器’NSManagedObjectContext之间的桥梁。
  • //3 对‘暂存器’NSManagedObjectContext进行初始化,并与我们的‘桥梁’连接起来。
  • //4 通过
    func addPersistentStoreWithType(storeType: String, configuration: String?, URL storeURL: NSURL?, options: [NSObject : AnyObject]?, error: NSErrorPointer) -> NSPersistentStore?
    方法对NSPersistentStore进行初始化,在这里我们指定了persistent store类型。
  • 最后我们添加了保存方法,也就是将‘暂存器’context保存到磁盘中,代码很容易理解,就不进行解释了。

以上添加的代码仔细观察的话,很容易就会发现基本就是在我们勾选了 use core data 以后xcode自动生成的代码,而我们只是将它写在了一个类中,以便于观察及理解。


像我们上面那样创建了一个类,但是这个类根本没有参与到我们的程序中来,接下来就是将这个类参与到程序中。
打开AppDelegate.swift,添加以下代码:

         import CoreData

          lazy var coreDataStack = CoreDataStack()

如此一来coreDataStack这个变量就拥有了我们之前写的四个对象了。
在下面两个方法中添加coreDataStack.saveContext():

<pre><code>
func applicationDidEnterBackground(application: UIApplication) {
coreDataStack.saveContext()
}
func applicationWillTerminate(application: UIApplication) {
coreDataStack.saveContext()
}
</code></pre>

这俩方法确定应用在发生意外退出的时候保存数据,使数据不会丢失。


再来观察一下我们的文件目录,哦~我们还差一个.xcdatamodeld文件,那么就来新建一个吧。
右击文件目录->New file...->选择iOS Core Data->选择Data Model->next->命名为‘CoreDataTest3’,像这样:

1.png

现在来看我们的程序就和勾选了use core data选择一模一样了,接下来就可以进行前面两篇的操作,而且效果相同。哦~唯一的区别就是使用coreDataStack.context来替代前面篇章中AppDelegate.swift中的managedObjectContext。


(3)、relationships属性和删除功能

接下来的内容,我们通过一个Demo,来演示一下data model中的relationships属性和删除功能。
打开storyboard删除原有的控制器,拖入一个tableviewcontroller,并将之设置为程序人口,如图所示:

勾选红色框内的选择框

再为这个控制器创建一个类,如以下步骤:
New File->选择iOS Source,继续选择cocoa touch class->next->将其选择为UITableviewController的子类->next->Create

3.jpeg

同时将控制器和我们新建的文件进行绑定。


4.png

将控制器转化为Navigationcontroller,日图操作:

先选中控制器

给navigation Bar添加一个right Button,并添加动作‘addTime’,添加代码。
将实现以下功能,由于这段实现并不复杂,在这里就不进行描述,有问题的可以留言。

点击“+”按钮,在下面列表中添加时间

目前这个版本并没有实现数据持久化,当我们退出应用以后数据将消失,接下来我们做的就是将数据持久化,这听起来好像在前两篇我们已经做过了,不然~~~在这里我们将引进relationships的概念,同时实现删除功能。


  • relationships

这个逻辑是这样的,我们需要一个数组来存放时间,我们暂且把这个数组叫做‘时间组’,这个‘时间组’存放了许多时间,那么这个时间组就是一个Entity,里面存放的时间也是Entity,不过时间组这个Entity的每一个对象都拥有很多的时间Entity,先来创建这两个Entity,一个起名为“TimeArry”,一个起名为“Time”,在"Time"这个Entity中有一个属性time类型选择为NSDate:

1.png

而在“TimeArry”中则存在一个relationship,起名为“times”,指向“Time”Entity:

Note:relationship生成的属性是什么类型的?“To Many”生成的是NSSet类型,如果想使用下标来来使用对象的话,请在右边的属性栏中勾选Ordered选项,当你勾选了这个选项以后生成的“To Many”类型就是NSOrderedSet类型。
在这里我们使用NSOrderedSet类型

红色框内为Relationship属性

同时将Type选择为“To Many”,勾选Ordered

勾选Ordered

给我们的两个Entity生成对象类吧,生成方法在上一篇中已经讲过:
Editor—>Create NSManagedObject Subclass .............

首先要添加的代码当然是addTime方法来添加数据,在此方法中添加以下代码:
<pre><code>
@IBAction func addTime(sender: AnyObject) {
//获取当前时间
let date=NSDate()
//1
let entity = NSEntityDescription.entityForName("Time", inManagedObjectContext: managedContext)
let TimeObject = Time(entity: entity!, insertIntoManagedObjectContext: managedContext)
TimeObject.time=date

    //2 Insert the new times into the TimeArry's times set
    var times = timearry.times.mutableCopy() as! NSMutableOrderedSet
    times.addObject(TimeObject)
    timearry.times = times.copy() as! NSOrderedSet
    
    //3 Save the managed object context
    var error: NSError?
    if !managedContext!.save(&error) {
        println("Could not save: \(error)")
    }
    
    //4
    let timeFetch = NSFetchRequest(entityName: "TimeArry")
    let result = managedContext.executeFetchRequest(timeFetch, error: &error) as! [TimeArry]!
    self.timearry=result[0]
    self.tableview.reloadData()
}

</code></pre>

代码解释:

  • //1 初始化一个“Time”对象实例。
  • //2
  • 初始化times属性
  • 将之前的“Time”对象实例
  • 添加到relationships中
  • 将relationship添加到当前显示的“TimeArry”
  • //3 保存数据
  • //4 更新timearry数组

Note:timearry就是我们在界面上显示的“TimeArry”对象的一个实例,按理来说TimeArry有很多个对象,每一个“TimeArry”对象都有自己的“Time”,这样说起来好像很像一个二维数组,而事实上我们只显示了这个二维数组的第一行。那么在进入程序的时候我们就得创建这个timearry对象。

添加一下代码:
<pre><code>

var timearry:TimeArry!
override func viewDidLoad() {
super.viewDidLoad()
//初始化暂存器
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
managedContext = appDelegate.coreDataStack.context
//1 获取“TimeArry”对象,并初始化timearry
var error: NSError?
let timeFetch = NSFetchRequest(entityName: "TimeArry")
let result = managedContext.executeFetchRequest(timeFetch, error: &error) as! [TimeArry]!
if result.count == 0 {
let entity = NSEntityDescription.entityForName("TimeArry", inManagedObjectContext: managedContext)
self.timearry = TimeArry(entity: entity!, insertIntoManagedObjectContext: managedContext)
}else{
self.timearry=result[0]
}
// 添加Edit按钮
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
</code></pre>

这段代码很容易理解(如果你看了我前面的两篇内容的话),就是读取了“TimeArry”的内容,然后如果存在数据,则使timearry为第一组数据,若不存在数据则初始化。

因为我们是用一个tableview来显示数据,所以添加以下代码:
<pre><code>
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.timearry.times.count
}
</code></pre>
就不对这段代码做解释了,大家应该都懂。

接下来就是在界面上显示数据了:
<pre><code>

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell

    var fmt=NSDateFormatter()
    fmt.dateFormat = "yyyy-MM-dd-hh-mm-ss"
    let date = self.timearry.times[indexPath.row] as! Time
    let showtime = fmt.stringFromDate(date.time)
    cell.textLabel!.text = showtime
    return cell
}

</code></pre>

"timearry"这个对象的times属性就是我们要读取的内容,你可以把他当作一个数组来操作,因为他也可以用下表来获取期中的每一个数据。
若你在之前data model没有勾选Ordered则在这里生成的times是NSSet类型,那么就不可以用下标来获取内容了。

现在来运行下app:

退出app,重新登录数据还在,说明保存数据成功

现在来添加删除功能,以下代码:
<pre><code>

// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return NO if you do not want the specified item to be editable.
    return true
}    
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
    //1
    let timeToRemove = self.timearry.times[indexPath.row] as! Time
    //2
    let times = self.timearry.times.mutableCopy() as! NSMutableOrderedSet
    times.removeObject(timeToRemove)
    self.timearry.times = times.copy() as! NSOrderedSet
    //3
    managedContext.deleteObject(timeToRemove)
    //4
    var error: NSError?
    if !managedContext.save(&error) {
        println("Could not save: \(error)")
    }

    tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    } else if editingStyle == .Insert {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }    
}

</code></pre>

第一个方法添加左滑编辑功能。
来解释下第二个方法,当你点击Delete会调用此方法:

  • 首先获取要删除的对象
    • 先获取NSOrderedSet对象
  • 从中删除对象
  • 同步到self.timearry.times
  • 从内存中删除对象
  • 保存‘暂存器’

大功告成,这一篇写的好艰苦啊,逻辑混乱,有看不懂的小朋友,实在不好意思了,不清楚的部分请留言,我来解释。
最后运行一下吧:

大功告成

源代码已上传Github:https://github.com/superxlx/CoreDataTest3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容