iOS Swift教程 Core Data (四)Fetch进阶 上

前面三节中,我们使用了最简单的方法来保存或获取CoreData中的数据比如:获取所有的领结实例,但是有些时候,你可能想给予fetch更多的掌控,通过这节的学习,你将学会以下知识:

  • 只获取你需要的数据;
  • 使用predicates来优化fetch的结果;
  • 在后台进程中进行fetch操作而不阻塞UI进程;
  • 直接更新persistent store而减少不必要的fetch操作;

转载请注明出处:http://blog.csdn.net/yamingwu/article/details/42318091

源代码地址:https://github.com/dnawym/StudySwift/tree/master/CoreData/Bubble%20Tea%20Finder

NSFetchRequest:

前面章节中我们已经学到,NSFetchRequest实例用于从Core Data中获取数据,配置这个类的实例并将其交给NSManagedObjectContext,后后者会帮我们完成后续的工作。构造fetch request的方法有如下四种:

第一种:创建NSFetchRequest的实例和NSEntityDescription的实例,将entity赋值给fetch request

let fetchRequest1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext!)

fetchRequest1.entity = entity!
第二种:将entity的名称作为参数传递给fetch request,这样就不用显示的构造entity
let fetchRequest2 = NSFetchRequest(entityName: "Person")
第三种:使用NSManagedObjectModel来构造fetch request,这一节我们会使用这种方法
let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peoperFR")
第四种:和第三种方法类似,但是会传递额外的参数给NSManagedObjectModel,这些额外的参数用于优化fetch结果
let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" : "Ray"])
珍珠奶茶:

这款应用程序使用从Foursqure得到纽约市内30个珍珠奶茶点作为数据源(json),给据给定的过滤条件在table view中进行显示。

创建fetch request:

打开Model。xcdatamodeld左键按住Add Entity不放,选择弹出对话框中的第二个选项“Add Fetch Request”


这样就创建了我们需要的一个Fetch Request,我们暂时不需要为新创建的fetch request添加其它条件


接下来,我们在ViewController里面使用这个FetchRequest,添加变量的定义

import CoreData
    var fetchRequest: NSFetchRequest!
    var venues: [Venue]!

修改viewDidLoad,通过managed object model来获取我们创建的FetchRequest。fetchRequestTemplateForName使用字符串来查找我们使用model editor创建的FetchRequest

    override func viewDidLoad() {
        super.viewDidLoad()
    
        fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
        
        fetchAndReload()
    }
fetchAndReload函数顾名思义,从core data获取数据并刷新table view

    //MARK: - 辅助函数
    func fetchAndReload() {
        var error: NSError?
        let results = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [Venue]?
        
        if let fetchResults = results {
            venues = fetchResults
        } else {
            println("Could not fetch \(error), \(error!.userInfo)")
        }
        
        tableView.reloadData()
    }
修改tableView的2个函数numberOfRowsInSection和cellForRowAtIndexPath

    func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return venues.count
    }
  
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
      
        var cell = tableView.dequeueReusableCellWithIdentifier("VenueCell") as UITableViewCell
        
        let venue = venues[indexPath.row]
        
        cell.textLabel!.text = venue.name
        cell.detailTextLabel!.text = venue.priceInfo.priceCategory
      
        return cell
    }
程序执行效果如下:



获取不同类型的结果:

至此,你可能认为NSFetchRequest这个类是一个很简单的工具,设置一些属性,你就可以得到所需的数据。其实,NSFetchRequest像瑞士军刀一样,有这多种多样的功能。比如:获取一个单独的数据,计算统计结果(平均值、最小值和最大值等等)。

  • NSFetchRequest有一个名为resultType的属性,我们现在只是用了.Default值,除了这个值还有如下几种:
  • NSManagedObjectResultType:返回一个managed object(默认值)
  • NSCountResultType:返回满足fetch request的object数量
  • NSDictionaryResultType:
  • NSManagedObjectIDResultType:返回唯一的标示符而不是managed object

计算不同价位商家的数目:

修改FilterViewController.swift,添加最低价位的predicate函数:

    // 这个NSPredicate用于计算属于最低价格区间的venues数量
    lazy var cheapVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
        
        return predicate!
    }()

添加函数查询最低价位商家的数目并更新到Label上:

    func populateCheapVenueCountLabel() {
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .CountResultType
        fetchRequest.predicate = cheapVenuePredicate
        
        var error: NSError?
        let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
        
        if let countArray = result {
            let count = countArray[0].integerValue
            
            firstPriceCategoryLabel.text = "\(count) bubble tea pleases"
        } else {
            println("Could not fetch \(error), \(error!.userInfo)")
        }
    }
重载viewDidLoad函数

    override func viewDidLoad() {
        super.viewDidLoad()
        
        populateCheapVenueCountLabel()
    }
接下来添加中等价位和高价位商家的数量

    lazy var moderateVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$")
        
        return predicate!
        }()
    
    func populateModerateVenueCountLabel() {
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .CountResultType
        fetchRequest.predicate = moderateVenuePredicate
        
        var error: NSError?
        let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
        
        if let countArray = result {
            let count = countArray[0].integerValue
            
            secondPriceCategoryLabel.text = "\(count) bubble tea pleases"
        } else {
            println("Could not fetch \(error), \(error!.userInfo)")
        }
    }
    
    lazy var expensiveVenuePredicate: NSPredicate = {
        var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$$")
        
        return predicate!
        }()
    
    func populateExpensiveVenueCountLabel() {
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .CountResultType
        fetchRequest.predicate = expensiveVenuePredicate
        
        var error: NSError?
        let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
        
        if let countArray = result {
            let count = countArray[0].integerValue
            
            thirdPriceCategoryLabel.text = "\(count) bubble tea pleases"
        } else {
            println("Could not fetch \(error), \(error!.userInfo)")
        }
    }
更新viewDidLoad函数

    override func viewDidLoad() {
        super.viewDidLoad()
        
        populateCheapVenueCountLabel()
        populateModerateVenueCountLabel()
        populateExpensiveVenueCountLabel()
    }
程序filter页面效果如下,低价位商家27家,中等价位2家,高价位1家:


对fetch request进行计算:

首先计算deal的和,设置返回值类型是.DictionaryResultType

    func populateDealsCountLabel() {
        // 创建FetchRequest获取Venue对象并设置返回值类型为DictionaryResultType
        let fetchRequest = NSFetchRequest(entityName: "Venue")
        fetchRequest.resultType = .DictionaryResultType
        
        // 创建NSExpressionDescription来请求进行总和计算,取名为sumDeals,一会儿就可以通过这个名字
        // 从fetch请求返回的字典中找到总和
        let sumExpressionDesc = NSExpressionDescription()
        sumExpressionDesc.name = "sumDeals"
        
        // 指定要进行总和计算的字段名-specialCount并设置返回值类型
        sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments:[NSExpression(forKeyPath: "specialCount")])
        sumExpressionDesc.expressionResultType = .Integer32AttributeType
        
        // 设置fetchRequest的propertiesToFetch属性为我们构造的sumExpressionDesc告诉fetchRequest
        // 我们需要对数据进行求和
        fetchRequest.propertiesToFetch = [sumExpressionDesc]
        
        // 执行fetch request,将返回结果转换为optional字典,使用设置的字段名-sumDeals来索引求和的结果
        var error: NSError?
        let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSDictionary]?
        
        if let resultArray = result {
            let resultDict = resultArray[0]
            let numDeals: AnyObject? = resultDict["sumDeals"]
            numDealsLabel.text = "\(numDeals!) total deals"
        } else {
            println("Could not fetch \(error), \(error!.userInfo)")
        }
    }

最后一种返回值类型是.ManagedObjectIDResultType,这种类型的fetch结果返回值是NSManagedObjectID的数组,类似于数据库的主键。

由于每一次fetch,可能返回大量的数据,这样每一个fetch都会消耗大量的内存,接下来我们使用Predicate来限制返回的数据的数量。

为FilterViewController类添加protocol,这样实现了这个protocol的类在用户修改了排序或过滤条件后就会被通知到。

protocol FilterViewControllerDelegate: class {
    func filterViewController(filter: FilterViewController,
        didSelectPredicate predicate: NSPredicate?,
        sortDescriptor: NSSortDescriptor?)
}
添加delegate,选定的NSSortDescriptor和NSPredicate的定义

    weak var delegate: FilterViewControllerDelegate?
    var selectedSortDescriptor: NSSortDescriptor?
    var selectedPredicate: NSPredicate?
修改saveButtonTapped函数和didSelectRowAtIndexPath。当用户点击价格分类cell时,更新selectedPredicate。当用户点击search按钮时,调用代理的filterViewController的函数。
    //MARK - UITableViewDelegate methods
  
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        
        let cell = tableView.cellForRowAtIndexPath(indexPath)!
        
        switch cell {
        case cheapVenueCell:
            selectedPredicate = cheapVenuePredicate
        case moderateVenueCell:
            selectedPredicate = moderateVenuePredicate
        case expensiveVenueCell:
            selectedPredicate = expensiveVenuePredicate
        default:
            println("default case")
        }
        
        cell.accessoryType = .Checkmark
    }
  
    // MARK - UIButton target action
  
    @IBAction func saveButtonTapped(sender: UIBarButtonItem) {
        
        delegate!.filterViewController(self, didSelectPredicate: selectedPredicate, sortDescriptor: selectedSortDescriptor)
        
        dismissViewControllerAnimated(true, completion:nil)
    }
回到ViewController,实现代理函数

    //MARK: - FilterViewControllerDelegate methods
    func filterViewController(filter: FilterViewController, didSelectPredicate predicate: NSPredicate?, sortDescriptor: NSSortDescriptor?) {
        fetchRequest.predicate = nil
        fetchRequest.sortDescriptors = nil
        
        if let fetchPredicate = predicate {
            fetchRequest.predicate = fetchPredicate
        }
        
        if let sr = sortDescriptor {
            fetchRequest.sortDescriptors = [sr]
        }
        
        fetchAndReload()
    }
程序执行效果如下,在过滤页面选择只显示中等价位的商家:



类似的方法,实现其它三种predicate

    lazy var offeringDealPredicate: NSPredicate = {
        var pr = NSPredicate(format: "specialCount > 0")
        return pr!
    }()
    
    lazy var walkingDistancePredicate: NSPredicate = {
        var pr = NSPredicate(format: "location.distance < 500")
        return pr!
    }()
    
    lazy var hasUserTipsPredicate: NSPredicate = {
        var pr = NSPredicate(format: "stats.tipCount > 0")
        return pr!
    }()
更新didSelectRowAtIndexPath函数

    //MARK - UITableViewDelegate methods
  
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        
        let cell = tableView.cellForRowAtIndexPath(indexPath)!
        
        switch cell {
        case cheapVenueCell:
            selectedPredicate = cheapVenuePredicate
        case moderateVenueCell:
            selectedPredicate = moderateVenuePredicate
        case expensiveVenueCell:
            selectedPredicate = expensiveVenuePredicate
        case offeringDealCell:
            selectedPredicate = offeringDealPredicate
        case walkingDistanceCell:
            selectedPredicate = walkingDistancePredicate
        case userTipsCell:
            selectedPredicate = hasUserTipsPredicate
        default:
            println("default case")
        }
        
        cell.accessoryType = .Checkmark
    }




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值