Core Data 简单查询 查询大全

作者: 扬仔360 | 来源:发表于2015-11-26 16:11 被阅读1923次

    简单的查询通过创建 NSFetchRequest 来从 CoreData 中取得数据。

    下面展示四种查询数据的方式:

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

    第一种方法通过默认初始化NSFetchRequest,从 managedContext 来创建 Person 类的 EntityDescription,然后设置fetchRequest的entity来完成。

    //2
    let fetchRequest2 = NSFetchRequest(entityName: "Person")
    

    第二种方法可以在初始化NSFetchRequest的时候传入EntityName来完成,这是一种便捷的快速方法,在init的时候就制定了Entity。

    //3
    let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peopleFR")
    

    第三种通过调用managedObjectModel.fetchRequestTemplateForName方法来获取 NSFetchRequest。在Xcode的 Data Model Editor 界面中可以手动设置一些针对用户需求常用的fetch属性,可以使用这种方法来快速调用。

    //4
    let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" :"Ray"])
    

    第四种基于第三种,但是会在第三种的基础上使用substitutionVariables进行再次的筛选。


    在Editor上创建 Fetch Template

    在 Data Model Editor 界面上长时间按住 Add Entity 的那个按钮,选择Add Fetch Request

    之后如果是查询一个实体的全部数据,就吧下拉框选为目标查询的实体。

    几个小点:

    • 从 Editor 模板中取得 FetchRequest 的时候必须从 ManagedObjectModel 中取。

    • 构建的 CoreDataStack 类中只应该有 ManagedContext 是 Public 的,其余的都应该是 Private。

    • NSManagedObjectModel.fetchRequestTemplateForName()的参数必须跟 Editor 中的保持一致。

    NSFetchRequest 神奇の存在

    在 CoreData 框架中,NSFetchRequest 就像一把多功能的瑞士军刀,你可以批量获取数据,可以获取单个数据,可以获取最大最小、平均值等、

    那么他是如何实现这些的呢,FR 有一个property叫做 resultType,默认值是 NSManagedResultType

    • NSManagedObjectResultType:默认值,返回批量的 Managed Object 对象

    • NSCountResultType: 类型如其名,返回 ManagedObjects.count

    • NSDictionaryResultType: 返回不同的计算类型,稍后补充

    • NSManagedObjectIDResultType: 返回特殊的标记,而不是真实的对象,其实这个有点儿像 hashCode 的意思

    NSCountResultType 实现 count 计数

    在获取了 FR 之后,需要给 FR 添加 predicate,predicate 在创建的时候支持 key path,比如:

    lazy var cheapVenuePredicate: NSPredicate = {
      var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
      return predicate
    }()
    

    上面代码中的 priceInfo.priceCategory 就是一个应用。

    func populateCheapVenueCountLabel() {
      // $ fetch request
      let fetchRequest = NSFetchRequest(entityName: "Venue")
      fetchRequest.resultType = .CountResultType
      fetchRequest.predicate = cheapVenuePredicate
      do {
        let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSNumber]
        let count = results.first!.integerValue
        firstPriceCategoryLabel.text = "\(count) bubble tea places"
      } catch let error as NSError {
        print("Could not fetch \(error), \(error.userInfo)")
      }
    }
    

    这个方法使用上面的 LazyLoading 的iVar - cheapVenuePredicate 作为 predicate,来做数据查询。

    这里指明了 fetchRequest.resultType = NSCountResultType,所以结果会返回一个包含了一个 NSNumberNSArray。当然这个 NSNumber 是一个 NSInteger,它就是那个count。

    除了上面的一种执行 executeFetchRequest 的方法获取Count的方法之外,还可以直接调用 context 的countForFetchRequest 方法来获取Count:

    func populateExpensiveVenueCountLabel() {
      // $$$ fetch request
      let fetchRequest = NSFetchRequest(entityName: "Venue")
      fetchRequest.resultType = .CountResultType
      fetchRequest.predicate = expensiveVenuePredicate
      
      var error: NSError?
      let count = coreDataStack.context.countForFetchRequest(fetchRequest, error: &error)
      
      if count != NSNotFound {
        thirdPriceCategoryLabel.text = "\(count) bubble tea places"
      } else {
        print("Could not fetch \(error), \(error?.userInfo)")
      }
    }
    

    上面的代码中,使用的错误处理不是像之前使用的 try-catch 结构,而是使用 iOS SDK 中常见的 error 的结构,这是因为 countForFetchRequest 方法的第二个参数是一个 *NSError 类型,需要将一个 error 对象的指针传递进去。使用:

    coreDataStack.context.countForFetchRequest(fetchRequest, error: &error)
    

    来获取查询结果的 count。

    DictionaryResultType 实现计算逻辑

    前面说了,NSFetchRequest 可以左好多事情,这里包括了一些计算的逻辑。

    对于某些需求,比如对查询的结构进行一些SUM、MIN、MAX这样的常见操作,常见一些低效率的代码把所有的数据全部查询出来,然后在程序中使用 For 循环来进行筛选,这样做又 Naive 又低效。

    代码如下:

    func populateDealsCountLabel() {
      //1 像之前一样普通的创建 NSFetchRequest,但是 resultType 指定为 .DictionaryResultType
      let fetchResult = NSFetchRequest(entityName: "Venue")
      fetchResult.resultType = .DictionaryResultType
      
      //2 创建一个 NSExpressionDescription 对象去请求 SUM,而且给它一个 name 标示“sumDeals”,在resultResults中就会有这个 name 标识的返回结果
      let sumExpressionDesc = NSExpressionDescription()
      sumExpressionDesc.name = "sumDeals"
      
      //3 上面仅仅给了 ExpressionDescription 一个name标示,但是只是一个String而已,真正让它清楚自己要做sum求和需要给ExpressionDesc对象的这个 .expression 对象做配置:
      //初始化一个 NSExpression 对象,function写上“sum”,还有好多,使用 command 键按进去方法描述下面一大堆,后面的 argument 参数指明对查询出来的结果的 specialCount 来进行逻辑计算。
      sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
      //指定计算结果的数据类型
      sumExpressionDesc.expressionResultType = .Integer32AttributeType
      
      //4 配置好了 NSExpressionDescription 对象之后,将配置好的它 set 为 NSFetchRequest 对象的 .propertiesToFetch(看见没这里是ties,意思要接受数组,而且可以配置好多个,配置多个就返回多个喽~)
      fetchResult.propertiesToFetch = [sumExpressionDesc]
      
      //5 司空见惯的查询,看从 resultDic 中取得查询结果的那个 [sumDeals] 就是前面 NSExpressDescription.name。
      do {
        let results = try coreDataStack.context.executeFetchRequest(fetchResult) as! [NSDictionary]
        let resultDic = results.first!
        let numDeals = resultDic["sumDeals"]
        numDealsLabel.text = "\(numDeals!) total deals"
      } catch let error as NSError {
        print("Could not fetch \(error), \(error.userInfo)")
      }
    }
    

    ManagedObjectIDResultType 查询结果的唯一标示

    四种类型前面已经说了三种,接下来的一种就是 ManagedObjectIDResultType,这种查询类型会返回查询结果的一个特殊标志,一种 universal identifier 每一个 ManagedObject 对应着一个特殊的 ID。

    之前笔者认为它像 hashCode 但是明显又不一样,因为即便是两个内容相同的 ManagedObjcet,他们的 ID 也都是不一样的,但是 HashCode 确应该是一样的。

    iOS 5 的时候取得 NSManagedObjectID 是线程安全的,但是现在已被弃用,但是有更好的并发模型提供给我们使用,以后会有 CoreData 与多线程并发。

    另外提两点:

    • NSFetchRequest 支持批量返回,可以通过设置 fetchBatchSizefetchLimitfetchOffset 去配置 Batch 的 FetchRequest。

    • 另外可以通过 faulting 来优化内存消耗:fault 是一中占位符,它代表着一个还没有真正从存储层取出到内存的 ManagedObject。

    • 另外一重优化内存的方法就是使用 predicate,接下来会写到。

    (如果使用了 Editor 配置的 predicate,那么在 Runtime 的时候就不能更改 predicate。)

    其实 NSPredicate 不属于 Core Data 框架,它是属于 Foundation 框架中的,更多有关于 NSPredicate 的,可以看 苹果官方的文档

    FetchResults 排序

    NSFetchRequest 另外一个神奇的功能是它能把获取的数据进行排序,实现的机制是使用 NSSortDescription 类,而且这个排序的执行时机是在 数据存储层 也就是在 SQLite 层完成的,保证了排序的速度和效率。

    案例:需要排序的模块中加入 NSSortDescription 的 lazy Var 如下:

    lazy var nameSortDescriptor: NSSortDescriptor = {
      var sd = NSSortDescriptor(key: "name", ascending: true, selector: "localizedStandardCompare:")
      return sd
    }()
    
    lazy var distanceSortDescription: NSSortDescriptor = {
      var sd = NSSortDescriptor(key: "location.distance", ascending: true)
      return sd
    }()
    

    第一个 NSSortDescription 按照姓名来进行排序,排序的比较方法选择了 String 的 localizedStandardCompare:

    第二个 NSSortDescription 按照 location.distance 进行排序。
    ascending 参数表示是否要升序,true -> ascendingfalse -> descending

    注意:

    • 在 Core Data 框架之外,NSSortDescriptor 支持基于 Block Closure 的 Comparator,这里我们用的是一个 selector。其实在 Core Data 框架内是不允许使用 Comparator 的方法来定义排序的。

    • 同样的,供给 Core Data 使用的 NSPredicate 也是不允许使用任何基于 Block 的 API。

    • 上面两点为什么呢?因为前面说了排序发生在数据存储层,也就是在SQLite查询的时候完成的,Block 的方式不能有效组成一个 SQLite 查询语句。

    localizedStandardCompare 是什么,当你对用户看到的那些字符串进行排序的时候,Apple 都建议传递 localizedStandardCompare来当做排序的规则来对当前字符串进行排序。

    如果要某个 SortDescriptor 的逆向排序,可以调用它的 .reversedSordDescriptor取得。

    nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
    

    配置好了 SortDescription 之后,将 NSFetchRequest 的 sortDescriptors 属性设置为包含了 SortDescription 的数组:

    fetchRequest.sortDescriptors = [sr]
    

    另外:Core Data 中异步查询

    相关文章

      网友评论

        本文标题:Core Data 简单查询 查询大全

        本文链接:https://www.haomeiwen.com/subject/xgrdhttx.html