Core Data详细解析(四) —— 一个简单的入门示例(二)

版本记录

版本号 时间
V1.0 2018.09.26 星期三

前言

数据是移动端的重点关注对象,其中有一条就是数据存储。CoreData是苹果出的数据存储和持久化技术,面向对象进行数据相关存储。感兴趣的可以看下面几篇文章。
1. iOS CoreData(一)
2. iOS CoreData实现数据存储(二)
3. Core Data详细解析(三) —— 一个简单的入门示例(一)

Modeling Your Data - 建模数据

现在您知道如何检查持久性,您可以深入了解Core Data。 您对HitList应用程序的目标很简单:保留您输入的名称,以便在新应用程序启动后可以查看这些names

到目前为止,您一直使用普通的旧Swift字符串将names存储在内存中。 在本节中,您将使用Core Data对象替换这些字符串。

第一步是创建一个managed object model,该模型描述了Core Data在磁盘上表示数据的方式。

默认情况下,Core Data使用SQLite数据库作为持久性存储,因此您可以将数据模型视为数据库模式。

注意:在处理Core Data时,您会遇到相当多的“managed”。 如果您在类的名称中看到“managed”,例如在NSManagedObjectContext中,则可能正在处理Core Data类。 “Managed”是指Core Data对Core Data对象生命周期的管理。但是,不要假设所有Core Data类都包含“managed”一词。 大多数没有。 有关Core Data类的完整列表,请查看文档浏览器中的Core Data框架参考。

由于您已选择使用Core Data,因此Xcode会自动为您创建一个数据模型文件,并将其命名为HitList.xcdatamodeld

Core Data详细解析(四) —— 一个简单的入门示例(二)_第1张图片

打开HitList.xcdatamodeld。 如您所见,Xcode拥有强大的数据模型编辑器:

Core Data详细解析(四) —— 一个简单的入门示例(二)_第2张图片

数据模型编辑器具有许多您可以在以后探索的功能。 现在,让我们专注于创建一个Core Data实体。

单击左下方的Add Entity以创建新实体。 双击新实体并将其名称更改为Person,如下所示:

Core Data详细解析(四) —— 一个简单的入门示例(二)_第3张图片

您可能想知道为什么模型编辑器使用术语Entity。你不是只是简单地定义一个新类吗?正如您将很快看到的,Core Data带有自己的词汇。以下是您将经常遇到的一些术语的快速概述:

  • entity是Core Data中的类定义。典型的例子是EmployeeCompany。在关系数据库中,实体对应于表。
  • attribute是附加到特定entity的一条信息。例如,Employee实体可以具有员工namepositionsalary`的属性。在数据库中,属性对应于表中的特定字段。
  • relationship是多个实体之间的链接。在Core Data中,两个实体之间的关系称为to-one relationships,而一个和多个实体之间的关系称为to-many relationships。例如,经理可以与一组员工建立一对多(to-many relationship)的关系,而个人员工通常与他的经理有一对一( to-one relationship)的关系。

注意:您可能已经注意到实体听起来很像类。 同样,属性和关系听起来很像属性。 有什么不同? 您可以将Core Data实体视为类定义,将managed object视为该类的实例。

现在您知道属性是什么,您可以向之前创建的Person对象添加属性。 仍然在HitList.xcdatamodeld中,选择左侧的Person并单击Attributes下的加号(+)

将新属性的名称设置为,比如说,name并将其类型更改为String

Core Data详细解析(四) —— 一个简单的入门示例(二)_第4张图片

Core Data中,属性可以是多种数据类型之一。


Saving to Core Data - 保存到Core Data

打开ViewController.swift,在UIKit导入下面添加以下Core Data模块导入:

import CoreData

要在代码中开始使用Core Data API,只需导入即可。

接下来,使用以下内容替换names属性定义:

var people: [NSManagedObject] = []

您将存储Person实体而不是字符串名称,因此您将用作table view的数据模型的数组重命名为people。 它现在包含NSManagedObject的实例而不是简单的字符串。

NSManagedObject表示存储在Core Data中的单个对象; 您必须使用它来创建,编辑,保存和删除Core Data持久性存储。 正如您将很快看到的,NSManagedObject是一个形状移位器。 它可以采用数据模型中任何实体的形式,占用您定义的任何属性和关系。

由于您要更改table view的模型,因此还必须替换之前实现的两种数据源方法。 用以下内容替换您的UITableViewDataSource扩展:

// MARK: - UITableViewDataSource

extension ViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView,
                 numberOfRowsInSection section: Int) -> Int {
    return people.count
  }

  func tableView(_ tableView: UITableView,
                 cellForRowAt indexPath: IndexPath)
                 -> UITableViewCell {

    let person = people[indexPath.row]
    let cell =
      tableView.dequeueReusableCell(withIdentifier: "Cell",
                                    for: indexPath)
    cell.textLabel?.text =
      person.value(forKeyPath: "name") as? String
    return cell
  }
}

这些方法最重要的变化发生在tableView(_:cellForRowAt :)中。 现在,您不是将单元格与模型数组中的相应字符串进行匹配,而是将单元格与相应的NSManagedObject匹配。

请注意如何从NSManagedObject中获取name属性。 如下所示:

cell.textLabel?.text =
  person.value(forKeyPath: "name") as? String

你为什么要这样做? 事实证明,NSManagedObject不知道您在数据模型中定义的name属性,因此无法直接使用属性访问它。 Core Data提供读取值的唯一方法是键值编码,通常称为KVC。

注意:KVC是Foundation中用于间接使用字符串访问对象属性的机制。 在这种情况下,KVC使NSMangedObject在运行时的行为有点像字典。键值编码可用于从NSObject继承的所有类,包括NSManagedObject。 您不能在不继承自NSObject的Swift对象上使用KVC访问属性。

接下来,找到addName(_ :)并用以下内容替换save UIAlertAction

let saveAction = UIAlertAction(title: "Save", style: .default) {
  [unowned self] action in
  
  guard let textField = alert.textFields?.first,
    let nameToSave = textField.text else {
      return
  }
  
  self.save(name: nameToSave)
  self.tableView.reloadData()
}

这将获取text field中的文本并将其传递给名为save(name :)的新方法。 Xcode报错因为save(name :)尚不存在。 在addName(_ :)下面添加它:

func save(name: String) {
  
  guard let appDelegate =
    UIApplication.shared.delegate as? AppDelegate else {
    return
  }
  
  // 1
  let managedContext =
    appDelegate.persistentContainer.viewContext
  
  // 2
  let entity =
    NSEntityDescription.entity(forEntityName: "Person",
                               in: managedContext)!
  
  let person = NSManagedObject(entity: entity,
                               insertInto: managedContext)
  
  // 3
  person.setValue(name, forKeyPath: "name")
  
  // 4
  do {
    try managedContext.save()
    people.append(person)
  } catch let error as NSError {
    print("Could not save. \(error), \(error.userInfo)")
  }
}

这就是Core Data的用武之地! 这是代码的作用:

  • 1)在从Core Data存储中保存或检索任何内容之前,首先需要开始使用NSManagedObjectContext。 您可以将managed object context视为内存中的“暂存器”,以便使用managed objects

考虑将新的managed object保存到Core Data,这是一个两步过程:首先,将新的managed object插入到managed object context; 一旦您满意,您可以“提交”managed object context中的更改以将其保存到磁盘。

Xcode已经生成了一个managed object context,作为新项目模板的一部分。 请记住,只有在您选中开始时Use Core Data复选框时才会发生这种情况。 此缺省managed object context作为应用程序委托中NSPersistentContainer的属性。 要访问它,首先要获得对app delegate的引用。

  • 2) 您创建一个新的managed object并将其插入到managed object context中。 您可以使用NSManagedObject的静态方法一步完成此操作:entity(forEntityName:in :)

您可能想知道NSEntityDescription是什么。 回想一下,NSManagedObject被称为shape-shifter类,因为它可以表示任何实体。 实体描述是在运行时将数据模型中的实体定义与NSManagedObject实例相链接的部分。

  • 3) 使用NSManagedObject,您可以使用键值编码设置name属性。 您必须完全按照数据模型中显示的方式拼写KVC键(在本例中为name),否则,您的应用程序将在运行时崩溃。

  • 4) 通过在managed object context中调用save,可以将更改提交给person并保存到磁盘。 注意save可能会抛出错误,这就是你在do-catch块中使用try关键字调用它的原因。 最后,将新的managed object插入到people数组中,以便在table view重新加载时显示。

这比使用字符串数组要复杂一点,但也不算太糟糕。 这里的一些代码,例如获取managed object contextentity,可以在您自己的init()viewDidLoad()中完成一次,然后再重复使用。 为简单起见,您可以使用相同的方法完成所有操作。

构建并运行应用程序,并在table view中添加一些名称:

Core Data详细解析(四) —— 一个简单的入门示例(二)_第5张图片

如果names实际存储在Core Data中,则HitList应用程序应通过持久性测试。 将应用程序置于前台,转到快速应用程序切换器,然后终止它。

Springboard,点击HitList应用程序以触发新的启动。 等等,发生了什么? table view为空:

Core Data详细解析(四) —— 一个简单的入门示例(二)_第6张图片

您保存到Core Data,但在新应用程序启动后,people数组是空的! 那是因为数据正在你的磁盘上,但你还没有显示它。


Fetching from Core Data - 从Core Data中获取

要将持久存储中的数据导入到managed object context,您必须fetch它。 打开ViewController.swift并在viewDidLoad()下面添加以下内容:

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  
  //1
  guard let appDelegate =
    UIApplication.shared.delegate as? AppDelegate else {
      return
  }
  
  let managedContext =
    appDelegate.persistentContainer.viewContext
  
  //2
  let fetchRequest =
    NSFetchRequest(entityName: "Person")
  
  //3
  do {
    people = try managedContext.fetch(fetchRequest)
  } catch let error as NSError {
    print("Could not fetch. \(error), \(error.userInfo)")
  }
}

下面一步一步看代码的作用:

  • 1) 在使用Core Data执行任何操作之前,您需要一个managed object contextFetching没有什么不同! 像以前一样,您启动应用程序委托并获取对其持久容器的引用以获取其NSManagedObjectContext

  • 2) 顾名思义,NSFetchRequest是负责从Core Data获取数据的类。 获取请求既强大又灵活。 您可以使用获取请求来获取符合所提供标准的一组对象(即,让我所有员工都住在威斯康星州并且已经在公司工作至少三年),个人价值(即在数据库中给我最长的名字)和 更多。

Fetch请求有几个限定符用于优化返回的结果集。 现在,你应该知道NSEntityDescription是这些必需的限定符之一。

设置获取请求的entity属性,或者使用init(entityName :)初始化它,获取特定实体的所有对象。 这是你在这里获取所有Person实体的方法。 另请注意,NSFetchRequest是泛型类型。 泛型的使用指定了获取请求的预期返回类型,在本例中为NSManagedObject

  • 3) 您将fetch请求移交给managed object context以执行繁重的工作。 fetch(_ :)返回符合获取请求指定条件的managed object数组。

注意:与save()一样,fetch(_ :)也会抛出错误,因此您必须在do block中使用它。 如果在fetch期间发生错误,您可以检查catch block内的错误并进行适当的响应。

构建并运行应用程序。 您应该立即看到之前添加的names列表:

Core Data详细解析(四) —— 一个简单的入门示例(二)_第7张图片

在列表中添加更多names并重新启动应用程序以验证保存和提取是否正常。 如果没有删除应用程序,重置模拟器或将手机从高层建筑物中移开,则无论如何都会在table view中显示名称。

注意:此示例应用程序中有一些粗略的边缘:您必须每次从应用程序委托获取managed object context,并且您使用KVC访问实体的属性而不是更自然的对象样式person.name


Key Points - 关键点

  • Core Data提供磁盘持久性(on-disk persistence),这意味着即使在终止应用程序或关闭设备后,您的数据也可以访问。 这与内存中持久性不同,内存持久性只会在应用程序位于内存中时保存您的数据,无论是在前台还是在后台。
  • Xcode附带了一个功能强大的数据模型编辑器(Data Model editor),您可以使用它来创建managed object model
  • managed object模型由entitiesattributesrelationships组成
  • 实体entitiesCore Data中的类定义。
  • 属性attributes是附加到实体的一条信息。
  • 关系relationships是多个实体之间的链接。
  • NSManagedObject是Core Data实体的运行时表示。 您可以使用键值编码来读取和写入其属性。
  • 您需要NSManagedObjectContext来从或者向Core Data save()fetch(_:)读取和写入数据。

源码

下面看一下Swift源码。

1. ViewController.swift
import UIKit
import CoreData

class ViewController: UIViewController {

  @IBOutlet weak var tableView: UITableView!
  var people: [NSManagedObject] = []

  override func viewDidLoad() {
    super.viewDidLoad()

    title = "The List"
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
  }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
      return
    }

    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest(entityName: "Person")

    do {
      people = try managedContext.fetch(fetchRequest)
    } catch let error as NSError {
      print("Could not fetch. \(error), \(error.userInfo)")
    }
  }

  @IBAction func addName(_ sender: UIBarButtonItem) {
    let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)

    let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in

      guard let textField = alert.textFields?.first,
        let nameToSave = textField.text else {
          return
      }

      self.save(name: nameToSave)
      self.tableView.reloadData()
    }

    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
    alert.addTextField()
    alert.addAction(saveAction)
    alert.addAction(cancelAction)

    present(alert, animated: true)
  }

  func save(name: String) {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
      return
    }

    let managedContext = appDelegate.persistentContainer.viewContext
    let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
    let person = NSManagedObject(entity: entity, insertInto: managedContext)
    person.setValue(name, forKeyPath: "name")

    do {
      try managedContext.save()
      people.append(person)
    } catch let error as NSError {
      print("Could not save. \(error), \(error.userInfo)")
    }
  }
}

// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return people.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let person = people[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = person.value(forKeyPath: "name") as? String
    return cell
  }
}

下面看一下效果展示

后记

好几天没有更新了,因为这几天做封闭区块链开发,今天有时间就更新了下,希望大家能喜欢,给个赞或者关注,祝大家中秋快乐(迟到的祝福!)。

Core Data详细解析(四) —— 一个简单的入门示例(二)_第8张图片

你可能感兴趣的:(Core Data详细解析(四) —— 一个简单的入门示例(二))