首页 iOS.& Swift Books 通过教程的核心数据

9
多个托管对象上下文 由matthew morey写的

托管对象上下文是内存中的暂存器暂存器,用于与托管对象一起使用。在第3章“核心数据堆栈”中,您了解到托管对象上下文如何与核心数据堆栈中的其他类合作。

大多数应用程序只需要一个托管对象上下文。大多数核心数据应用中的默认配置是与主队列关联的单个托管对象上下文。多个托管对象上下文使您的应用程序更难调试;在每种情况下,它都不是您在每个应用中使用的东西。

已经说,某些情况确实保证使用多个托管对象上下文。例如,长期运行的任务(例如导出数据)将阻止仅使用单个主队列托管对象上下文的应用程序的主线程并导致UI口吃。

在其他情况下,例如当正在向用户数据进行编辑时,将托管对象上下文视为应用程序可以丢弃的一组更改,这是有助于如果它不再需要它们。使用子上下文使其成为可能。

在本章中,您将了解通过添加多种环境拍摄日记应用冲浪者和改善它在几个方面的多个管理对象上下文。

笔记:如果常见的核心数据短语如托管对象子类和持久性商店协调器不响铃声,或者如果您不确定核心数据堆栈应该做什么,您可能希望读取或重新读取前三章章节这本书在继续之前。本章涵盖了高级主题,并假设您已经知道基础知识。

入门

本章的Starter项目是冲浪者的简单期刊应用程序。在每个冲浪会话之后,冲浪者可以使用该应用程序创建一个新的日记帐分录,以记录越来越多的海洋参数,例如膨胀高度或期间,并将会议从1到5。伙计汇率,如果您不喜欢挂十个和挂起喝桶,不用担心,胸罩。只需用您最喜欢的选择更换冲浪术语“!

介绍冲浪

转到本章的文件并找到 Surfjournal. 初学者项目。打开项目,然后构建并运行应用程序。

核心数据堆栈

打开 coredatastack.swift. and find the following code in seedCoreDataContainerIfFirstLaunch():

// 1
let previouslyLaunched =
  UserDefaults.standard.bool(forKey: "previouslyLaunched")
if !previouslyLaunched {
  UserDefaults.standard.set(true, forKey: "previouslyLaunched")

  // Default directory where the CoreDataStack will store its files
  let directory = NSPersistentContainer.defaultDirectoryURL()
  let url = directory.appendingPathComponent(
    modelName + ".sqlite")

  // 2: Copying the SQLite file
  let seededDatabaseURL = Bundle.main.url(
    forResource: modelName,
    withExtension: "sqlite")!

  _ = try? FileManager.default.removeItem(at: url)

  do {
    try FileManager.default.copyItem(at: seededDatabaseURL,
                                     to: url)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }  
  // 3: Copying the SHM file
  let seededSHMURL = Bundle.main.url(forResource: modelName,
    withExtension: "sqlite-shm")!
  let shmURL = directory.appendingPathComponent(
    modelName + ".sqlite-shm")

  _ = try? FileManager.default.removeItem(at: shmURL)

  do {
    try FileManager.default.copyItem(at: seededSHMURL,
                                     to: shmURL)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }

  // 4: Copying the WAL file
  let seededWALURL = Bundle.main.url(forResource: modelName,
    withExtension: "sqlite-wal")!
  let walURL = directory.appendingPathComponent(
    modelName + ".sqlite-wal")

  _ = try? FileManager.default.removeItem(at: walURL)

  do {
    try FileManager.default.copyItem(at: seededWALURL,
                                     to: walURL)
  } catch let nserror as NSError {
    fatalError("Error: \(nserror.localizedDescription)")
  }

  print("Seeded Core Data")
}

在背景中做工作

如果你还没有这样做,请点按 出口 按钮在左上角,然后立即尝试滚动冲浪会话日记帐分录列表。注意什么?导出操作需要几秒钟,它可以防止UI响应触摸事件,例如滚动。

导出数据

Start by viewing how the app creates the CSV strings for the JournalEntry entity. Open JUSHENTRY + HELPER.SWIFT. and find csv():

func csv() -> String {
  let coalescedHeight = height ?? ""
  let coalescedPeriod = period ?? ""
  let coalescedWind = wind ?? ""
  let coalescedLocation = location ?? ""
  let coalescedRating: String
  if let rating = rating?.int16Value {
    coalescedRating = String(rating)
  } else {
    coalescedRating = ""
  }

  return "\(stringForDate()),\(coalescedHeight),\(coalescedPeriod),\(coalescedWind),\(coalescedLocation),\(coalescedRating)\n"
}
// 1
let context = coreDataStack.mainContext
var results: [JournalEntry] = []
do {
  results = try context.fetch(self.surfJournalFetchRequest())
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
}

// 2
let exportFilePath = NSTemporaryDirectory() + "export.csv"
let exportFileURL = URL(fileURLWithPath: exportFilePath)
FileManager.default.createFile(atPath: exportFilePath,
  contents: Data(), attributes: nil)

// 3
let fileHandle: FileHandle?
do {
  fileHandle = try FileHandle(forWritingTo: exportFileURL)
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
  fileHandle = nil
}

if let fileHandle = fileHandle {
  // 4
  for journalEntry in results {
    fileHandle.seekToEndOfFile()
    guard let csvData = journalEntry
      .csv()
      .data(using: .utf8, allowLossyConversion: false) else {
        continue
    }

    fileHandle.write(csvData)
  }

  // 5
  fileHandle.closeFile()

  print("出口 Path: \(exportFilePath)")
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
  self.showExportFinishedAlertView(exportFilePath)

} else {
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
}

出口在背景中

您希望UI继续而出口正在发生的工作。要解决该问题,用户界面,你会在一个私人的背景情况下,而不是在主背景执行导出操作。

// 1
let context = coreDataStack.mainContext
var results: [JournalEntry] = []
do {
  results = try context.fetch(self.surfJournalFetchRequest())
} catch let error as NSError {
  print("ERROR: \(error.localizedDescription)")
}
// 1
coreDataStack.storeContainer.performBackgroundTask { context in
  var results: [JournalEntry] = []
  do {
    results = try context.fetch(self.surfJournalFetchRequest())
  } catch let error as NSError {
    print("ERROR: \(error.localizedDescription)")
  }
  print("出口 Path: \(exportFilePath)")
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
  self.showExportFinishedAlertView(exportFilePath)
} else {
  self.navigationItem.leftBarButtonItem =
    self.exportBarButtonItem()
}
    print("出口 Path: \(exportFilePath)")
    // 6
    DispatchQueue.main.async {
      self.navigationItem.leftBarButtonItem =
        self.exportBarButtonItem()
      self.showExportFinishedAlertView(exportFilePath)
    }
  } else {
    DispatchQueue.main.async {
      self.navigationItem.leftBarButtonItem =
        self.exportBarButtonItem()
    }
  }
} // 7 Closing brace for performBackgroundTask

编辑刮板

Right now, SurfJournal uses the main context (coreDataStack.mainContext) when creating a new journal entry or viewing an existing one. There’s nothing wrong with this approach; the starter project works as-is.

观看和编辑

操作的第一部分需要从主列表视图中搜索到日志细节视图。

// 1
if segue.identifier == "SegueListToDetail" {
  // 2
  guard let navigationController =
    segue.destination as? UINavigationController,
    let detailViewController =
      navigationController.topViewController
        as? JournalEntryViewController,
    let indexPath = tableView.indexPathForSelectedRow else {
      fatalError("Application storyboard mis-configuration")
  }
  // 3
  let surfJournalEntry =
    fetchedResultsController.object(at: indexPath)
  // 4
  detailViewController.journalEntry = surfJournalEntry
  detailViewController.context =
    surfJournalEntry.managedObjectContext
  detailViewController.delegate = self
protocol JournalEntryDelegate {
  func didFinish(viewController: JournalEntryViewController,
                 didSave: Bool)
}
func didFinish(viewController: JournalEntryViewController,
               didSave: Bool) {
  // 1
  guard didSave,
    let context = viewController.context,
    context.hasChanges else {
      dismiss(animated: true)
      return
  }
  // 2
  context.perform {
    do {
      try context.save()
    } catch let error as NSError {
      fatalError("Error: \(error.localizedDescription)")
    }
    // 3
    self.coreDataStack.saveContext()
  }
  // 4
  dismiss(animated: true)
}
If the save fails, call `fatalError` to abort the app with the relevant error information.

使用子上下文进行编辑集

Now that you know how the app currently edits and creates JournalEntry entities, you’ll modify the implementation to use a child managed object context as a temporary scratch pad.

detailViewController.journalEntry = surfJournalEntry
detailViewController.context =
  surfJournalEntry.managedObjectContext
detailViewController.delegate = self
// 1
let childContext = NSManagedObjectContext(
  concurrencyType: .mainQueueConcurrencyType)
childContext.parent = coreDataStack.mainContext

// 2
let childEntry = childContext.object(
  with: surfJournalEntry.objectID) as? JournalEntry

// 3
detailViewController.journalEntry = childEntry
detailViewController.context = childContext
detailViewController.delegate = self

关键点

  • 托管对象上下文中内存暂存暂存器,用于与托管对象一起使用。
  • 私有背景上下文可用于防止阻塞主和i。
  • 上下文与特定队列相关联,只应在这些队列上访问。
  • 儿童上下文可以通过节省或抛出简单的编辑来简化应用程序的架构。
  • 托管对象紧密绑定到它们的上下文,也不能与其他上下文一起使用。
  • 冲浪者谈话有趣。

挑战

通过您的新发现知识,尝试更新 segelisttodetailadd 添加新日记帐分录时使用子上下文。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您对UI,UX,高亮反馈,或者我们的在线读者的其他功能,您可以付款给设计团队与下面的表格:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁印刷书,以及我们的书籍和视频目录,具有订阅职业raywenderlich.com。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。