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 中異步查詢

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容