swiftSwiftJC专题

CoreData高能组合拳NSFetchedResultsCon

2016-06-20  本文已影响780人  ChinaSwift
高能组合拳

1.你造吗?

对于ios编程人员来说,提起数据持久性存储都会有这么几个概念:

这几种存储方式各自都有自己的特点,开发者一般都可根据不同的需求不同的场合去选择不同的存储方案。

然而几乎每一次关于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

好了,先记录到这,来杯酸柠檬缓缓~

上一篇下一篇

猜你喜欢

热点阅读