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

版本記錄

版本號(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:SegueListToDetailSegueListToDetailAdd。當(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’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é)議非常簡(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è)置為CoreDataStackmainContext

  • 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è)置所有必需的變量。 這次井仰,您使用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)擊一行來(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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纬乍,隨后出現(xiàn)的幾起案子碱茁,更是在濱河造成了極大的恐慌,老刑警劉巖仿贬,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纽竣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诅蝶,警方通過(guò)查閱死者的電腦和手機(jī)退个,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)调炬,“玉大人语盈,你說(shuō)我怎么就攤上這事$峙荩” “怎么了刀荒?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棘钞。 經(jīng)常有香客問(wèn)我缠借,道長(zhǎng),這世上最難降的妖魔是什么宜猜? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任泼返,我火速辦了婚禮,結(jié)果婚禮上姨拥,老公的妹妹穿的比我還像新娘绅喉。我一直安慰自己,他們只是感情好叫乌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布柴罐。 她就那樣靜靜地躺著,像睡著了一般憨奸。 火紅的嫁衣襯著肌膚如雪革屠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音似芝,去河邊找鬼那婉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛国觉,可吹牛的內(nèi)容都是我干的吧恃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼麻诀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痕寓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蝇闭,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤噪奄,失蹤者是張志新(化名)和其女友劉穎雁歌,沒想到半個(gè)月后他托,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祸憋,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年逻悠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了元践。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡童谒,死狀恐怖单旁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饥伊,我是刑警寧澤象浑,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站琅豆,受9級(jí)特大地震影響愉豺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茫因,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一蚪拦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冻押,春花似錦驰贷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)孩擂。三九已至狼渊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狈邑。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工城须, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人米苹。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓糕伐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蘸嘶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子良瞧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容