如何使用 Core Data 访问和使用实体
How do I access and use an entity using Core Data
是时候使用 Core Data,但是公开的文档和指南花了大量时间讨论一般设置和“幕后”细节。这些东西很重要,但我想要一个快速、干净的资源来展示如何实际使用存储在 Core Data 模型中的信息。
场景
在我的简单示例中,我有一个实体类型:
Job
- salary [Double]
- dateCreated [Date]
这是一个 Swift iOS 应用程序,由故事板提供支持,默认生成 AppDelegate.swift 处理我的托管对象上下文的生成。
问题
如何在我的应用程序中使用 Job 实例?
Bonus points if you can also provide insight around these items:
- As someone who is used to the MVC design pattern, how do I avoid including dirty data access inside of my controllers without bucking iOS development best practices?
- How can I access entities from Core Data while following DRY?
- How do I pass managed objects between methods and controllers while maintaining their type?
Core Data 文档提供了 some snippets 用于获取记录。这个问题本质上是在询问该逻辑在 iOS 应用程序中的位置,以及在获取记录后如何与获取的记录进行实际交互。
一个例子
这个问题并不是一个宽泛的、包罗万象的问题,所以我将以尝试使用 Core Data 的示例作为基础。在我的示例中,我有一个带有标签的 UIViewController。我想让这个标签显示一份工作的薪水。
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
func updateLabel() {
var job = getCurrentJob()
salaryLabel.text = job.salary // ERRORS
}
func getCurrentJob()->(???) {
var error: NSError?
if let fetchedResults = managedObjectContext!.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: &error) {
return fetchedResults[0]
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
这个例子不会编译有两个原因:
- 我没有为 getCurrentJob 指定 return 类型,因为我不确定 return
的类型
- 尝试访问
salary
属性的带有“错误”的行将出错,因为无法知道薪水是工作的实际属性。
如何传递和使用作业对象?
上面示例中缺少的关键部分是 NSManagedObject subclasses,而在 Swift 中,@NSManaged
Swift 注释。 NSManagedObject 是一个通用的 class,它以其最简单的形式可以扩展为简单地提供对核心数据实体属性的访问,但实际上这是传统模型逻辑应该存在的地方。
创建 NSManagedObject 子classes
您可以通过查看 Core Data 模型并使用菜单命令自动生成这些对象:Editor->Create NSManagedObject Subclass
。
这将生成 Job.swift
(或任何您的实体名称)
import Foundation
import CoreData
class Job: NSManagedObject {
@NSManaged var dateCreated: NSDate
@NSManaged var salary: NSNumber
}
使用 NSManagedObject 子classes
您的新 class 现在可以使用了,您可以相应地对获取的结果进行类型转换!为了完成,这里是之前损坏示例的更新版本
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
func updateLabel() {
var job:Job = getCurrentJob()
salaryLabel.text = job.salary // ERRORS
}
func getCurrentJob()->Job {
var error: NSError?
if let fetchedResults = managedObjectContext!.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: &error) {
return fetchedResults[0]
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
例如,像这样创建一个核心数据模型:
现在从中生成 Swift 源代码(使用 Editor
| Create NSManagedObject Subclass
)。这将允许您编译以下版本的 JobViewController
(目前缺少错误处理等):
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
var jobs: [Job] = []
override func viewDidLoad() {
super.viewDidLoad();
jobs = managedObjectContext.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: nil) as [Job];
}
func updateLabel() {
salaryLabel.text = "\(jobs[0].salary) $"
}
}
虽然我知道一些 OOP 的铁杆拥护者会对这个解决方案皱眉,但我建议使用单例 classes 来管理整个应用程序的核心数据。
我建议设置一个可以通过共享实例访问的全局 CoreDataManager。现在您可以全局访问您的检索、更新、删除方法等,并且您的全局变量保持私有。
private var sharedCoreDataManager: CoreDataManager!
class CoreDataManager {
let managedContext: NSManagedObjectContext
class var shared: CoreDataManager {
return sharedCoreDataManager
}
class func initialize(context: NSManagedObjectContext) {
sharedCoreDataManager = CoreDataManager(context: context)
}
private init(context: NSManagedObjectContext) {
managedContext = context
}
func delete(entity: String, index: Int) -> Bool {
var data = fetch(entity)
if data != nil {
managedContext.deleteObject(data![index])
data!.removeAtIndex(index)
managedContext.save(nil)
return true
}
return false
}
func fetch(entity: String) -> [NSManagedObject]? {
var request = NSFetchRequest(entityName: entity)
var error: NSError?
if let entities = managedContext.executeFetchRequest(request, error: &error) as? [NSManagedObject] {
if entities.count > 0 {
return entities
}
}
return nil
}
func save(entity: String, _ attributes: [String: AnyObject]) -> NSManagedObject? {
var entity = NSEntityDescription.entityForName(entity, inManagedObjectContext: managedContext)
let object = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: self.managedContext)
for (key, attr) in attributes {
object.setValue(attr, forKey: key)
}
var error: NSError?
if !managedContext.save(&error) {
return nil
}
return object
}
}
这可以在 AppDelegate 的 didFinishingLaunchingWithOptions 函数中初始化
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
CoreDataManager.initialize(self.managedObjectContext!)
return true
}
您可以通过单击导航器中的 YourProject.xcdatamodeld
来设置您的 NSManagedObject
。在您的例子中,您将添加一个具有 salary
(double) 和 date
(date) 属性的 Job
实体。在顶部菜单访问 Editor > CreateNSManagedObjectSubclass 以自动生成您的 Job subclass。当您仍在 xcdatmodel 编辑器中时,打开最右边的窗格——您应该会看到 'Name' 和 'Class' 的文本字段。请务必将 class 更改为 'ProjectName.Name`——在你的情况下 'ProjectName.Job'——否则你将无法实例化新的 NSManagedObject class.
您的 NSManagedObject class 应该会自动为您生成并可在项目导航器中进行检查。它看起来像这样:
import Foundation
import CoreData
@objc class Job: NSManagedObject {
@NSManaged var salary: NSNumber
@NSManaged var date: NSDate
}
为了限制对托管对象的访问,您应该创建具有 get 和 set 样式变量的中介 classes。虽然 Swift 没有 'protected' 访问级别,但您可以将 NSManagedObjects 保密,并允许通过对象调解器进行访问,方法是将它们分组到一个 class 文件中:
class ManagedObjectMediator<T: NSManagedObject> {
private var managedObject: T!
init?(_ type: String, attributes: [String: AnyObject]) {
if let newManagedObject = CoreDataManager.shared.save(type, attributes) {
managedObject = newManagedObject as T
} else {
return nil
}
}
}
class JobMediator<T: Job>: ManagedObjectMediator<Job> {
var date: NSDate {
return managedObject.date
}
var salary: NSNumber {
return managedObject.salary
}
init?(attributes: [String:AnyObject]) {
super.init("Job", attributes: attributes)
}
}
是时候使用 Core Data,但是公开的文档和指南花了大量时间讨论一般设置和“幕后”细节。这些东西很重要,但我想要一个快速、干净的资源来展示如何实际使用存储在 Core Data 模型中的信息。
场景
在我的简单示例中,我有一个实体类型:
Job
- salary [Double]
- dateCreated [Date]
这是一个 Swift iOS 应用程序,由故事板提供支持,默认生成 AppDelegate.swift 处理我的托管对象上下文的生成。
问题
如何在我的应用程序中使用 Job 实例?
Bonus points if you can also provide insight around these items:
- As someone who is used to the MVC design pattern, how do I avoid including dirty data access inside of my controllers without bucking iOS development best practices?
- How can I access entities from Core Data while following DRY?
- How do I pass managed objects between methods and controllers while maintaining their type?
Core Data 文档提供了 some snippets 用于获取记录。这个问题本质上是在询问该逻辑在 iOS 应用程序中的位置,以及在获取记录后如何与获取的记录进行实际交互。
一个例子
这个问题并不是一个宽泛的、包罗万象的问题,所以我将以尝试使用 Core Data 的示例作为基础。在我的示例中,我有一个带有标签的 UIViewController。我想让这个标签显示一份工作的薪水。
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
func updateLabel() {
var job = getCurrentJob()
salaryLabel.text = job.salary // ERRORS
}
func getCurrentJob()->(???) {
var error: NSError?
if let fetchedResults = managedObjectContext!.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: &error) {
return fetchedResults[0]
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
这个例子不会编译有两个原因:
- 我没有为 getCurrentJob 指定 return 类型,因为我不确定 return 的类型
- 尝试访问
salary
属性的带有“错误”的行将出错,因为无法知道薪水是工作的实际属性。
如何传递和使用作业对象?
上面示例中缺少的关键部分是 NSManagedObject subclasses,而在 Swift 中,@NSManaged
Swift 注释。 NSManagedObject 是一个通用的 class,它以其最简单的形式可以扩展为简单地提供对核心数据实体属性的访问,但实际上这是传统模型逻辑应该存在的地方。
创建 NSManagedObject 子classes
您可以通过查看 Core Data 模型并使用菜单命令自动生成这些对象:Editor->Create NSManagedObject Subclass
。
这将生成 Job.swift
(或任何您的实体名称)
import Foundation
import CoreData
class Job: NSManagedObject {
@NSManaged var dateCreated: NSDate
@NSManaged var salary: NSNumber
}
使用 NSManagedObject 子classes
您的新 class 现在可以使用了,您可以相应地对获取的结果进行类型转换!为了完成,这里是之前损坏示例的更新版本
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
func updateLabel() {
var job:Job = getCurrentJob()
salaryLabel.text = job.salary // ERRORS
}
func getCurrentJob()->Job {
var error: NSError?
if let fetchedResults = managedObjectContext!.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: &error) {
return fetchedResults[0]
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
例如,像这样创建一个核心数据模型:
现在从中生成 Swift 源代码(使用 Editor
| Create NSManagedObject Subclass
)。这将允许您编译以下版本的 JobViewController
(目前缺少错误处理等):
import UIKit
import CoreData
class JobViewController: UIViewController {
@IBOutlet var salaryLabel: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
var jobs: [Job] = []
override func viewDidLoad() {
super.viewDidLoad();
jobs = managedObjectContext.executeFetchRequest(NSFetchRequest(entityName:"Job"), error: nil) as [Job];
}
func updateLabel() {
salaryLabel.text = "\(jobs[0].salary) $"
}
}
虽然我知道一些 OOP 的铁杆拥护者会对这个解决方案皱眉,但我建议使用单例 classes 来管理整个应用程序的核心数据。
我建议设置一个可以通过共享实例访问的全局 CoreDataManager。现在您可以全局访问您的检索、更新、删除方法等,并且您的全局变量保持私有。
private var sharedCoreDataManager: CoreDataManager!
class CoreDataManager {
let managedContext: NSManagedObjectContext
class var shared: CoreDataManager {
return sharedCoreDataManager
}
class func initialize(context: NSManagedObjectContext) {
sharedCoreDataManager = CoreDataManager(context: context)
}
private init(context: NSManagedObjectContext) {
managedContext = context
}
func delete(entity: String, index: Int) -> Bool {
var data = fetch(entity)
if data != nil {
managedContext.deleteObject(data![index])
data!.removeAtIndex(index)
managedContext.save(nil)
return true
}
return false
}
func fetch(entity: String) -> [NSManagedObject]? {
var request = NSFetchRequest(entityName: entity)
var error: NSError?
if let entities = managedContext.executeFetchRequest(request, error: &error) as? [NSManagedObject] {
if entities.count > 0 {
return entities
}
}
return nil
}
func save(entity: String, _ attributes: [String: AnyObject]) -> NSManagedObject? {
var entity = NSEntityDescription.entityForName(entity, inManagedObjectContext: managedContext)
let object = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: self.managedContext)
for (key, attr) in attributes {
object.setValue(attr, forKey: key)
}
var error: NSError?
if !managedContext.save(&error) {
return nil
}
return object
}
}
这可以在 AppDelegate 的 didFinishingLaunchingWithOptions 函数中初始化
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
CoreDataManager.initialize(self.managedObjectContext!)
return true
}
您可以通过单击导航器中的 YourProject.xcdatamodeld
来设置您的 NSManagedObject
。在您的例子中,您将添加一个具有 salary
(double) 和 date
(date) 属性的 Job
实体。在顶部菜单访问 Editor > CreateNSManagedObjectSubclass 以自动生成您的 Job subclass。当您仍在 xcdatmodel 编辑器中时,打开最右边的窗格——您应该会看到 'Name' 和 'Class' 的文本字段。请务必将 class 更改为 'ProjectName.Name`——在你的情况下 'ProjectName.Job'——否则你将无法实例化新的 NSManagedObject class.
您的 NSManagedObject class 应该会自动为您生成并可在项目导航器中进行检查。它看起来像这样:
import Foundation
import CoreData
@objc class Job: NSManagedObject {
@NSManaged var salary: NSNumber
@NSManaged var date: NSDate
}
为了限制对托管对象的访问,您应该创建具有 get 和 set 样式变量的中介 classes。虽然 Swift 没有 'protected' 访问级别,但您可以将 NSManagedObjects 保密,并允许通过对象调解器进行访问,方法是将它们分组到一个 class 文件中:
class ManagedObjectMediator<T: NSManagedObject> {
private var managedObject: T!
init?(_ type: String, attributes: [String: AnyObject]) {
if let newManagedObject = CoreDataManager.shared.save(type, attributes) {
managedObject = newManagedObject as T
} else {
return nil
}
}
}
class JobMediator<T: Job>: ManagedObjectMediator<Job> {
var date: NSDate {
return managedObject.date
}
var salary: NSNumber {
return managedObject.salary
}
init?(attributes: [String:AnyObject]) {
super.init("Job", attributes: attributes)
}
}