版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2018.09.26 星期三 |
前言
數(shù)據(jù)是移動(dòng)端的重點(diǎn)關(guān)注對(duì)象,其中有一條就是數(shù)據(jù)存儲(chǔ)。CoreData是蘋果出的數(shù)據(jù)存儲(chǔ)和持久化技術(shù),面向?qū)ο筮M(jìn)行數(shù)據(jù)相關(guān)存儲(chǔ)。感興趣的可以看下面幾篇文章。
1. iOS CoreData(一)
2. iOS CoreData實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)(二)
3. Core Data詳細(xì)解析(三) —— 一個(gè)簡單的入門示例(一)
4. Core Data詳細(xì)解析(四) —— 一個(gè)簡單的入門示例(二)
5. Core Data詳細(xì)解析(五) —— 基于多上下文的Core Data簡單解析示例(一)
Editing on a Scratchpad - 在Scratchpad上編輯
現(xiàn)在,SurfJournal在創(chuàng)建新日記帳分錄或查看現(xiàn)有日記帳分錄時(shí)使用主要上下文(coreDataStack.mainContext)。這種方法沒有錯(cuò),開始項(xiàng)目按原樣運(yùn)作。
對(duì)于像這樣的日記式應(yīng)用程序,您可以通過將編輯或新條目視為一組更改(如便箋簿)來簡化應(yīng)用程序體系結(jié)構(gòu)。在用戶編輯日記帳分錄時(shí),您將更新managed object的屬性。更改完成后,您可以保存或丟棄它們,具體取決于用戶想要執(zhí)行的操作。
您可以將子managed object contexts視為可以完全丟棄的臨時(shí)暫存區(qū),或者將更改保存并發(fā)送到父上下文。
但從技術(shù)上講,子上下文是什么?
所有managed object contexts都有一個(gè)父存儲(chǔ),您可以從中檢索和更改managed objects形式的數(shù)據(jù),例如此項(xiàng)目中的JournalEntry對(duì)象。通常,父存儲(chǔ)是persistent store coordinator,這是CoreDataStack類提供的主上下文的情況。或者,您可以將給定上下文的父存儲(chǔ)設(shè)置為另一個(gè)managed object context,使其成為子上下文。

保存子上下文時(shí),更改僅轉(zhuǎn)到父上下文。 在保存父上下文之前,不會(huì)將對(duì)父上下文的更改發(fā)送到持久性存儲(chǔ)協(xié)調(diào)器(persistent store coordinator)。
在您跳入并添加子上下文之前,您需要了解當(dāng)前查看和編輯操作的工作原理。
1. Viewing and Editing - 查看和編輯操作
操作的第一部分需要從主列表視圖到日志詳細(xì)信息視圖進(jìn)行劃分。 打開JournalListViewController.swift并找到prepare(for:sender :):
// 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
讓我們一步步的看一下代碼:
1) 有兩個(gè)segue:
SegueListToDetail和SegueListToDetailAdd。當(dāng)用戶點(diǎn)擊表視圖中的一行以查看或編輯以前的日記帳分錄時(shí),將運(yùn)行前一個(gè)代碼塊中顯示的第一個(gè)代碼塊。2) 接下來,您將獲得用戶最終會(huì)看到的
JournalEntryViewController的引用。它出現(xiàn)在導(dǎo)航控制器內(nèi)部,因此需要進(jìn)行一些拆包。此代碼還驗(yàn)證table view中是否存在選定的索引路徑。3) 接下來,您將使用獲取的
results controller’s的object(at:)方法獲取用戶選擇的JournalEntry。4) 最后,在
JournalEntryViewController實(shí)例上設(shè)置所有必需的變量。surfJournalEntry變量對(duì)應(yīng)于在步驟3中解析的JournalEntry實(shí)體。上下文變量是用于任何操作的managed object context;現(xiàn)在,它只使用主要上下文。JournalListViewController將自身設(shè)置為JournalEntryViewController的委托,以便在用戶完成編輯操作時(shí)通知它。
SegueListToDetailAdd類似于SegueListToDetail,除了應(yīng)用程序創(chuàng)建一個(gè)新的JournalEntry實(shí)體而不是檢索現(xiàn)有的實(shí)體。 當(dāng)用戶點(diǎn)擊右上角的加號(hào)(+)按鈕創(chuàng)建新的日記帳分錄時(shí),應(yīng)用程序?qū)?zhí)行SegueListToDetailAdd。
現(xiàn)在您已了解兩個(gè)segue的工作原理,打開JournalEntryViewController.swift并查看文件頂部的JournalEntryDelegate協(xié)議:
protocol JournalEntryDelegate {
func didFinish(viewController: JournalEntryViewController,
didSave: Bool)
}
JournalEntryDelegate協(xié)議非常簡短,只包含一個(gè)方法:didFinish(viewController:didSave :)。 協(xié)議要求委托實(shí)施的此方法指示用戶是否已完成編輯或查看日記帳分錄以及是否應(yīng)保存任何更改。
要了解didFinish(viewController:didSave :)是如何工作的,切換回JournalListViewController.swift并找到該方法:
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)
}
下面進(jìn)行詳細(xì)分解:
1) 首先,使用
guard語句檢查didSave參數(shù)。如果用戶點(diǎn)擊“保存”按鈕而不是“取消”按鈕,則會(huì)出現(xiàn)這種情況,因此應(yīng)用應(yīng)保存用戶的數(shù)據(jù)。 guard語句還使用hasChanges屬性來檢查是否有任何改變;如果沒有任何改變,就沒有必要浪費(fèi)時(shí)間做更多的工作。2) 接下來,在
perform(_ :)閉包內(nèi)進(jìn)行journalEntryViewController上下文保存。代碼將此上下文設(shè)置為主上下文;在這種情況下,它有點(diǎn)多余,因?yàn)橹挥幸粋€(gè)上下文,但這不會(huì)改變行為。
稍后將子上下文添加到工作流后,JournalEntryViewController上下文將與主上下文不同,從而使此代碼成為必需。
如果保存失敗,請(qǐng)調(diào)用fatalError以使用相關(guān)錯(cuò)誤信息中止應(yīng)用程序。
3) 接下來,通過在
CoreDataStack.swift中定義的saveContext保存主上下文,將任何編輯持久保存到磁盤。4) 最后,關(guān)閉
JournalEntryViewController。
注意:如果
managed object context的類型為MainQueueConcurrencyType,則不必在perform(_ :)中包裝代碼,但使用它并沒有什么壞處。如果您不知道上下文的類型,就像didFinish(viewController:didSave :)中的情況一樣,使用perform(_ :)最安全,因此它將適用于父上下文和子上下文。
上述實(shí)現(xiàn)存在問題 - 您是否發(fā)現(xiàn)了它?
當(dāng)應(yīng)用程序添加新的旅行實(shí)體記錄時(shí),它會(huì)創(chuàng)建一個(gè)新對(duì)象并將其添加到managed object context。 如果用戶點(diǎn)擊“取消”按鈕,則應(yīng)用程序?qū)⒉粫?huì)保存上下文,但新對(duì)象仍將存在。 如果用戶然后添加并保存另一個(gè)條目,則取消的對(duì)象仍然存在! 你不會(huì)在UI中看到它,除非你有耐心一直滾動(dòng)到最后,但它會(huì)顯示在CSV導(dǎo)出的底部。
您可以通過在用戶取消視圖控制器時(shí)刪除對(duì)象來解決此問題。 但是,如果更改很復(fù)雜,涉及多個(gè)對(duì)象,或者要求您在編輯工作流程中更改對(duì)象的屬性,該怎么辦? 使用子上下文可以幫助您輕松管理這些復(fù)雜的情況。
2. Using Child Contexts for Sets of Edits - 使用子上下文編輯集
現(xiàn)在您已了解應(yīng)用程序當(dāng)前如何編輯和創(chuàng)建JournalEntry實(shí)體,您將修改實(shí)現(xiàn)以將子managed object context用作臨時(shí)暫存區(qū)。
這很容易 - 你只需要修改segues。 打開JournalListViewController.swift并在prepare(for:sender :)中找到SegueListToDetail的以下代碼:
detailViewController.journalEntry = surfJournalEntry
detailViewController.context =
surfJournalEntry.managedObjectContext
detailViewController.delegate = self
將上面的代碼進(jìn)行如下替換:
// 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
下面進(jìn)行逐步解析:
1) 首先,使用
.mainQueueConcurrencyType創(chuàng)建名為childContext的新managed object context。 在這里,您可以像創(chuàng)建managed object context時(shí)通常那樣設(shè)置父上下文而不是持久性存儲(chǔ)協(xié)調(diào)器(persistent store coordinator)。 在這里,您將parent設(shè)置為CoreDataStack的mainContext。2) 接下來,使用子上下文的
object(with:)方法檢索相關(guān)的記錄。 您必須使用object(with :)來檢索條目,因?yàn)?code>managed objects特定于創(chuàng)建它們的上下文。 但是,objectID值并非特定于單個(gè)上下文,因此您可以在需要訪問多個(gè)上下文中的對(duì)象時(shí)使用它們。3) 最后,在
JournalEntryViewController實(shí)例上設(shè)置所有必需的變量。 這次,您使用childEntry和childContext而不是原始的surfJournalEntry和surfJournalEntry.managedObjectContext。
注意:您可能想知道為什么需要將
managed object和managed object context都傳遞給detailViewController,因?yàn)?code>managed object已經(jīng)有了上下文變量。 這是因?yàn)?code>managed object僅具有對(duì)上下文的弱weak引用。 如果您沒有傳遞上下文,ARC將從內(nèi)存中刪除上下文(因?yàn)闆]有其他內(nèi)容retain它),并且應(yīng)用程序?qū)⒉粫?huì)按預(yù)期運(yùn)行。
構(gòu)建并運(yùn)行您的應(yīng)用程序,它應(yīng)該像以前一樣工作。 在這種情況下,應(yīng)用程序沒有明顯的變化是好事;用戶仍然可以點(diǎn)擊一行來查看和編輯沖浪會(huì)話條目。

通過使用子上下文作為日志編輯的容器,您可以降低應(yīng)用程序體系結(jié)構(gòu)的復(fù)雜性。 通過在單獨(dú)的上下文中進(jìn)行編輯,取消或保存managed object更改是微不足道的。
源碼
1. Swift源碼
下面看一下Swift源碼。
1. AppDelegate.swift
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var coreDataStack = CoreDataStack(modelName: "SurfJournalModel")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
_ = coreDataStack.mainContext
guard let navigationController = window?.rootViewController as? UINavigationController,
let listViewController = navigationController.topViewController as? JournalListViewController else {
fatalError("Application Storyboard mis-configuration")
}
listViewController.coreDataStack = coreDataStack
return true
}
func applicationWillTerminate(_ application: UIApplication) {
coreDataStack.saveContext()
}
}
2. JournalListViewController.swift
import UIKit
import CoreData
class JournalListViewController: UITableViewController {
// MARK: Properties
var coreDataStack: CoreDataStack!
var fetchedResultsController: NSFetchedResultsController<JournalEntry> = NSFetchedResultsController()
// MARK: IBOutlets
@IBOutlet weak var exportButton: UIBarButtonItem!
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 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)
let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
childContext.parent = coreDataStack.mainContext
let childEntry = childContext.object(with: surfJournalEntry.objectID) as? JournalEntry
detailViewController.journalEntry = childEntry
detailViewController.context = childContext
detailViewController.delegate = self
} else if segue.identifier == "SegueListToDetailAdd" {
guard let navigationController = segue.destination as? UINavigationController,
let detailViewController = navigationController.topViewController as? JournalEntryViewController else {
fatalError("Application storyboard mis-configuration")
}
let newJournalEntry = JournalEntry(context: coreDataStack.mainContext)
detailViewController.journalEntry = newJournalEntry
detailViewController.context = newJournalEntry.managedObjectContext
detailViewController.delegate = self
}
}
}
// MARK: IBActions
extension JournalListViewController {
@IBAction func exportButtonTapped(_ sender: UIBarButtonItem) {
exportCSVFile()
}
}
// MARK: Private
private extension JournalListViewController {
func configureView() {
fetchedResultsController = journalListFetchedResultsController()
}
func exportCSVFile() {
navigationItem.leftBarButtonItem = activityIndicatorBarButtonItem()
// 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)")
}
// 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("Export 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
}
// MARK: Export
func activityIndicatorBarButtonItem() -> UIBarButtonItem {
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
let barButtonItem = UIBarButtonItem(customView: activityIndicator)
activityIndicator.startAnimating()
return barButtonItem
}
func exportBarButtonItem() -> UIBarButtonItem {
return UIBarButtonItem(title: "Export", style: .plain, target: self, action: #selector(exportButtonTapped(_:)))
}
func showExportFinishedAlertView(_ exportPath: String) {
let message = "The exported CSV file can be found at \(exportPath)"
let alertController = UIAlertController(title: "Export Finished", message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .default)
alertController.addAction(dismissAction)
present(alertController, animated: true)
}
}
// MARK: NSFetchedResultsController
private extension JournalListViewController {
func journalListFetchedResultsController() -> NSFetchedResultsController<JournalEntry> {
let fetchedResultController = NSFetchedResultsController(fetchRequest: surfJournalFetchRequest(),
managedObjectContext: coreDataStack.mainContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultController.delegate = self
do {
try fetchedResultController.performFetch()
} catch let error as NSError {
fatalError("Error: \(error.localizedDescription)")
}
return fetchedResultController
}
func surfJournalFetchRequest() -> NSFetchRequest<JournalEntry> {
let fetchRequest:NSFetchRequest<JournalEntry> = JournalEntry.fetchRequest()
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(key: #keyPath(JournalEntry.date), ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
}
// MARK: NSFetchedResultsControllerDelegate
extension JournalListViewController: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
}
// MARK: UITableViewDataSource
extension JournalListViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.sections?[section].numberOfObjects ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SurfEntryTableViewCell
configureCell(cell, indexPath: indexPath)
return cell
}
private func configureCell(_ cell: SurfEntryTableViewCell, indexPath: IndexPath) {
let surfJournalEntry = fetchedResultsController.object(at: indexPath)
cell.dateLabel.text = surfJournalEntry.stringForDate()
guard let rating = surfJournalEntry.rating?.int32Value else { return }
switch rating {
case 1:
cell.starOneFilledImageView.isHidden = false
cell.starTwoFilledImageView.isHidden = true
cell.starThreeFilledImageView.isHidden = true
cell.starFourFilledImageView.isHidden = true
cell.starFiveFilledImageView.isHidden = true
case 2:
cell.starOneFilledImageView.isHidden = false
cell.starTwoFilledImageView.isHidden = false
cell.starThreeFilledImageView.isHidden = true
cell.starFourFilledImageView.isHidden = true
cell.starFiveFilledImageView.isHidden = true
case 3:
cell.starOneFilledImageView.isHidden = false
cell.starTwoFilledImageView.isHidden = false
cell.starThreeFilledImageView.isHidden = false
cell.starFourFilledImageView.isHidden = true
cell.starFiveFilledImageView.isHidden = true
case 4:
cell.starOneFilledImageView.isHidden = false
cell.starTwoFilledImageView.isHidden = false
cell.starThreeFilledImageView.isHidden = false
cell.starFourFilledImageView.isHidden = false
cell.starFiveFilledImageView.isHidden = true
case 5:
cell.starOneFilledImageView.isHidden = false
cell.starTwoFilledImageView.isHidden = false
cell.starThreeFilledImageView.isHidden = false
cell.starFourFilledImageView.isHidden = false
cell.starFiveFilledImageView.isHidden = false
default:
cell.starOneFilledImageView.isHidden = true
cell.starTwoFilledImageView.isHidden = true
cell.starThreeFilledImageView.isHidden = true
cell.starFourFilledImageView.isHidden = true
cell.starFiveFilledImageView.isHidden = true
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard case(.delete) = editingStyle else { return }
let surfJournalEntry = fetchedResultsController.object(at: indexPath)
coreDataStack.mainContext.delete(surfJournalEntry)
coreDataStack.saveContext()
}
}
// MARK: JournalEntryDelegate
extension JournalListViewController: JournalEntryDelegate {
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)
}
}
3. JournalEntryViewController.swift
import UIKit
import CoreData
// MARK: JournalEntryDelegate
protocol JournalEntryDelegate {
func didFinish(viewController: JournalEntryViewController, didSave: Bool)
}
class JournalEntryViewController: UITableViewController {
// MARK: Properties
var journalEntry: JournalEntry?
var context: NSManagedObjectContext!
var delegate:JournalEntryDelegate?
// MARK: IBOutlets
@IBOutlet weak var heightTextField: UITextField!
@IBOutlet weak var periodTextField: UITextField!
@IBOutlet weak var windTextField: UITextField!
@IBOutlet weak var locationTextField: UITextField!
@IBOutlet weak var ratingSegmentedControl: UISegmentedControl!
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
}
// MARK: Private
private extension JournalEntryViewController {
func configureView() {
guard let journalEntry = journalEntry else { return }
title = journalEntry.stringForDate()
heightTextField.text = journalEntry.height
periodTextField.text = journalEntry.period
windTextField.text = journalEntry.wind
locationTextField.text = journalEntry.location
guard let rating = journalEntry.rating else { return }
ratingSegmentedControl.selectedSegmentIndex = rating.intValue - 1
}
func updateJournalEntry() {
guard let entry = journalEntry else { return }
entry.date = Date()
entry.height = heightTextField.text
entry.period = periodTextField.text
entry.wind = windTextField.text
entry.location = locationTextField.text
entry.rating = NSNumber(value:ratingSegmentedControl.selectedSegmentIndex + 1)
}
}
// MARK: IBActions
extension JournalEntryViewController {
@IBAction func cancelButtonWasTapped(_ sender: UIBarButtonItem) {
delegate?.didFinish(viewController: self, didSave: false)
}
@IBAction func saveButtonWasTapped(_ sender: UIBarButtonItem) {
updateJournalEntry()
delegate?.didFinish(viewController: self, didSave: true)
}
}
4. SurfEntryTableViewCell.swift
import UIKit
class SurfEntryTableViewCell: UITableViewCell {
// MARK: IBOutlets
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var starOneImageView: UIImageView!
@IBOutlet weak var starTwoImageView: UIImageView!
@IBOutlet weak var starThreeImageView: UIImageView!
@IBOutlet weak var starFourImageView: UIImageView!
@IBOutlet weak var starFiveImageView: UIImageView!
@IBOutlet weak var starOneFilledImageView: UIImageView!
@IBOutlet weak var starTwoFilledImageView: UIImageView!
@IBOutlet weak var starThreeFilledImageView: UIImageView!
@IBOutlet weak var starFourFilledImageView: UIImageView!
@IBOutlet weak var starFiveFilledImageView: UIImageView!
}
5. CoreDataStack.swift
import CoreData
class CoreDataStack {
// MARK: Properties
private let modelName: String
lazy var mainContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
self.seedCoreDataContainerIfFirstLaunch()
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
// MARK: Initializers
init(modelName: String) {
self.modelName = modelName
}
}
// MARK: Internal
extension CoreDataStack {
func saveContext () {
guard mainContext.hasChanges else { return }
do {
try mainContext.save()
} catch let nserror as NSError {
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
// MARK: Private
private extension CoreDataStack {
func 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")
}
}
}
6. JournalEntry.swift
import Foundation
import CoreData
class JournalEntry: NSManagedObject {
@nonobjc public class func fetchRequest() -> NSFetchRequest<JournalEntry> {
return NSFetchRequest<JournalEntry>(entityName: "JournalEntry")
}
@NSManaged var date: Date?
@NSManaged var height: String?
@NSManaged var period: String?
@NSManaged var wind: String?
@NSManaged var location: String?
@NSManaged var rating: NSNumber?
}
7. JournalEntry+Helper.swift
import Foundation
import CoreData
extension JournalEntry {
func stringForDate() -> String {
guard let date = date else { return "" }
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
return dateFormatter.string(from: date)
}
func csv() -> String {
let coalescedHeight = height ?? ""
let coalescedPeriod = period ?? ""
let coalescedWind = wind ?? ""
let coalescedLocation = location ?? ""
let coalescedRating: String
if let rating = rating?.int32Value {
coalescedRating = String(rating)
} else {
coalescedRating = ""
}
return "\(stringForDate()),\(coalescedHeight),\(coalescedPeriod),\(coalescedWind),\(coalescedLocation),\(coalescedRating)\n"
}
}
下面看一下實(shí)現(xiàn)效果

后記
本篇主要講述了基于多上下文的Core Data簡單解析示例,感興趣的給個(gè)贊或者關(guān)注~~~
