1.你造吗?
对于ios编程人员来说,提起数据持久性存储
都会有这么几个概念:
- 文件写入
- 对象归档
- SQLite数据库
- CoreData
- NSUserDefault
这几种存储方式各自都有自己的特点,开发者一般都可根据不同的需求不同的场合去选择不同的存储方案。
然而几乎每一次关于iOS技术的交流或讨论都会被提到一个问题,那就是“你是用哪种方法做数据持久化”,因为不同的存储方案决定了一个APP运行效率,所以而且大家对这个问题的热情始终高涨。
数据储存,首先要明确区分两个很重要概念,数据结构和储存方式。所谓数据结构就是数据存在的形式。除了基本的NSDictionary、NSArray和NSSet这些对象,还有更复杂的如:关系模型、对象图和属性列表多种结构。而存储方式则简单的分为两种:内存与闪存。内存存储是临时的,运行时有效的,但效率高,而闪存则是一种持久化存储,但产生I/O消耗,效率相对低。把内存数据转移到闪存中进行持久化的操作称成为归档。二者结合起来才是完整的数据存储方案,我们刚刚提及的那些:SQLite、CoreData、NSUserDefaults等都是属于数据存储方案。
2.揭开CoreData神秘面纱
今天这里主要描述Coredata的基本使用和一套高效能组合拳。有人说Core Data是iOS编程乃至Mac编程中使用持久性数据存储的最佳方式。本质上,Core Data使用的就是SQLite,但是通过一系列特性避免了使用SQL的一些列的麻烦,不仅如此,他还能够合理管理内存,好处很多非常便利,我们推荐使用。
我们来看看官方的定义,CoreData是一个支持持久化的,对象图和生命周期的自动化管理方案。严格意义上说CoreData是一个管理方案,他的持久化可以通过SQLite、XML或二进制文件储存。如官方定义所说,CoreData的作用远远不止储存数据这么简单,它可以把整个应用中的对象建模并进行自动化的管理。
3.CoreData基本应用
下面,我们省点口水直奔主题吧,coredata我们代码见。
在创建项目的时候我们选择勾选使用coredata,从而在我们的appdelegate中多了这么些代码:
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "Grandre.GrCoreData" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("GrCoreData", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
我们先不管它,继续直奔应用内部->—>
import UIKit
import CoreData
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var btn1:UIButton!
var btn2:UIButton!
var nameTextF:UITextField!
var priceTextF:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.hidden = false
self.navigationController?.navigationBar.barStyle = .Black
btn1 = UIButton(frame: CGRectMake(10,100,200,30))
btn1.backgroundColor = UIColor.blueColor()
btn1.setTitle("Next Page", forState: .Normal)
btn1.addTarget(self, action: #selector(self.presentNextView), forControlEvents: .TouchUpInside)
self.view.addSubview(btn1)
btn2 = UIButton(frame: CGRectMake(10,250,200,30))
btn2.backgroundColor = UIColor.blueColor()
btn2.setTitle("保存", forState: .Normal)
btn2.addTarget(self, action: #selector(self.save), forControlEvents: .TouchUpInside)
self.view.addSubview(btn2)
self.nameTextF = UITextField(frame: CGRectMake(10, 150, 200, 30))
self.nameTextF.backgroundColor = UIColor.brownColor()
self.view.addSubview(self.nameTextF)
self.priceTextF = UITextField(frame: CGRectMake(10, 200, 200, 30))
self.priceTextF.backgroundColor = UIColor.brownColor()
self.view.addSubview(self.priceTextF)
zeng增()
zeng增2()
cha查()
shan删()
cha查()
}
func presentNextView(){
let vc = TableViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
/**
把两个textfield的值保存起来
*/
func save(){
let object = NSEntityDescription.insertNewObjectForEntityForName("BOOK", inManagedObjectContext: appDelegate.managedObjectContext)
object.setValue(self.nameTextF.text, forKey: "name")
object.setValue(Float(self.priceTextF.text!), forKey: "price")
appDelegate.saveContext()
}
/**
zeng增()
*/
func zeng增(){
//向指定实体中插入托管对象,也可以理解为对象的实例化
let object = NSEntityDescription.insertNewObjectForEntityForName("BOOK", inManagedObjectContext: appDelegate.managedObjectContext)
object.setValue("grandre", forKey: "name")
object.setValue(1.211, forKey: "price")
appDelegate.saveContext()
}
/**
zeng增2(),第二种增加实例的方法,前提是有生成对应的类。推荐此方法,可充分利用类的属性功能。
*/
func zeng增2(){
let entity = NSEntityDescription.entityForName("BOOK", inManagedObjectContext: appDelegate.managedObjectContext)
let book2 = BOOK(entity: entity!, insertIntoManagedObjectContext: appDelegate.managedObjectContext)
book2.name = "grandre"
book2.price = 3.3
appDelegate.saveContext()
}
/**
cha查()
*/
func cha查(){
//首先,规定获取数据的实体
let request = NSFetchRequest(entityName: "BOOK")
//配置查询条件,如果有需要还可以配置结果排序
let predicate = NSPredicate(format: "%K == %@", "name", "grandre")
request.predicate = predicate
var result:[NSManagedObject] = []
do{
//进行查询,结果是一个托管对象数组
result = try appDelegate.managedObjectContext.executeFetchRequest(request) as! [NSManagedObject]
} catch {}
print(result.count)
for item in result {
//用键值对的方式获取各个值
print( "书名:\(item.valueForKey("name") as! String) 价格:\(item.valueForKey("price") as! NSNumber)\n")
}
}
/**
shan删(),其实就是先查后删除
*/
func shan删(){
let request = NSFetchRequest(entityName: "BOOK")
let predicate = NSPredicate(format: "%K == %@","name", "grandre")
request.predicate = predicate
var result:[NSManagedObject] = []
do{
result = try appDelegate.managedObjectContext.executeFetchRequest(request) as! [NSManagedObject]
} catch {}
if result.count != 0{
for item in result {
appDelegate.managedObjectContext.deleteObject(item)
print("delete imte \(item)")
}
}
appDelegate.saveContext()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
此时,代码已经定义了两个button两个textfield。同时,定义了增,删,查基本操作。有了这些基本操作,现在应该明白了appdelegate中代码的意思了吧?
这里重点提一下func zeng增2(),创建BOOK实例前首先要做这些操作:选中要为其创建类的实体 ->Editor -> Create NSManagedObject Subclass... ,之后会自动创建两个文件 实体名.swift和实体名+CoreDataProperties.swift。
4.CoreData重磅一击
go on !终于到了高能组合拳的出击了!之所以高效能,是因为NSFetchedResultsController与UITableViewController的完美结合,这是苹果官方的心愿。这中间涉及到了tableview的高级编辑功能以及数据逻辑的更新。细节,代码见!
import UIKit
import CoreData
class TableViewController: UITableViewController,NSFetchedResultsControllerDelegate {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var fetchResultsController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
// self.clearsSelectionOnViewWillAppear = false
self.navigationItem.rightBarButtonItem = self.editButtonItem()
let fetchRequest = NSFetchRequest(entityName: "BOOK")
/**
必须要加入排序
*/
let sortDesctiptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDesctiptor]
self.fetchResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.appDelegate.managedObjectContext, sectionNameKeyPath: "name", cacheName: nil)
self.fetchResultsController.delegate = self
do{
try fetchResultsController.performFetch()
}catch let error as NSError{
print("Error:\(error.localizedDescription)")
}
}
/**
下面四个方法属于NSFetchedResultsController代理。
*/
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
/**
如果涉及到section的增加删除,就必须实现此代理方法
*/
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
if type == NSFetchedResultsChangeType.Delete{
self.tableView.deleteSections(NSIndexSet.init(index: sectionIndex), withRowAnimation: .Fade)
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
if let _newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([_newIndexPath], withRowAnimation: .Automatic)
}
break
case .Delete:
if let _indexPath = indexPath {
self.tableView.deleteRowsAtIndexPaths([_indexPath], withRowAnimation: .Automatic)
}
break
case .Update:
if let _indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([_indexPath], withRowAnimation: .Automatic)
}
default:
tableView.reloadData()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
//必须要为footer设置高度>=1,才能后面设置footview。
override func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
/**
如果是最后一个section,则返回uiview,可以把后面的空cell去掉。
*/
override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return section == (self.fetchResultsController.sections?.count)! - 1 ?UIView():nil
}
/**
这是直接返回fetchResultsController.sections?.count,充分利用fetchResultsController带来的便利
*/
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return (fetchResultsController.sections?.count)!
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchResultsController.sections![section].numberOfObjects
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell")
// Configure the cell...
let object = fetchResultsController.objectAtIndexPath(indexPath) as! BOOK
cell.textLabel?.text = object.name
cell.detailTextLabel?.text = object.price?.stringValue
return cell
}
// 默认全部可编辑
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.Delete
}
/**
如果自定义了editActionsForRowAtIndexPath,下面这个代理不会再执行
*/
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let object = self.fetchResultsController.objectAtIndexPath(indexPath)
self.appDelegate.managedObjectContext.deleteObject(object as! NSManagedObject)
do{
try self.appDelegate.managedObjectContext.save()
}catch{
}
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let 分享行为 = UITableViewRowAction(style: .Default, title: "分享") { (action, indexPath) -> Void in
let alert = UIAlertController(title: "分享到",message: "请选择您要分享的社交类型", preferredStyle: .ActionSheet)
let qqAction = UIAlertAction(title: "qq",style: .Default, handler: nil)
let weiboAction = UIAlertAction(title: "微博",style: .Default, handler: nil)
let wxAction = UIAlertAction(title: "微信",style: .Default, handler: nil)
alert.addAction(qqAction)
alert.addAction(weiboAction)
alert.addAction(wxAction)
self.presentViewController(alert, animated: true, completion: nil)
}
分享行为.backgroundColor = UIColor(red: 218/255, green: 225/255, blue: 218/255, alpha: 1)
let 删除行为 = UITableViewRowAction(style: .Default, title: "删除") { (action, indexPath) -> Void in
let buffer = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
let restaurantToDel = self.fetchResultsController.objectAtIndexPath(indexPath) as! BOOK
buffer?.deleteObject(restaurantToDel)
do {
try buffer?.save()
} catch {
print(error)
}
}
return [分享行为, 删除行为]
}
}
有必要再次点名的是,在创建NSFetchedResultsController的时候如果指明了sectionNameKeyPath并正确使用的话,往往可以达到事半功倍的效果,这就需要客官在代码的调试当中慢慢体会了。
self.fetchResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.appDelegate.managedObjectContext, sectionNameKeyPath: "name", cacheName: nil)
这套组合拳,如果结合到自己的项目当中,那可谓是一等一的高手。虽然这只是coredata的冰山一角,但也有着举足轻重的意义。凡事也是一步步去探索去学习,这也如此。
5.FunnyTimes
好了,先记录到这,来杯酸柠檬缓缓~