Core Data learning note with Swift.

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)建了屬于自定義的EntityManaged 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í)包含著NSEntityDescriptionNSPropertyDescription谨胞,前者相當(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 ObjectContext勇婴,那么在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础米、NSPersistentStoreCoordinatorNSManagedObjectModel

 //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 獲取。

而所有的NSManagedObjectModelNSPersistentStore都可以通過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方法蒲凶,這里就是在applicationDidEnterBackgroundapplicationWillTerminate代理方法中調(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.count

  • NSDictionaryResultType: 返回不同的計(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è) NSNumberNSArray。當(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啤它、fetchLimitfetchOffset 去配置 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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市激率,隨后出現(xiàn)的幾起案子咳燕,更是在濱河造成了極大的恐慌,老刑警劉巖乒躺,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件招盲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嘉冒,警方通過查閱死者的電腦和手機(jī)曹货,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門咆繁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人控乾,你說我怎么就攤上這事么介。” “怎么了蜕衡?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵壤短,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我慨仿,道長(zhǎng)久脯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任镰吆,我火速辦了婚禮帘撰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘万皿。我一直安慰自己摧找,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布牢硅。 她就那樣靜靜地躺著蹬耘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪减余。 梳的紋絲不亂的頭發(fā)上综苔,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音位岔,去河邊找鬼如筛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抒抬,可吹牛的內(nèi)容都是我干的杨刨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼擦剑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拭嫁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抓于,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤做粤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捉撮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怕品,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年巾遭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肉康。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闯估。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吼和,靈堂內(nèi)的尸體忽然破棺而出涨薪,到底是詐尸還是另有隱情,我是刑警寧澤炫乓,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布刚夺,位于F島的核電站,受9級(jí)特大地震影響末捣,放射性物質(zhì)發(fā)生泄漏侠姑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一箩做、第九天 我趴在偏房一處隱蔽的房頂上張望莽红。 院中可真熱鬧,春花似錦邦邦、人聲如沸安吁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鬼店。三九已至,卻和暖如春郭赐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背确沸。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工捌锭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人罗捎。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓观谦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親桨菜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豁状,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容