Core Data 是一个强大的框架,用于管理 iOS 和 macOS 应用程序的数据模型。它可以帮助开发者快速地创建和维护数据库,并且可以轻松地将数据存储在本地文件中。Core Data 提供了一个面向对象的方法来管理数据,使得开发者可以快速而有效地处理大量的数据。
Core Data 的核心是一个关系型数据库,它使用 SQLite 作为存储引擎。它允许开发者将多个表格连接起来,并使用 SQL 语句来执行复杂的数据库查询。此外,Core Data 还包含一些高级功能,如对象图形映射 (Object Graph Mapping)、内存管理 (Memory Management) 和性能优化 (Performance Optimization) 等。
// 创建 Core Data Stack let persistentContainer = NSPersistentContainer(name: "MyDataModel") persistentContainer.loadPersistentStores { (description, error) in if let error = error { fatalError("Unresolved error \(error)") } } // 获取上下文对象 let context = persistentContainer.viewContext // 创建新的实体对象 let newEntity = NSEntityDescription.insertNewObject(forEntityName: "MyEntity", into: context) as! MyEntity // MyEntity 是你创建的实体名称 // 这里你可以保存新创建的实体对象 try context.save() // 查询所有实体对象 let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MyEntity") do { let results = try context.fetch(fetchRequest) for result in results as! [NSManagedObject] { print("result: \(result)") } } catch { print("Error fetching data") }
原文出处:http://chengway.in/post/ji-zhu/core-data-by-tutorials-bi-ji-ba
今天来学习一下多个context的情况,特别是在多线程环境下。第十章也是本书的最后一章,如果你对core data的其他内容感兴趣,可以去翻看之前的笔记,或直接购买《Core Data by Tutorials》
作者一开始介绍了几种使用多个context的情形,比如会阻塞UI的的任务,最好还是在后台线程单独使用一个context,和主线程context分开。还有处理临时编辑的数据时,使用一个child context也会很有帮助。
本章提供了一个冲浪评分的APP作为Start Project,你可以添加冲浪地点的评价,还可以将所有记录导出为CSV文件。
与之前章节不同的是,这个APP的初始数据存放在app bundle中,我们看看在Core Data stack中如何获取:
// 1 找到并创建一个URL引用
let seededDatabaseURL = bundle .URLForResource("SurfJournalDatabase",
withExtension: "sqlite")
// 2 尝试拷贝seeded database文件到document目录,只会拷贝一次,存在就会失败。
var fileManagerError:NSError? = nil
let didCopyDatabase = NSFileManager.defaultManager()
.copyItemAtURL(seededDatabaseURL!, toURL: storeURL,
error: &fileManagerError)
// 3 只有拷贝成功才会运行下面方法
if didCopyDatabase {
// 4 拷贝smh(shared memory file)
fileManagerError = nil
let seededSHMURL = bundle
.URLForResource("SurfJournalDatabase", withExtension: "sqlite-shm")
let shmURL = documentsURL.URLByAppendingPathComponent(
"SurfJournalDatabase.sqlite-shm")
let didCopySHM = NSFileManager.defaultManager()
.copyItemAtURL(seededSHMURL!, toURL: shmURL,
error: &fileManagerError)
if !didCopySHM {
println("Error seeding Core Data: (fileManagerError)")
abort()
}
// 5 拷贝wal(write-ahead logging file)
fileManagerError = nil
let walURL = documentsURL.URLByAppendingPathComponent(
"SurfJournalDatabase.sqlite-wal")
let seededWALURL = bundle
.URLForResource("SurfJournalDatabase", withExtension: "sqlite-wal")
let didCopyWAL = NSFileManager.defaultManager()
.copyItemAtURL(seededWALURL!, toURL: walURL,
error: &fileManagerError)
if !didCopyWAL {
println("Error seeding Core Data: (fileManagerError)")
abort()
}
println("Seeded Core Data")
}
// 6 指定store URL即可
var error: NSError? = nil
let options = [NSInferMappingModelAutomaticallyOption:true,
NSMigratePersistentStoresAutomaticallyOption:true]
store = psc.addPersistentStoreWithType(NSSQLiteStoreType,
configuration: nil,
URL: storeURL,
options: options,
error: &error)
// 7
if store == nil {
println("Error adding persistent store: (error)")
abort()
}
上面的方法除了拷贝sqlite文件,还拷贝了SHM (shared memory file) 和WAL (write-ahead logging) files,这都是为了并行读写的需要。无论那个文件出错了都直接让程序终止abort。
当我们导出数据时,会发现这个过程会阻塞UI。传统的方法是使用GCD在后台执行export操作,但Core data managed object contexts并不是线程安全的,也就是说你不能简单的开启一个后台线程然后使用相同的core data stack。
解决方法也很简单:针对export操作创建一个新的context放到一个私有线程中去执行,而不是在主线程里。
将数据导出为csv,其实很多场景都能用到,具体来学习一下:
先为实体JournalEntry子类添加一个csv string方法,将属性输出为字符串:
func csv() -> String {
let coalescedHeight = height ?? ""
let coalescedPeriod = period ?? ""
let coalescedWind = wind ?? ""
let coalescedLocation = location ?? ""
var coalescedRating:String
if let rating = rating?.intValue {
coalescedRating = String(rating)
} else {
coalescedRating = ""
}
return "(stringForDate()),(coalescedHeight)," +
"(coalescedPeriod),(coalescedWind)," +
"(coalescedLocation),(coalescedRating)n"
}
通过fetch得到所有的jouranlEntry实体,用NSFileManager在临时文件夹下创建一个csv文件并返回这个URL
// 1
var fetchRequestError: NSError? = nil
let results = coreDataStack.context.executeFetchRequest(
self.surfJournalFetchRequest(), error: &fetchRequestError)
if results == nil {
println("ERROR: (fetchRequestError)")
}
// 2
let exportFilePath = NSTemporaryDirectory() + "export.csv"
let exportFileURL = NSURL(fileURLWithPath: exportFilePath)!
NSFileManager.defaultManager().createFileAtPath(
exportFilePath, contents: NSData(), attributes: nil)
用这个URL初始化一个NSFileHandle,用for-in遍历取出每一个journalEntry实体,执行csv()将自身属性处理成字符串,然后用UTF8-encoded编码转换为NSData类型的data,最后NSFileHandle将data写入URL
// 3
var fileHandleError: NSError? = nil
let fileHandle = NSFileHandle(forWritingToURL: exportFileURL,
error: &fileHandleError)
if let fileHandle = fileHandle {
// 4
for object in results! {
let journalEntry = object as JournalEntry
fileHandle.seekToEndOfFile()
let csvData = journalEntry.csv().dataUsingEncoding(
NSUTF8StringEncoding, allowLossyConversion: false)
fileHandle.writeData(csvData!)
}
// 5
fileHandle.closeFile()
学习完如何将数据导出为csv,我们来进入本章真正的主题,创建一个私有的后台线程,把export操作放在这个后台线程中去执行。
// 1 创建一个使用私有线程的context,与main context共用一个persistentStoreCoordinator
let privateContext = NSManagedObjectContext(
concurrencyType: .PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator =
coreDataStack.context.persistentStoreCoordinator
// 2 performBlock这个方法会在context的线程上异步执行block里的内容
privateContext.performBlock { () -> Void in
// 3 获取所有的JournalEntry entities
var fetchRequestError:NSError? = nil
let results = privateContext.executeFetchRequest(
self.surfJournalFetchRequest(),
error: &fetchRequestError)
if results == nil {
println("ERROR: (fetchRequestError)")
}
......
在后台执行performBlock的过程中,所有UI相关的操作还是要回到主线程中来执行。
// 4
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem =
self.exportBarButtonItem()
println("Export Path: (exportFilePath)")
self.showExportFinishedAlertView(exportFilePath)
})
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
println("ERROR: (fileHandleError)") })
}
} // 5 closing brace for performBlock()
关于managed object context的concurrency types一共有三种类型:
本节介绍了另外一种情形,类似于便笺本,你在上面涂写,到最后你可以选择保存也可以选择丢弃掉。作者使用了一种child managed object contexts的方式来模拟这个便签本,要么发送这些changes到parent context保存,要么直接丢弃掉。
具体的技术细节是:所有的managed object contexts都有一个叫做parent store(父母空间)的东西,用来检索和修改数据(具体数据都是managed objects形式)。进一步讲,the parent store其实就是一个persistent store coordinator,比如main context,他的parent store就是由CoreDataStack提供的persistent store coordinator。相对的,你可以将一个context设置为另一个context的parent store,其中一个context就是child context。而且当你保存这个child context时,这些changes只能到达parent context,不会再向更高的parent context传递(除非parent context save)。
关于这个冲浪APP还是有个小问题,当添加了一个新的journal entry后,就会创建新的object1添加到context中,如果这时候点击Cancel按钮,应用是不会保存到context,但这个object1会仍然存在,这个时候,再增加另一个object2然后保存到context,此时object1这个被取消的对象仍然会出现在table view中。
你可以在cancel的时候通过简单的删除操作来解决这个issue,但是如果操作更加复杂还是使用一个临时的child context更加简单。
// 1
let childContext = NSManagedObjectContext(
concurrencyType: .MainQueueConcurrencyType)
childContext.parentContext = coreDataStack.context
// 2
let childEntry = childContext.objectWithID(
surfJournalEntry.objectID) as JournalEntry
// 3
detailViewController.journalEntry = childEntry
detailViewController.context = childContext
detailViewController.delegate = self
创建一个childContext,parent store设为main context。这里使用了objectID来获取journal entry。因为managed objects只特定于自己的context的,而objectID针对所有的context都是唯一的,所以childContext要使用objectID来获取mainContext中的managed objects。
最后一点要注意的是注释3,这里同时为detailViewController传递了managed object(childEntry)和managed object context(childContext),为什么不只传递managed object呢,他可以通过属性managed object context来得到context呀,原因就在于managed object对于context仅仅是弱引用,如果不传递context,ARC就有可能将其移除,产生不可控结果。
历时一周终于写完了,通过对Core Data的系统学习还是收获不小的:)
云计算提供了一个令人难以置信的计算平台。用户通过点击几下用户可以以每小时不到10美分的价格租用在云中的服务器,节约了使用物...
jQuery event.stopPropagation() 方法jQuery 事件方法实例 阻止 click 事件冒泡到父元素:$(span).click(function(event){ event...
jQuery 效果参考手册实例显示出隐藏的 p 元素。$(.btn2).click(function(){$(p).show();});亲自试一试定义和用法如果被选元素已...
jQuery 效果参考手册实例使用淡入效果来显示一个隐藏的 p 元素:$(.btn2).click(function(){$(p).fadeIn();});亲自试一试定义和...
jQuery 文档操作参考手册实例在每个 p 元素的内容上包围 b 元素:$(.btn1).click(function(){ $(p).wrapInner(b/b);});亲自试一...