iOS - 核心数据堆栈与主 NSManagedObjectContext 单例

iOS - Core Data Stack as singleton with main NSManagedObjectContext

我看过很多教程,它们确实帮助我理解父子托管对象上下文以及与此相关的其他内容。我准备开始在我的应用程序中使用它,但我有一个问题。为什么没有人使用单例来保持主要的托管对象上下文。我想从 AppDelegate 中提取与 Core Data 相关的对象并将其设置为 own class 会好得多吧?类似 Tutorial at raywenderlich.com 中的内容。但是他们仍然实例化 CoreDataStack class(这没问题,单例也必须实例化)并且在需要时他们在 prepareForSegue 中设置 managedObjectContext(并将其设置为 AppDelegate 的第一个视图控制器)。为什么不消除这种需求而只使用单例 CoreDataStack 并在需要时可以在每个控制器中使用 managedObjectContext?

第二个奖励问题:我认为控制器中的代码越少,其他 classes 中的代码越多越好。我认为这有助于提高可读性。那么,如果我将此代码从控制器中移出并将其设置为 CoreDataStack class 或其他一些有助于核心数据请求和响应的 class:

  func surfJournalFetchRequest() -> NSFetchRequest {

    let fetchRequest =
      NSFetchRequest(entityName: "JournalEntry")
    fetchRequest.fetchBatchSize = 20

    let sortDescriptor =
      NSSortDescriptor(key: "date", ascending: false)

    fetchRequest.sortDescriptors = [sortDescriptor]

    return fetchRequest
  }

我知道这是可能的,但它更好吗?如果你从我这里得到应用程序代码,如果在控制器中它会更好,它会是一行 CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?

如果我将这段代码插入到单例中并创建带有闭包的函数呢?我会在单例中创建子管理上下文,并在其中和控制器中执行所需的操作,我只是更改 UI:

  func exportCSVFile() {

    navigationItem.leftBarButtonItem = activityIndicatorBarButtonItem()

    let privateContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    privateContext.persistentStoreCoordinator = coreDataStack.context.persistentStoreCoordinator

    privateContext.performBlock { () -> Void in

      var fetchRequestError:NSError? = nil
      let results = privateContext.executeFetchRequest(self.surfJournalFetchRequest(), error: &fetchRequestError)
      if results == nil {
        println("ERROR: \(fetchRequestError)")
      }

      let exportFilePath = NSTemporaryDirectory() + "export.csv"
      let exportFileURL = NSURL(fileURLWithPath: exportFilePath)!
      NSFileManager.defaultManager().createFileAtPath(exportFilePath, contents: NSData(), attributes: nil)

      var fileHandleError: NSError? = nil
      let fileHandle = NSFileHandle(forWritingToURL: exportFileURL, error: &fileHandleError)
      if let fileHandle = fileHandle {

        for object in results! {
          let journalEntry = object as! JournalEntry

          fileHandle.seekToEndOfFile()
          let csvData = journalEntry.csv().dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
          fileHandle.writeData(csvData!)
        }

        fileHandle.closeFile()

        dispatch_async(dispatch_get_main_queue(), { () -> Void in
          self.navigationItem.leftBarButtonItem =
            self.exportBarButtonItem()
          println("Export Path: \(exportFilePath)")
          self.showExportFinishedAlertView(exportFilePath)
        })

      } else {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
          self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
          println("ERROR: \(fileHandleError)")
       })
      }
    }
  }

我只是想确保我的方法没问题,并且会比原来的更好。谢谢

我用单例模式构建了我的第一个核心数据应用程序。这对我来说似乎合乎逻辑,因为无论如何只有一个核心数据堆栈。我大错特错了,单例模式很快就乱七八糟了。我添加了越来越多的代码来将单例堆栈转变为有效的东西。最后我放弃了,我投入时间用依赖注入来取代单例混乱。

下面是我甩单例之前遇到的一些问题:

由于该应用保留了重要数据,我的用户要求备份功能。为了从备份中恢复,我切换了 sqlite 文件,然后我将创建一个新的核心数据堆栈。如果您使用拉模式从单例中获取 managedObjectContext,以干净的方式执行此操作几乎是不可能的。所以我切换核心数据堆栈的方法是告诉用户他们必须重新启动应用程序。接着是 exit()。这不是处理这个问题的最优雅的方式。

在 Apple 添加 childContexts 之后,我决定摆脱撤消管理器和上下文回滚,因为这对我来说从来没有 100% 有效。但是更改我的编辑 viewControllers 以便它们使用在用户点击 cancel 时被丢弃的子上下文,这是一个令人难以置信的痛苦行为,因为我现在混合了单例上下文和 viewController 本地上下文在一个 viewController.
为了编辑关系的目标,我在 editViewController 中设置了 editViewControllers。因为我在编辑 viewControllers 中创建了编辑上下文,所以我最终将不应该保存的数据保存到主上下文中。解释起来有点复杂,但是第二个 viewController 将新对象之类的东西保存到主上下文中,即使外部编辑 viewController 中的用户点击了取消。这总是导致孤立的对象。因此,我添加了更多代码来以一种减少单例的方式来改变单例。

我还有 CSV 导入功能,我想在用户按下 "Import" 之前向用户预览数据。我为此建立了一个全新的基础设施。首先,我将 CSV 解析为一个数据结构,该数据结构基本上复制了我的核心数据 类。然后我构建一个 viewController 来显示这些非核心数据 类,代码重复更多。我只会在用户按下导入时才开始创建核心数据对象。
在摆脱单例模式后,我可以重用现有的数据显示 viewController。我只想给它一个不同的上下文,在本例中是一个包含将要导入的数据的内存上下文。更干净,更少重复的代码。

我想其中一些问题并不是真正的单例问题。我只是太没经验了。
但我仍然强烈建议不要使用单例核心数据。


would be one line CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?

为此您不需要单例。像这样的东西应该在您为 JournalEntry 创建的 NSManagedObject 子类中。


And what about if I take this code and insert it to singleton and created function with closure? I would created child managed context in singleton and do needed operations in there and in controller I would just changed UI:

为什么不创建一个根本不需要内部状态的方法?

class func export(#context: NSManagedObjectContext, toCSVAtPath path: String, 
                  progress: ((current: Int, totalCount: Int) -> Void)?,
                completion: ((success: Bool, error: NSError?) -> Void)?) {

更加灵活。