【问题标题】:How do I access and use an entity using Core Data如何使用 Core Data 访问和使用实体
【发布时间】:2015-03-07 16:14:20
【问题描述】:

是时候使用Core Data了,但是那里的开放文档和指南花了很多时间谈论一般设置和“幕后”细节。这些东西很重要,但我希望有一个快速、干净的资源来展示如何实际使用存储在 Core Data 模型中的信息。

情景

在我的简单示例中,我有一个实体类型:

Job
 - salary [Double]
 - dateCreated [Date]

这是一个由故事板提供支持的 Swift iOS 应用程序,默认生成 AppDelegate.swift 来处理我的托管对象上下文的生成。

问题

如何在我的应用程序中使用 Job 实例?

如果您还可以提供有关这些项目的见解,则可以加分:

  • 作为习惯于 MVC 设计模式的人,我如何避免在不违背 iOS 开发最佳实践的情况下避免在控制器内部包含脏数据访问?
  • 我如何在关注 DRY 时从 Core Data 访问实体?
  • 如何在方法和控制器之间传递托管对象,同时保持它们的类型?

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()
    }
}

这个例子不能编译有两个原因:

  1. 我没有为 getCurrentJob 指定返回类型,因为我不确定要返回什么类型
  2. 尝试访问salary 属性的带有“ERRORS”的行会出错,因为无法知道薪水是工作的实际属性。

如何传递和使用作业对象?

【问题讨论】:

  • 这是一个非常大的问题...您可以下载漂亮而简单的库,它有一个很好的核心数据管理器ObjectiveRecord,还可以让您将一些数据从 JSON 字典映射到您的核心数据。它是开源项目,因此您可以将管理器实现视为示例
  • @aquarium_moose 恕我直言,不需要特殊库:Core Data 本身可以很好地涵盖这一点
  • UIViewControllers 是什么类型的?
  • @Drux 的 UIViewControllers 链接到故事板,除此之外我不确定你的意思:-/
  • 现在听起来肯定更土气了 :) 所以你需要一个核心数据模型(新/文件/核心数据/数据模型),它有一个实体(工作)和两个属性。然后,您可以从中为 Job.swift 生成 Swift 代码(Editor / Create NSManagedObject Subclass ...),这将允许您执行 job.salary。这些是 Objective-C 的常用步骤,也适用于 Swift。但是,在 Swift 中,您也可以使用 @NSManagedObject 注解。我没有这方面的经验,但谷歌是你的朋友......

标签: ios swift core-data


【解决方案1】:

上面示例中缺少的关键部分是NSManagedObject subclasses,而在 Swift 中,@NSManaged Swift 注释。 NSManagedObject 是一个通用类,以其最简单的形式,可以扩展为简单地提供对 Core Data 实体属性的访问,但实际上这是传统模型逻辑应该存在的地方。

创建 NSManagedObject 子类

您可以通过查看 Core Data 模型并使用菜单命令自动生成这些对象:Editor->Create NSManagedObject Subclass

这将生成Job.swift(或您的实体名称)

import Foundation
import CoreData

class Job: NSManagedObject {

    @NSManaged var dateCreated: NSDate
    @NSManaged var salary: NSNumber

}

使用 NSManagedObject 子类

您的新类现在可以使用了,您可以相应地对获取的结果进行类型转换!为了完成,这里是先前损坏示例的更新版本

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()
    }
}

【讨论】:

    【解决方案2】:

    例如,像这样创建一个核心数据模型:

    现在从中生成 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) $"
        }
    }
    

    【讨论】:

    • 出于好奇,您通常会将 managedObjectContext 获取代码放在控制器中吗?我的直觉是在 Job.swift 中创建某种类型的静态方法,但也许有更标准的 iOS 方法呢? (例如,我不知道的菜单项!)
    • @slifty 我通常将它保存在控制器中(取决于实体相对于视图控制器的生命周期;或在 NSFetchedResultsController 中——见上文),通常也关闭主线程,因为获取可能需要一些时间(取决于上下文背后的商店)。
    【解决方案3】:

    虽然我知道一些铁杆 OOP 拥护者会反对这种解决方案,但我建议使用单例类来跨应用程序管理核心数据。

    我建议设置一个可以通过共享实例访问的全局 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 子类。当您仍在 xcdatmodel 编辑器中时,打开最右侧的窗格——您应该会看到“名称”和“类”的文本字段。请务必将您的类更改为“ProjectName.Name”——在您的情况下为“ProjectName.Job”——否则您将无法实例化新的 NSManagedObject 类。

    您的 NSManagedObject 类应该会自动为您生成,并且可以在项目导航器中进行检查。它看起来像这样:

    import Foundation
    import CoreData
    
    @objc class Job: NSManagedObject {
        @NSManaged var salary: NSNumber
        @NSManaged var date: NSDate
    }
    

    为了限制对托管对象的访问,您应该使用 get- 和 set-style 变量创建中介类。尽管 Swift 没有“受保护”访问级别,但您可以将 NSManagedObjects 保持为私有,并通过将它们分组到一个类文件中来允许通过对象 Mediator 进行访问:

    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)
        }
    }
    

    【讨论】:

    • 你有没有将它扩展到包含父子上下文的案例?
    • 我没有——我想这是我必须解决的问题。您能想到任何担心的理由吗?
    • 我过去也使用过这种模式,但由于更频繁地采用父/子上下文不再这样做。 (只是好奇你是否可以结合这两种做法。)
    • 您在第一个示例中描述的基本上是一个实体/存储库,想想数据映射器模式。干得好
    猜你喜欢
    • 2015-09-01
    • 1970-01-01
    • 2013-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-14
    • 2022-11-30
    相关资源
    最近更新 更多