大宝剑之CoreData(三)

作者: StrongX | 来源:发表于2015-04-21 13:18 被阅读10763次

    我把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

    相关文章

      网友评论

      • StrongX:@我是大C 非常感谢指出错误,如你所说的确在上面已经同步过了,也许这个错误是因为我习惯性的最后一步把数组更新一下,已经重新编辑过了,感谢~~
      • 逍然:写的不错,学习了。
        有一点不太了解,就是在删除数据哪块不知为何要更新self.timearry数组,上面不是已经把数据同步到self.timearry.tims中去了嘛?

      本文标题:大宝剑之CoreData(三)

      本文链接:https://www.haomeiwen.com/subject/acyyfttx.html