版本記錄
版本號(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è)簡(jiǎn)單的入門示例(一)
4. Core Data詳細(xì)解析(四) —— 一個(gè)簡(jiǎn)單的入門示例(二)
5. Core Data詳細(xì)解析(五) —— 基于多上下文的Core Data簡(jiǎn)單解析示例(一)
Editing on a Scratchpad - 在Scratchpad上編輯
現(xiàn)在法绵,SurfJournal
在創(chuàng)建新日記帳分錄或查看現(xiàn)有日記帳分錄時(shí)使用主要上下文(coreDataStack.mainContext)
箕速。這種方法沒有錯(cuò),開始項(xiàng)目按原樣運(yùn)作朋譬。
對(duì)于像這樣的日記式應(yīng)用程序弧满,您可以通過(guò)將編輯或新條目視為一組更改(如便箋簿)來(lái)簡(jiǎn)化應(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) 接下來(lái),您將獲得用戶最終會(huì)看到的
JournalEntryViewController
的引用艘刚。它出現(xiàn)在導(dǎo)航控制器內(nèi)部管宵,因此需要進(jìn)行一些拆包。此代碼還驗(yàn)證table view
中是否存在選定的索引路徑攀甚。3) 接下來(lái)箩朴,您將使用獲取的
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é)議非常簡(jiǎn)短称鳞,只包含一個(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
語(yǔ)句檢查didSave
參數(shù)候齿。如果用戶點(diǎn)擊“保存”按鈕而不是“取消”按鈕靶瘸,則會(huì)出現(xiàn)這種情況,因此應(yīng)用應(yīng)保存用戶的數(shù)據(jù)毛肋。 guard語(yǔ)句還使用hasChanges
屬性來(lái)檢查是否有任何改變怨咪;如果沒有任何改變,就沒有必要浪費(fèi)時(shí)間做更多的工作润匙。2) 接下來(lái)诗眨,在
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) 接下來(lái)撩穿,通過(guò)在
CoreDataStack.swift
中定義的saveContext
保存主上下文,將任何編輯持久保存到磁盤食寡。4) 最后抵皱,關(guān)閉
JournalEntryViewController
。
注意:如果
managed object context
的類型為MainQueueConcurrencyType
锭弊,則不必在perform(_ :)
中包裝代碼擂错,但使用它并沒有什么壞處樱蛤。如果您不知道上下文的類型昨凡,就像didFinish(viewController:didSave :)
中的情況一樣,使用perform(_ :)
最安全蚂四,因此它將適用于父上下文和子上下文哪痰。
上述實(shí)現(xiàn)存在問(wè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)出的底部。
您可以通過(guò)在用戶取消視圖控制器時(shí)刪除對(duì)象來(lái)解決此問(wèn)題授药。 但是,如果更改很復(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) 接下來(lái)繁疤,使用子上下文的
object(with:)
方法檢索相關(guān)的記錄。 您必須使用object(with :)
來(lái)檢索條目稠腊,因?yàn)?code>managed objects特定于創(chuàng)建它們的上下文躁染。 但是架忌,objectID
值并非特定于單個(gè)上下文吞彤,因此您可以在需要訪問(wèn)多個(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)擊一行來(lái)查看和編輯沖浪會(huì)話條目窜管。
通過(guò)使用子上下文作為日志編輯的容器,您可以降低應(yīng)用程序體系結(jié)構(gòu)的復(fù)雜性稚机。 通過(guò)在單獨(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簡(jiǎn)單解析示例失乾,感興趣的給個(gè)贊或者關(guān)注~~~