CoreData高能组合拳NSFetchedResultsController

高能组合拳

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容