Core Data 简单查询 查询大全

简单的查询通过创建 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 中异步查询

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

推荐阅读更多精彩内容