Core Data詳細(xì)解析(六) —— 基于多上下文的Core Data簡單解析示例(二)

版本記錄

版本號(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:SegueListToDetailSegueListToDetailAdd。當(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’sobject(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è)置為CoreDataStackmainContext。

  • 2) 接下來,使用子上下文的object(with:)方法檢索相關(guān)的記錄。 您必須使用object(with :)來檢索條目,因?yàn)?code>managed objects特定于創(chuàng)建它們的上下文。 但是,objectID值并非特定于單個(gè)上下文,因此您可以在需要訪問多個(gè)上下文中的對(duì)象時(shí)使用它們。

  • 3) 最后,在JournalEntryViewController實(shí)例上設(shè)置所有必需的變量。 這次,您使用childEntrychildContext而不是原始的surfJournalEntrysurfJournalEntry.managedObjectContext。

注意:您可能想知道為什么需要將managed objectmanaged 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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容