CoreData遇見iCloud的那些坑
盡管蘋果把iCloud與CoreData之間的***配合吹的天花亂墜,但在iOS7之前,想用iCloud同步CoreData數(shù)據(jù)簡直就是噩夢,蘋果自己也承認(rèn)了之前的諸多bug和不穩(wěn)定性,這讓蘋果不得不重新站出來說他們的工程師已經(jīng)在iOS7中修復(fù)了bug,增強(qiáng)了體驗,balabala,關(guān)鍵是對于程序員來說,將iCloud集成到CoreData變得無比簡單。
在蘋果的官方文檔中已經(jīng)把配置工作敘述的很明確了,簡單地說可以總結(jié)為三步:
在iTunes Connect創(chuàng)建App ID,在Xcode中找到項目的Capabilities標(biāo)簽并開啟iCloud選項。這會為你創(chuàng)建一個默認(rèn)的iCloud容器,名字格式為“com.XXX.yourAppID”
添加NSPersistentStore時向options參數(shù)傳入一個持久存儲的名稱,自己起一個就行,示例代碼如下:
- NSDictionary *storeOptions =
- @{NSPersistentStoreUbiquitousContentNameKey: @"MyAppCloudStore"};
- NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
- configuration:nil
- URL:storeURL
- options:storeOptions
- error:&error];
對NSPersistentStoreCoordinatorStoresWillChangeNotification,NSPersistentStoreCoordinatorStoresDidChangeNotification和NSPersistentStoreDidImportUbiquitousContentChangesNotification這三個通知進(jìn)行注冊以便接收通知后對數(shù)據(jù)進(jìn)行處理。***用NSNotificationCenter的addObserverForName:object:queue:usingBlock:方法來使邏輯更加明確,代碼更緊湊。
***貼上Swift實現(xiàn)persistentStoreCoordinator的代碼:
- var persistentStoreCoordinator: NSPersistentStoreCoordinator! {
- if _persistentStoreCoordinator == nil {
- let storeURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent("HardChoice.sqlite")
- var error: NSError? = nil
- _persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
- // iCloud notification subscriptions
- let dc = NSNotificationCenter.defaultCenter()
- dc.addObserverForName(NSPersistentStoreCoordinatorStoresWillChangeNotification, object: self.persistentStoreCoordinator, queue: NSOperationQueue.mainQueue(), usingBlock: { (note) -> Void in
- self.managedObjectContext.performBlock({ () -> Void in
- var error: NSError? = nil
- if self.managedObjectContext.hasChanges {
- if !self.managedObjectContext.save(&error) {
- println(error?.description)
- }
- }
- self.managedObjectContext.reset()
- })
- })
- dc.addObserverForName(NSPersistentStoreCoordinatorStoresDidChangeNotification, object: self.persistentStoreCoordinator, queue: NSOperationQueue.mainQueue(), usingBlock: { (note) -> Void in
- self.managedObjectContext.performBlock({ () -> Void in
- var error: NSError? = nil
- if self.managedObjectContext.hasChanges {
- if !self.managedObjectContext.save(&error) {
- println(error?.description)
- }
- }
- })
- })
- dc.addObserverForName(NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: self.persistentStoreCoordinator, queue: NSOperationQueue.mainQueue(), usingBlock: { (note) -> Void in
- self.managedObjectContext.performBlock({ () -> Void in
- self.managedObjectContext.mergeChangesFromContextDidSaveNotification(note)
- })
- })
- if _persistentStoreCoordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: [NSPersistentStoreUbiquitousContentNameKey:"MyAppCloudStore"], error: &error) == nil {
- println("Unresolved error \(error), \(error?.userInfo)")
- abort()
- }
- }
- return _persistentStoreCoordinator!
- }
- var _persistentStoreCoordinator: NSPersistentStoreCoordinator? = nil
當(dāng)然你也可以用lazy關(guān)鍵字同樣來實現(xiàn)persistentStoreCoordinator屬性的惰性加載。
已經(jīng)有人將整套CoreData集成iCloud的邏輯抽象出來,比如iCloudCoreDataStack。完全不需要再用宣稱能讓CoreData與iCloud搭配更簡單的第三方庫了,因為在iOS7中蘋果的確讓它簡單至極了。
然而當(dāng)Xcode6和iOS8襲來,一個個坑爭先恐后的出現(xiàn)了。
首先是iCloud Drive,它與之前iCloud有沖突。如升級,請徹底,讓測試機(jī)器都升級iCloud Drive。
然后是Xcode6中開啟Capabilities標(biāo)簽的iCloud選項卡后,如下的場景簡直是臥槽:
該怎么選怎么選?。?!我只能說按照上圖這么選就對了。順便說一下iCloud默認(rèn)容器名稱格式已經(jīng)變成了“iCloud.com.yourname.yourAppID”,其實這也不太準(zhǔn)確,官方稱作“iCloud.$(CFBundleIdentifier)”,后面的美元號所指的變量就是General中Identity一欄的“Bundle Identifier”值。此外“Key-value storage”和“CloudKit”選項選不選都可以,但“iCloud Documents”一定要勾選,否則是無法同步CoreData數(shù)據(jù)的。
PS:CloudKit是蘋果***推出的基于iCloud的一個云端數(shù)據(jù)存儲服務(wù),提供了低成本的云存儲并能作為一個后端服務(wù)通過用戶們的iCloud賬號分享其應(yīng)用數(shù)據(jù)。
接下來是時候檢查我們是否成功添加了iCloud容器,可以在applicationDidFinishLaunchingWithOptions方法中嘗試獲取容器的URL來判斷:
- let containerURL = NSFileManager.defaultManager().URLForUbiquityContainerIdentifier("iCloud.com.yulingtianxia.HardChoice")
- if containerURL != nil {
- println("success:\(containerURL)")
- }
- else{
- println("URL=nil")
- }
如果之前沒有在Capabilities標(biāo)簽的iCloud中勾選“iCloud Documents”,“URLForUbiquityContainerIdentifier”方法會始終返回nil。來看看蘋果開發(fā)者論壇上關(guān)于這個話題的討論吧
PS:官方文檔不建議在主線程使用URLForUbiquityContainerIdentifier方法,因為它可能需要較長時間來返回URL而阻塞主線程。這里只是為了測試使用。
然而判斷iCloud是否真的與CoreData工作正常,蘋果的官方文檔寫的很詳細(xì):Using the iCloud Debugging Tools
當(dāng)我興致沖沖的打開Xcode中的debug navigator,點(diǎn)擊左邊的iCloud查看狀態(tài)時,被眼前的一切驚呆了:
“iCloud Usage”告訴我狀態(tài)不可用,然而右下角的日志中Using local storage已經(jīng)從1變成了0,也就是證明了我的APP(HardChoice)已經(jīng)從CoreData使用本地持久倉庫轉(zhuǎn)移到了使用“iCloud-enabled”持久倉庫。“Transfer Activity”中柱狀圖更是顯示從iCloud下載了數(shù)據(jù)。而這其實應(yīng)該是Xcode6的一個bug,有人已經(jīng)在蘋果開發(fā)者論壇討論了。
根據(jù)我的測試,只勾選“Key-value storage”或者在模擬器上調(diào)試時,“iCloud Usage”都不會出現(xiàn)。而即使“iCloud Usage”出現(xiàn)了,狀態(tài)也始終是Disabled,“Transfer Activity”也不是很靈敏。唯獨(dú)只能相信CoreData的log了。
但我們可以查看“My Mac”的“iCloud Usage”而不是iPhone的“iCloud Usage”:
在“Documents”一欄中可以看出我在兩個設(shè)備間同步了數(shù)據(jù),“mobile”后面跟著的是我的設(shè)備編號。展開數(shù)據(jù)可以看到更詳細(xì)的同步記錄:
雖然通過“My Mac”可以看到iCloud與CoreData的數(shù)據(jù)同步記錄,但是在Xcode6.1.1中“Documents”的顯示不是很正常,在***的Xcode6.2beta版中雖然修復(fù)了“Documents”的顯示問題,但“iCloud Usage”的種種bug依然存在。
***,確保網(wǎng)絡(luò)通常。我在中軟實訓(xùn)一個月時,網(wǎng)絡(luò)奇差,或是屏蔽了iCloud,一直沒能調(diào)試成功。
貼一張HardChoice同步成功的測試圖,因為我是用Swift寫的這個Demo,所以喜歡用Swift的可以直接把我的那部分源碼粘過去用: