CoreData Learning note
后續(xù)閱讀:
https://www.objc.io/issue-4/core-data-overview.html
中文:http://www.cocoachina.com/ios/20130911/6981.html
01: 存群门馈:
首先開頭用最簡(jiǎn)單的小例子局雄。
//MARK: - Save Data
func saveName(name: String){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext)
let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
person.setValue(name, forKey: "name")
do {
try managedContext.save()
people.append(person)
} catch let error as NSError{
print("Could not save \(error),\(error.userInfo)")
}
}
//MARK: - Fetch Data
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
people = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
02.
可以使用BinaryData
來存儲(chǔ)圖像蜗搔,但是巨大的圖像直接存儲(chǔ)在數(shù)據(jù)庫(kù)中會(huì)導(dǎo)致性能降低肘交,選擇類型為BD的Attribute,然后可以在右邊的屬性選擇框中勾選Allows External Storage
墩邀,類似所以存儲(chǔ)在外部穆桂,提高數(shù)據(jù)訪問性能宫盔。
Transformable
類型用于存儲(chǔ)那些實(shí)現(xiàn)了NSCoding
protocol 的對(duì)象,比如 UIColor享完,UIColor 實(shí)現(xiàn)了NSSecureCoding
灼芭。
03. 不要過于依賴 NSManagedObject
雖然使用KVC很便捷,但是盡量不要過于依賴KVC般又。
04.自動(dòng)創(chuàng)建Entity的子類:
打開彼绷。xcfatamodeld
文件 -> Editor -> Create NSManagedObject Subclass
創(chuàng)建出來的每個(gè) Entity 對(duì)應(yīng)了兩個(gè)文件巍佑,普通的文件僅僅包含了所有的 action 操作,Entity 的 所有屬性都用 extension 的方式進(jìn)行了實(shí)現(xiàn)苛预。所有的屬性在Extension
中聲明的時(shí)候前面都加上了@NSManaged
句狼,這個(gè)標(biāo)示通知了編譯器,這個(gè) property 會(huì)在runtime的時(shí)候提供热某,而不是在編譯的時(shí)候腻菇。
一般的模式下,property 是由內(nèi)存中的實(shí)例變量實(shí)現(xiàn)的昔馋,但是在managedObject
中筹吐,是由managed object context
實(shí)現(xiàn)的,compile time 并不知道秘遏。
創(chuàng)建了屬于自定義的Entity
的Managed Object
的子類有兩個(gè)好處:
· 隔離的KVC丘薛,編譯器可以獲取 Property,而不是通過 KVC 的方式邦危,方便編寫程序洋侨。
· 方便重寫方法。當(dāng)時(shí)注意Apple文檔中標(biāo)出了一些從來都不應(yīng)該重寫的方法倦蚪。
05. 結(jié)合子類 NSManagedObject 去實(shí)現(xiàn)添加和 Retrieve 的 Demo:
這里也就是增加的Demo:
let bowtie = NSEntityDescription.insertNewObjectForEntityForName("Bowtie", inManagedObjectContext: managedObjectContext) as! Bowtie
bowtie.name = "My bow tie"
bowtie.lastWorn = NSDate()
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Save error\(error.localizedDescription)")
}
//Retrieve test bow tie
do {
let request = NSFetchRequest(entityName: "Bowtie")
let ties = try managedObjectContext.executeFetchRequest(request) as! [Bowtie]
let sample : Bowtie = ties[0]
print("Name = \(sample.name), Worn: \(sample.lastWorn)")
}catch let error as NSError {
print("Fetching error: \(error.localizedDescription)")
}
06. Data Validation:
選擇Entity
的Property希坚,然后右邊可以設(shè)置該項(xiàng)屬性的最大值、最小值和默認(rèn)值陵且。
07. 具體的操作對(duì)象:
具體這一部分可以添加常用的方法:會(huì)在后續(xù)添加裁僧。
http://justsee.iteye.com/blog/1881110
NSManagedObjectContext
表示被操作數(shù)據(jù)的上下文環(huán)境。類似于一種持續(xù)性的數(shù)據(jù)庫(kù)連接慕购,可以做增刪查等操作聊疲。
而且只有在調(diào)用Save方法的時(shí)候,這些所有的改動(dòng)才會(huì)被提交沪悲,否則是不會(huì)在持久層做任何改動(dòng)的获洲。
支持撤銷和重做。
NSManagedObjectModel
表示被管理的數(shù)據(jù)模型殿如,這里包含著所有對(duì)象的表格信息昌妹,所有數(shù)據(jù)結(jié)構(gòu),包括他們之間的關(guān)系握截。
在這里添加實(shí)體的屬性,添加實(shí)體和實(shí)體之間的關(guān)系烂叔。
所以NSManagedObjectModel
其實(shí)包含著NSEntityDescription
和NSPropertyDescription
谨胞,前者相當(dāng)于數(shù)據(jù)庫(kù)中的一個(gè)表,后者相當(dāng)于表中的一列蒜鸡。NSPropertyDescription
可以描述實(shí)體的基本屬性(Attributes)胯努、實(shí)體之間的關(guān)系(Relationships
)還有 查詢屬性(FetchedProperty
)牢裳。
查詢屬性對(duì)應(yīng)NSFetchedPropertyDescription
對(duì)象。
Persistent Store
持久化存儲(chǔ)層叶沛,是由文件或者外部數(shù)據(jù)庫(kù)組成的蒲讯,大多數(shù)情況下訪問持久化層全部由上下文Context來完成。
CoreData 提供了四種持久化層的方案:
非原子訪問的:NSQLiteStoreType
原子訪問的:BSXMLStoreType
灰署、NSBinaryStoreType
判帮、NSInMemoryStoreType
。
XML是將數(shù)據(jù)存為XML文件溉箕,只在 OS X 平臺(tái)下可用晦墙。Binary
的方法是存為一個(gè)Data文件。InMemory
的方式是不會(huì)對(duì)數(shù)據(jù)進(jìn)行真正意義上的持久化肴茄,全部存儲(chǔ)在內(nèi)存中晌畅,當(dāng)應(yīng)用程序退出時(shí),數(shù)據(jù)也就消失了寡痰。
NSPersistentStoreCoordinator
持久化存儲(chǔ)助理的存在相當(dāng)于和數(shù)據(jù)持久層的連接器抗楔,SQLite
的話就是和數(shù)據(jù)庫(kù)的連接,它設(shè)置著數(shù)據(jù)庫(kù)的路徑名字拦坠、位置连躏、存儲(chǔ)方式和存儲(chǔ)的時(shí)機(jī)。
CoreData其實(shí)就相當(dāng)于一個(gè)棧贪婉,棧的底層是持久化存儲(chǔ)層反粥,棧頂是Context
,所以一般簡(jiǎn)單的需求我們只需要使用棧頂?shù)?code>Context疲迂,那么NSPersistentStoreCoordinator
就是棧中層的一層存在才顿,協(xié)調(diào)上棧的上下層關(guān)系。
負(fù)責(zé)理解NSManagedObjectModel
和去NSPersistentStore
中執(zhí)行相應(yīng)操作尤蒿。
補(bǔ)充NSManagedObjectContext
NSManagedObjectContext
就像內(nèi)存中的便簽紙
郑气,臨時(shí)修改你的ManagedObject
,所以知道你提交save()
腰池,否則持久化層是不會(huì)有變化的尾组。
上下文管理這
Managed Object
的生命周期,管理的同時(shí)提供了很多高級(jí)特征可以給以使用示弓,比如排序讳侨,Validation、關(guān)系管理等奏属。一個(gè)
Managed Object
是不能獨(dú)立于Context
存在的跨跨,即有Managed Object
就一定會(huì)有它的Context
,也可以通過 Managed Object 的.managedObjectContext
屬性取得它的Context
對(duì)象。一旦設(shè)置了
Managed Object
的Context
勇婴,那么在Object
的很長(zhǎng)的生命周期中就會(huì)一直跟這個(gè)特定的Context
關(guān)聯(lián)忱嘹。一個(gè)應(yīng)用程序可以有很多個(gè)
Context
,你可以創(chuàng)建兩個(gè)Context
指向一些相同的PersistentStore
持久化層耕渴。Context
并不是線程安全的拘悦,所以對(duì)于Managed Object
也是一樣的,它們只可以在創(chuàng)建他們的那個(gè)線程上進(jìn)行操作橱脸。
08. 創(chuàng)建自己的線程棧
創(chuàng)建:CoreDataStack.swift
第一部分創(chuàng)建 Model 的名字和 Document 的路徑URL:
創(chuàng)建新的 .swift
文件
import CoreData
class CoreDataStack {
let modelName = "Dog Walk"
//Document's URL
private lazy var applciationDocumentDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
}
第二步給類添加懶加載的三個(gè)屬性 NSManagedObjectContext
础米、NSPersistentStoreCoordinator
和NSManagedObjectModel
:
//NSManagedObjectContext
//Note: ConcurrencyType 的具體參數(shù)會(huì)在后面補(bǔ)充添加,暫時(shí)先使用 .MainQueueConcurrencyType
//創(chuàng)建出來Context是完全沒有意義的慰技,直到設(shè)置了Context的PersistentStoreCoordinator
lazy var context: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.psc
return managedObjectContext
}()
//NSPersistentStoreCoordinator
//對(duì)StoreCoordinator做懶加載椭盏,StoreCoordinator是介與PersistentStore(s)和ObejctModel之間的,所以至少需要一個(gè)PersistentStore吻商。
private lazy var psc: NSPersistentStoreCoordinator = {
//coordinator init掏颊,傳入Model,Model指所有的Entity和所有的relationship
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
// PersisitentStore 的物理存儲(chǔ)路徑
let url = self.applciationDocumentDirectory.URLByAppendingPathComponent(self.modelName)
do{
// 一些Option配置:
let options = [NSMigratePersistentStoresAutomaticallyOption : true]
//addPersistentStoreWithType 選擇一個(gè)SQLite的type艾帐,使用SQLite作為存儲(chǔ)模式乌叶。
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL:url, options: options)
} catch {
print("Error adding persistnet store.")
}
return coordinator
}()
//NSManagedObjectModel
//這里包含著MainBundle里面的momb文件里面的 `.xcdatamodeld` 文件,就是Xcode圖形化設(shè)計(jì)Entity和Relationship的那個(gè)文件柒爸,使用它來創(chuàng)建ManagedObjectModel
private lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle().URLForResource(self.modelName, withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
添加的每 Property 都對(duì)應(yīng)著 Core Data 的重要組件准浴,使用Lazy Loading
,每個(gè)組件都依賴其他的組件捎稚。
這些 Property 中只有NSManagedObjectContext
是 Public 的 Property乐横,其余的都是 private 修飾。private 修飾的組件外界不需要獲取的今野。
另外葡公,NSPersistentStoreCoordinator
可以通過 NSManagedObjectContext
的 Public property 獲取。
而所有的NSManagedObjectModel
和NSPersistentStore
都可以通過NSPersistentStoreCoordinator
的Public property 來獲取条霜。
對(duì)StoreCoordinator
做懶加載催什,它是介與PersistentStore(s)
和ObejctModel
之間的,所以至少需要一個(gè)PersistentStore
宰睡。
最后添加一個(gè)方法:
//保存的方法
func saveContent () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
abort()
}
}
}
在AppDelegate
中添加LazyLoading的iVar:
lazy var coreDataStack = CoreDataStack()
在didFinishLaunchingWIthOption
的方法中可以給rootViewController中的Context賦值了:
let navigationController = window!.rootViewController as! UINavigationController
let viewController = navigationController.topViewController as! ViewController
viewController.managedContext = coreDataStack.context
接下來在合適的時(shí)機(jī)調(diào)用SaveContent
方法蒲凶,這里就是在applicationDidEnterBackground
和applicationWillTerminate
代理方法中調(diào)用:
coreDataStack.saveContent()
到這里,一個(gè)CoreData的棧類就創(chuàng)建完成了拆内,并且已經(jīng)實(shí)現(xiàn)了在應(yīng)用退出的時(shí)候?qū)?Context 進(jìn)行 saveing 的操作
09.高級(jí)查詢
簡(jiǎn)單的查詢通過創(chuàng)建 NSFetchRequest
來從 CoreData 中取得數(shù)據(jù)旋圆。
下面展示四種查詢數(shù)據(jù)的方式:
//1
let fetchRequest1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext)!
fetchRequest1.entity = entity
第一種方法通過默認(rèn)初始化NSFetchRequest
,從 managedContext 來創(chuàng)建 Person 類的 EntityDescription麸恍,然后設(shè)置fetchRequest的entity來完成灵巧。
//2
let fetchRequest2 = NSFetchRequest(entityName: "Person")
第二種方法可以在初始化NSFetchRequest的時(shí)候傳入EntityName來完成,這是一種便捷的快速方法,在init的時(shí)候就制定了Entity孩等。
//3
let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peopleFR")
第三種通過調(diào)用managedObjectModel
的.fetchRequestTemplateForName
方法來獲取 NSFetchRequest。在Xcode的 Data Model Editor 界面中可以手動(dòng)設(shè)置一些針對(duì)用戶需求常用的fetch屬性采够,可以使用這種方法來快速調(diào)用肄方。
//4
let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" :"Ray"])
第四種基于第三種,但是會(huì)在第三種的基礎(chǔ)上使用substitutionVariables
進(jìn)行再次的篩選蹬癌。
在Editor上創(chuàng)建 Fetch Template
在 Data Model Editor 界面上長(zhǎng)時(shí)間按住 Add Entity 的那個(gè)按鈕权她,選擇Add Fetch Request
。
之后如果是查詢一個(gè)實(shí)體的全部數(shù)據(jù)逝薪,就吧下拉框選為目標(biāo)查詢的實(shí)體隅要。
幾個(gè)小點(diǎn):
從 Editor 模板中取得 FetchRequest 的時(shí)候必須從 ManagedObjectModel 中取。
構(gòu)建的 CoreDataStack 類中只應(yīng)該有 ManagedContext 是 Public 的董济,其余的都應(yīng)該是 Private步清。
NSManagedObjectModel.fetchRequestTemplateForName()
的參數(shù)必須跟 Editor 中的保持一致。
NSFetchRequest 神奇の存在
在 CoreData 框架中虏肾,NSFetchRequest 就像一把多功能的瑞士軍刀廓啊,你可以批量獲取數(shù)據(jù),可以獲取單個(gè)數(shù)據(jù)封豪,可以獲取最大最小谴轮、平均值等、
那么他是如何實(shí)現(xiàn)這些的呢吹埠,F(xiàn)R 有一個(gè)property叫做 resultType
第步,默認(rèn)值是 NSManagedResultType
:
NSManagedObjectResultType
:默認(rèn)值,返回批量的 Managed Object 對(duì)象NSCountResultType
: 類型如其名缘琅,返回 ManagedObjects.countNSDictionaryResultType
: 返回不同的計(jì)算類型粘都,稍后補(bǔ)充NSManagedObjectIDResultType
: 返回特殊的標(biāo)記,而不是真實(shí)的對(duì)象胯杭,其實(shí)這個(gè)有點(diǎn)兒像 hashCode 的意思
NSCountResultType 實(shí)現(xiàn) count 計(jì)數(shù)
在獲取了 FR 之后驯杜,需要給 FR 添加 predicate,predicate 在創(chuàng)建的時(shí)候支持 key path做个,比如:
lazy var cheapVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
return predicate
}()
上面代碼中的 priceInfo.priceCategory 就是一個(gè)應(yīng)用鸽心。
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)")
}
}
這個(gè)方法使用上面的 LazyLoading 的iVar - cheapVenuePredicate 作為 predicate,來做數(shù)據(jù)查詢居暖。
這里指明了 fetchRequest.resultType
= NSCountResultType
顽频,所以結(jié)果會(huì)返回一個(gè)包含了一個(gè) NSNumber
的 NSArray
。當(dāng)然這個(gè) NSNumber 是一個(gè) NSInteger太闺,它就是那個(gè)count糯景。
除了上面的一種執(zhí)行 executeFetchRequest
的方法獲取Count的方法之外,還可以直接調(diào)用 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)")
}
}
上面的代碼中,使用的錯(cuò)誤處理不是像之前使用的 try-catch
結(jié)構(gòu)蟀淮,而是使用 iOS SDK 中常見的 error 的結(jié)構(gòu)最住,這是因?yàn)?countForFetchRequest
方法的第二個(gè)參數(shù)是一個(gè) *NSError 類型,需要將一個(gè) error 對(duì)象的指針傳遞進(jìn)去怠惶。使用:
coreDataStack.context.countForFetchRequest(fetchRequest, error: &error)
來獲取查詢結(jié)果的 count涨缚。
DictionaryResultType 實(shí)現(xiàn)計(jì)算邏輯
前面說了,NSFetchRequest 可以左好多事情策治,這里包括了一些計(jì)算的邏輯脓魏。
對(duì)于某些需求,比如對(duì)查詢的結(jié)構(gòu)進(jìn)行一些SUM通惫、MIN茂翔、MAX這樣的常見操作,常見一些低效率的代碼把所有的數(shù)據(jù)全部查詢出來履腋,然后在程序中使用 For 循環(huán)來進(jìn)行篩選珊燎,這樣做又 Naive 又低效。
代碼如下:
func populateDealsCountLabel() {
//1 像之前一樣普通的創(chuàng)建 NSFetchRequest府树,但是 resultType 指定為 .DictionaryResultType
let fetchResult = NSFetchRequest(entityName: "Venue")
fetchResult.resultType = .DictionaryResultType
//2 創(chuàng)建一個(gè) NSExpressionDescription 對(duì)象去請(qǐng)求 SUM俐末,而且給它一個(gè) name 標(biāo)示“sumDeals”,在resultResults中就會(huì)有這個(gè) name 標(biāo)識(shí)的返回結(jié)果
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
//3 上面僅僅給了 ExpressionDescription 一個(gè)name標(biāo)示奄侠,但是只是一個(gè)String而已卓箫,真正讓它清楚自己要做sum求和需要給ExpressionDesc對(duì)象的這個(gè) .expression 對(duì)象做配置:
//初始化一個(gè) NSExpression 對(duì)象,function寫上“sum”垄潮,還有好多烹卒,使用 command 鍵按進(jìn)去方法描述下面一大堆,后面的 argument 參數(shù)指明對(duì)查詢出來的結(jié)果的 specialCount 來進(jìn)行邏輯計(jì)算弯洗。
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
//指定計(jì)算結(jié)果的數(shù)據(jù)類型
sumExpressionDesc.expressionResultType = .Integer32AttributeType
//4 配置好了 NSExpressionDescription 對(duì)象之后旅急,將配置好的它 set 為 NSFetchRequest 對(duì)象的 .propertiesToFetch(看見沒這里是ties,意思要接受數(shù)組牡整,而且可以配置好多個(gè)藐吮,配置多個(gè)就返回多個(gè)嘍~)
fetchResult.propertiesToFetch = [sumExpressionDesc]
//5 司空見慣的查詢,看從 resultDic 中取得查詢結(jié)果的那個(gè) [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 查詢結(jié)果的唯一標(biāo)示
四種類型前面已經(jīng)說了三種谣辞,接下來的一種就是 ManagedObjectIDResultType
,這種查詢類型會(huì)返回查詢結(jié)果的一個(gè)特殊標(biāo)志沐扳,一種 universal identifier 每一個(gè) ManagedObject 對(duì)應(yīng)著一個(gè)特殊的 ID泥从。
之前筆者認(rèn)為它像 hashCode 但是明顯又不一樣,因?yàn)榧幢闶莾蓚€(gè)內(nèi)容相同的 ManagedObjcet沪摄,他們的 ID 也都是不一樣的躯嫉,但是 HashCode 確應(yīng)該是一樣的纱烘。
iOS 5 的時(shí)候取得 NSManagedObjectID 是線程安全的,但是現(xiàn)在已被棄用祈餐,但是有更好的并發(fā)模型提供給我們使用擂啥,以后會(huì)有 CoreData 與多線程并發(fā)。
另外提兩點(diǎn):
NSFetchRequest 支持批量返回帆阳,可以通過設(shè)置
fetchBatchSize
啤它、fetchLimit
和fetchOffset
去配置 Batch 的 FetchRequest。另外可以通過
faulting
來優(yōu)化內(nèi)存消耗:fault 是一中占位符舱痘,它代表著一個(gè)還沒有真正從存儲(chǔ)層取出到內(nèi)存的 ManagedObject。另外一重優(yōu)化內(nèi)存的方法就是使用 predicate离赫,接下來會(huì)寫到芭逝。
(如果使用了 Editor 配置的 predicate,那么在 Runtime 的時(shí)候就不能更改 predicate渊胸。)
其實(shí) NSPredicate 不屬于 Core Data 框架旬盯,它是屬于 Foundation 框架中的,更多有關(guān)于 NSPredicate 的翎猛,可以看 蘋果官方的文檔 胖翰。
FetchResults 排序
NSFetchRequest 另外一個(gè)神奇的功能是它能把獲取的數(shù)據(jù)進(jìn)行排序,實(shí)現(xiàn)的機(jī)制是使用 NSSortDescription
類切厘,而且這個(gè)排序的執(zhí)行時(shí)機(jī)是在 數(shù)據(jù)存儲(chǔ)層
也就是在 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
}()
第一個(gè) NSSortDescription 按照姓名來進(jìn)行排序疫稿,排序的比較方法選擇了 String 的 localizedStandardCompare:
培他。
第二個(gè) NSSortDescription 按照 location.distance
進(jìn)行排序。
ascending 參數(shù)表示是否要升序遗座,true -> ascending
舀凛,false -> descending
。
注意:
在 Core Data 框架之外途蒋,NSSortDescriptor 支持基于 Block Closure 的 Comparator猛遍,這里我們用的是一個(gè) selector。其實(shí)在 Core Data 框架內(nèi)是不允許使用 Comparator 的方法來定義排序的号坡。
同樣的懊烤,供給 Core Data 使用的 NSPredicate 也是不允許使用任何基于 Block 的 API。
上面兩點(diǎn)為什么呢筋帖?因?yàn)榍懊嬲f了排序發(fā)生在數(shù)據(jù)存儲(chǔ)層奸晴,也就是在SQLite查詢的時(shí)候完成的,Block 的方式不能有效組成一個(gè) SQLite 查詢語句日麸。
localizedStandardCompare
是什么寄啼,當(dāng)你對(duì)用戶看到的那些字符串進(jìn)行排序的時(shí)候逮光,Apple 都建議傳遞 localizedStandardCompare
來當(dāng)做排序的規(guī)則來對(duì)當(dāng)前字符串進(jìn)行排序。
如果要某個(gè) SortDescriptor 的逆向排序墩划,可以調(diào)用它的 .reversedSordDescriptor
取得涕刚。
nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
配置好了 SortDescription 之后,將 NSFetchRequest 的 sortDescriptors 屬性設(shè)置為包含了 SortDescription 的數(shù)組:
fetchRequest.sortDescriptors = [sr]
異步Fetching(iOS 8 的特性)
如同很多其他 iOS 上的需求一樣乙帮,當(dāng)復(fù)雜且耗時(shí)長(zhǎng)的工作放在主線程上杜漠,會(huì)造成線程阻塞,這個(gè)時(shí)候 UI 會(huì)處于一種假死的狀態(tài)察净。同樣的 Core Data 的 Fetch 也是一樣的驾茴,在 Fetch 條件復(fù)雜、數(shù)據(jù)量很大的情況下氢卡,同樣會(huì)造成線程阻塞锈至。這個(gè)時(shí)候,這部分工作就應(yīng)該異步執(zhí)行译秦。
Core Data 的異步執(zhí)行被封裝的相當(dāng)簡(jiǎn)單峡捡,在已有的 FetchRequest 的基礎(chǔ)上,使用 NSAsynchronousFetchRequest
來實(shí)現(xiàn)異步請(qǐng)求筑悴。
NSAsynchronousFetchRequest
的命名容易讓人產(chǎn)生歧義们拙,其實(shí)它并不是 FetchRequest 的 subclass,它跟 FetchRequest 一樣阁吝,同樣是 NSPersistentStoreRequest
的子類砚婆。也就是說實(shí)現(xiàn)異步的 Fetch 就是把 NSFetchRequest
替換為 NSAsynchronousFetchRequest
。
首先在模塊原有的 FetchRequest 中模塊中添加 iVar 變量:
var asyncFetchRequest: NSAsynchronousFetchRequest!
NSAsynchronousFetchRequest 就像是已經(jīng)存在的 FetchRequest 的一個(gè) Wrapper 一樣突勇。創(chuàng)建一個(gè) NSAsynchronousFetchRequest
需要一個(gè)正常的 FetchRequest 和一個(gè) Completion Handle射沟。
執(zhí)行請(qǐng)求的時(shí)候類似,不過請(qǐng)求的方法從 executeFetchRequest
變成了 executeRequest
与境,傳遞進(jìn)去的參數(shù)也從 FetchRequest 變成了 AsynchronousFetchRequest验夯。
執(zhí)行請(qǐng)求之后,返回的數(shù)據(jù)為:NSAsynchronousFetchResult
摔刁。
調(diào)用的方法如下:
fetchRequest = NSFetchRequest(entityName: "Venue")
asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) {
//查詢成功的處理
[unowned self] (result: NSAsynchronousFetchResult!) -> Void in
self.venues = result.finalResult as! [Venue]
self.tableView.reloadData()
}
do {
try coreDataStack.context.executeRequest(asyncFetchRequest)
//Returns immediately, cancel here if you want.
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
block 之內(nèi)是對(duì)返回的數(shù)據(jù)做處理挥转。
另外,如果要取消正在共屈,asyncFetchRequest 可以調(diào)用 cancel() 方法來取消這次異步請(qǐng)求绑谣。
批量更新(不做查詢,直接在Store層進(jìn)行更新拗引,iOS 8 的特性)
舉例一種情況借宵,如果我們要對(duì)十萬條數(shù)據(jù)都進(jìn)行同樣的一個(gè)屬性的更新,一般的做法我們需要取出這十萬條數(shù)據(jù)矾削,我們?nèi)〕鍪嗳f條數(shù)據(jù)只是為了做一個(gè)很簡(jiǎn)單的更新壤玫。
經(jīng)典的案例是電子郵箱類似的APP中 標(biāo)記所有郵件為已讀
這樣的需求豁护,難道要把上千封郵件全部請(qǐng)求出來嗎,顯然不是欲间。
iOS 8 發(fā)布了支持 批量更新(Bench Update)
的 NSBatchUpdateRequest
楚里,使用它可以在不做查詢的情況下更新數(shù)據(jù)。
NSBatchUpdateRequest 實(shí)現(xiàn)的原理是完全繞開了 NSManagedObjectContext猎贴,直接去 NSPersistentStore 層去做 Update班缎。
//這四行代碼,在初始化的時(shí)候配置好EntityName, 配置影響的property和更改的值 以及 配置影響的Store她渴,以及返回Result的數(shù)據(jù)類型达址。
//創(chuàng)建NSBatchUpdateRequest 的實(shí)例,entityName 作為初始化參數(shù)趁耗。
let batchUpdate = NSBatchUpdateRequest(entityName: "myEntityName")
//標(biāo)明需要 Update 的 property 和 值
batchUpdate.propertiesToUpdate = ["favorite" : NSNumber(bool: true)]
//被影響的Stores 默認(rèn)情況下這么寫就可以苏携,如果涉及比較多的PersistentStores 情況就更復(fù)雜了。
batchUpdate.affectedStores = coreDataStack.context.persistentStoreCoordinator!.persistentStores
//配置返回?cái)?shù)據(jù)的類型对粪,還可以是 UpdatedObjectIDsResultType。
batchUpdate.resultType = .UpdatedObjectsCountResultType
//執(zhí)行批量更新
do {
let batchResult = try coreDataStack.context.executeRequest(batchUpdate) as! NSBatchUpdateResult
print("Records updated \(batchResult.result!)")
} catch let error as NSError {
print("Could not update \(error), \(error.userInfo)")
}
在初始化的時(shí)候配置好EntityName, 配置影響的property和更改的值 以及 配置影響的Store装蓬,以及返回Result的數(shù)據(jù)類型著拭。
另外提一下:如同這種在 Store 層的數(shù)據(jù)更新,數(shù)據(jù)刪除也有同樣的API牍帚,在 iOS 9 的時(shí)候蘋果提供了一個(gè)類似 NSBatchUpdateRequest 的類來做在 Store 層的數(shù)據(jù)刪除儡遮,使用 ---- NSBatchDeleteRequest
,同樣它們兩個(gè)都是 NSPersistentStoreRequest
的子類暗赶。
注意:
額外需要注意的一點(diǎn)是鄙币,前面提到了「NSBatchUpdateRequest 實(shí)現(xiàn)的原理是完全繞開了 NSManagedObjectContext,直接去 NSPersistentStore 層去做 Update蹂随∈伲」
所以做了批量 Update / Delete 之后,你之前請(qǐng)求的那部分?jǐn)?shù)據(jù)已經(jīng)失效了岳锁,因?yàn)樗鼈兏鷶?shù)據(jù)庫(kù)已經(jīng)失去了同步性绩衷。
End.