簡單的查詢通過創建 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.countNSDictionaryResultType
: 返回不同的計算類型,稍后補充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
,所以結果會返回一個包含了一個 NSNumber
的 NSArray
。當然這個 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 支持批量返回,可以通過設置
fetchBatchSize
、fetchLimit
和fetchOffset
去配置 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 -> ascending
,false -> 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]