版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.09.26 星期三 |
前言
数据是移动端的重点关注对象,其中有一条就是数据存储。CoreData是苹果出的数据存储和持久化技术,面向对象进行数据相关存储。感兴趣的可以看下面几篇文章。
1. iOS CoreData(一)
2. iOS CoreData实现数据存储(二)
3. Core Data详细解析(三) —— 一个简单的入门示例(一)
4. Core Data详细解析(四) —— 一个简单的入门示例(二)
5. Core Data详细解析(五) —— 基于多上下文的Core Data简单解析示例(一)
Editing on a Scratchpad - 在Scratchpad上编辑
现在,SurfJournal
在创建新日记帐分录或查看现有日记帐分录时使用主要上下文(coreDataStack.mainContext)
。这种方法没有错,开始项目按原样运作。
对于像这样的日记式应用程序,您可以通过将编辑或新条目视为一组更改(如便笺簿)来简化应用程序体系结构。在用户编辑日记帐分录时,您将更新managed object
的属性。更改完成后,您可以保存或丢弃它们,具体取决于用户想要执行的操作。
您可以将子managed object contexts
视为可以完全丢弃的临时暂存区,或者将更改保存并发送到父上下文。
但从技术上讲,子上下文是什么?
所有managed object contexts
都有一个父存储,您可以从中检索和更改managed objects
形式的数据,例如此项目中的JournalEntry
对象。通常,父存储是persistent store coordinator
,这是CoreDataStack
类提供的主上下文的情况。或者,您可以将给定上下文的父存储设置为另一个managed object context
,使其成为子上下文。
保存子上下文时,更改仅转到父上下文。 在保存父上下文之前,不会将对父上下文的更改发送到持久性存储协调器(persistent store coordinator)
。
在您跳入并添加子上下文之前,您需要了解当前查看和编辑操作的工作原理。
1. Viewing and Editing - 查看和编辑操作
操作的第一部分需要从主列表视图到日志详细信息视图进行划分。 打开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) 有两个segue:
SegueListToDetail
和SegueListToDetailAdd
。当用户点击表视图中的一行以查看或编辑以前的日记帐分录时,将运行前一个代码块中显示的第一个代码块。2) 接下来,您将获得用户最终会看到的
JournalEntryViewController
的引用。它出现在导航控制器内部,因此需要进行一些拆包。此代码还验证table view
中是否存在选定的索引路径。3) 接下来,您将使用获取的
results controller’s
的object(at:)
方法获取用户选择的JournalEntry
。4) 最后,在
JournalEntryViewController
实例上设置所有必需的变量。surfJournalEntry
变量对应于在步骤3中解析的JournalEntry
实体。上下文变量是用于任何操作的managed object context
;现在,它只使用主要上下文。JournalListViewController
将自身设置为JournalEntryViewController
的委托,以便在用户完成编辑操作时通知它。
SegueListToDetailAdd
类似于SegueListToDetail
,除了应用程序创建一个新的JournalEntry
实体而不是检索现有的实体。 当用户点击右上角的加号(+)按钮创建新的日记帐分录时,应用程序将执行SegueListToDetailAdd
。
现在您已了解两个segue
的工作原理,打开JournalEntryViewController.swift
并查看文件顶部的JournalEntryDelegate
协议:
protocol JournalEntryDelegate {
func didFinish(viewController: JournalEntryViewController,
didSave: Bool)
}
JournalEntryDelegate
协议非常简短,只包含一个方法:didFinish(viewController:didSave :)
。 协议要求委托实施的此方法指示用户是否已完成编辑或查看日记帐分录以及是否应保存任何更改。
要了解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)
}
下面进行详细分解:
1) 首先,使用
guard
语句检查didSave
参数。如果用户点击“保存”按钮而不是“取消”按钮,则会出现这种情况,因此应用应保存用户的数据。 guard语句还使用hasChanges
属性来检查是否有任何改变;如果没有任何改变,就没有必要浪费时间做更多的工作。2) 接下来,在
perform(_ :)
闭包内进行journalEntryViewController
上下文保存。代码将此上下文设置为主上下文;在这种情况下,它有点多余,因为只有一个上下文,但这不会改变行为。
稍后将子上下文添加到工作流后,JournalEntryViewController
上下文将与主上下文不同,从而使此代码成为必需。
如果保存失败,请调用fatalError
以使用相关错误信息中止应用程序。
3) 接下来,通过在
CoreDataStack.swift
中定义的saveContext
保存主上下文,将任何编辑持久保存到磁盘。4) 最后,关闭
JournalEntryViewController
。
注意:如果
managed object context
的类型为MainQueueConcurrencyType
,则不必在perform(_ :)
中包装代码,但使用它并没有什么坏处。如果您不知道上下文的类型,就像didFinish(viewController:didSave :)
中的情况一样,使用perform(_ :)
最安全,因此它将适用于父上下文和子上下文。
上述实现存在问题 - 您是否发现了它?
当应用程序添加新的旅行实体记录时,它会创建一个新对象并将其添加到managed object context
。 如果用户点击“取消”按钮,则应用程序将不会保存上下文,但新对象仍将存在。 如果用户然后添加并保存另一个条目,则取消的对象仍然存在! 你不会在UI中看到它,除非你有耐心一直滚动到最后,但它会显示在CSV
导出的底部。
您可以通过在用户取消视图控制器时删除对象来解决此问题。 但是,如果更改很复杂,涉及多个对象,或者要求您在编辑工作流程中更改对象的属性,该怎么办? 使用子上下文可以帮助您轻松管理这些复杂的情况。
2. Using Child Contexts for Sets of Edits - 使用子上下文编辑集
现在您已了解应用程序当前如何编辑和创建JournalEntry
实体,您将修改实现以将子managed object context
用作临时暂存区。
这很容易 - 你只需要修改segues
。 打开JournalListViewController.swift
并在prepare(for:sender :)
中找到SegueListToDetail
的以下代码:
detailViewController.journalEntry = surfJournalEntry
detailViewController.context =
surfJournalEntry.managedObjectContext
detailViewController.delegate = self
将上面的代码进行如下替换:
// 1
let childContext = NSManagedObjectContext(
concurrencyType: .mainQueueConcurrencyType)
childContext.parent = coreDataStack.mainContext
// 2
let childEntry = childContext.object(
with: surfJournalEntry.objectID) as? JournalEntry
// 3
detailViewController.journalEntry = childEntry
detailViewController.context = childContext
detailViewController.delegate = self
下面进行逐步解析:
1) 首先,使用
.mainQueueConcurrencyType
创建名为childContext
的新managed object context
。 在这里,您可以像创建managed object context
时通常那样设置父上下文而不是持久性存储协调器(persistent store coordinator)
。 在这里,您将parent
设置为CoreDataStack
的mainContext
。2) 接下来,使用子上下文的
object(with:)
方法检索相关的记录。 您必须使用object(with :)
来检索条目,因为managed objects
特定于创建它们的上下文。 但是,objectID
值并非特定于单个上下文,因此您可以在需要访问多个上下文中的对象时使用它们。3) 最后,在
JournalEntryViewController
实例上设置所有必需的变量。 这次,您使用childEntry
和childContext
而不是原始的surfJournalEntry
和surfJournalEntry.managedObjectContext
。
注意:您可能想知道为什么需要将
managed object
和managed object context
都传递给detailViewController
,因为managed object
已经有了上下文变量。 这是因为managed object
仅具有对上下文的弱weak
引用。 如果您没有传递上下文,ARC将从内存中删除上下文(因为没有其他内容retain
它),并且应用程序将不会按预期运行。
构建并运行您的应用程序,它应该像以前一样工作。 在这种情况下,应用程序没有明显的变化是好事;用户仍然可以点击一行来查看和编辑冲浪会话条目。
通过使用子上下文作为日志编辑的容器,您可以降低应用程序体系结构的复杂性。 通过在单独的上下文中进行编辑,取消或保存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 = 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 {
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 {
let fetchRequest:NSFetchRequest = 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) {
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 {
return NSFetchRequest(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"
}
}
下面看一下实现效果
后记
本篇主要讲述了基于多上下文的Core Data简单解析示例,感兴趣的给个赞或者关注~~~