UITabBarController 应用程序中 iCloud 同步的核心数据堆栈实现 (Swift 1.2)
Posted
技术标签:
【中文标题】UITabBarController 应用程序中 iCloud 同步的核心数据堆栈实现 (Swift 1.2)【英文标题】:Core Data stack implementation for iCloud sync in a UITabBarController app (Swift 1.2) 【发布时间】:2015-04-13 19:48:57 【问题描述】:过去 4 天我一直在尝试为我的 Swift 1.2 应用实现一个适当的带有 iCloud 同步的核心数据堆栈,但我真的需要一些帮助。
以前,我使用的是从应用程序的任何地方访问的全局托管上下文;知道这是一个糟糕的实现,现在我添加了 iCloud 同步,我决定摆脱它,即使应用程序运行良好。
到目前为止,我已经实现了一个新的、可工作的 Core Data 堆栈,它在设备之间实现了不错但不完美的云同步。
现在我面临两个问题:
有时,一些对象不同步。 考虑到我的应用程序的特殊结构(稍后我会解释),我不知道我应该如何以及在我的代码中的何处处理用户登录或退出 iCloud 时 Core Data 发送的通知。但是,在解决这些问题之前,我真的很感激 - 如果合适的话 - 对我迄今为止所做的工作进行一些验证,主要是为了确认我正在写这篇文章:因为我已经花了很多时间改变我的核心数据堆栈,在继续之前我想知道我是否正确地传播了上下文(我的应用程序的结构不符合我在网上找到的任何教程,所以我有即兴发挥一点),或者如果我犯了一些会影响可靠同步或未来发展的基本错误。
我的应用结构如下:
UITabBarViewController
作为初始 ViewController
第一个标签:UIViewController
(在应用启动时显示)
第二个标签:UITableViewController
嵌入在UINavigationController
第三个标签:另一个UITableViewController
嵌入另一个UINavigationController
我有一个带有以下代码的 CoreDataStack.swift 类:
import CoreData
@objc class CoreDataStack : Printable
let context : NSManagedObjectContext
let psc : NSPersistentStoreCoordinator
let model : NSManagedObjectModel
let store : NSPersistentStore?
var description : String
return "context: \(context)\n" + "model: \(model)"
var applicationDocumentsDirectory : NSURL =
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
return urls[0]
()
init()
let modelURL = NSBundle.mainBundle().URLForResource("MyDataModel", withExtension:"momd")
model = NSManagedObjectModel(contentsOfURL: modelURL!)!
psc = NSPersistentStoreCoordinator(managedObjectModel: model)
context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
context.persistentStoreCoordinator = psc
let documentsURL = applicationDocumentsDirectory
let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")
let options = [NSPersistentStoreUbiquitousContentNameKey: "MyApp", NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)
if store == nil
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict as [NSObject : AnyObject])
println("Error adding persistent store: \(error), \(error!.userInfo)")
abort()
func saveContext()
var error: NSError? = nil
if context.hasChanges && !context.save(&error)
println("Could not save: \(error), \(error!.userInfo)")
var updateContextWithUbiquitousContentUpdates: Bool = false
willSet
ubiquitousChangesObserver = newValue ? NSNotificationCenter.defaultCenter() : nil
private var ubiquitousChangesObserver : NSNotificationCenter?
didSet
oldValue?.removeObserver(self, name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
ubiquitousChangesObserver?.addObserver(self, selector: "persistentStoreDidImportUbiquitousContentChanges:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification)
println("Merging ubiquitous content changes")
context.performBlock
self.context.mergeChangesFromContextDidSaveNotification(notification)
在我的 AppDelegate.swift 中,我在 var window: UIWindow?
下方添加了以下代码:
lazy var coreDataStack = CoreDataStack()
coreDataStack.updateContextWithUbiquitousContentUpdates = true
// The following code is the way I found to propagate the managed context of the stack instantiated above in all the ViewControllers of the UITabBarController, including those embedded in the two NavigationControllers;
// since in the future I'll probably need some flexibility in term of adding / rearranging the VCs in the TabBar, I kind of like this way to pass around the context.
// I could have also passed the context to the CustomTabBarViewController and from there do the same thing, but I figured I could just pass the context from AppDelegate, since I already can access all the ViewControllers from here with the following code.
var tabBarController = self.window!.rootViewController as! CustomTabBarViewController
for eachViewController in tabBarController.viewControllers!
if eachViewController.isKindOfClass(CustomViewController)
(eachViewController as! CustomViewController).passedManagedContext = coreDataStack.context // Context is passed to the VC of 1st tab
if eachViewController.isKindOfClass(UINavigationController)
var firstNavController = tabBarController.viewControllers![1] as! UINavigationController
for tvc in firstNavController.viewControllers!
if tvc.isKindOfClass(FirstCustomTableViewController)
(tvc as! FirstCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 2
var secondNavController = tabBarController.viewControllers![2] as! UINavigationController
for tvc in secondNavController.viewControllers!
if tvc.isKindOfClass(SecondCustomTableViewController)
(tvc as! SecondCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 3
// Of course, in applicationDidEnterBackground: and applicationWillTerminate: I save the context; obviously, I also save the context, when appropriate, from the other ViewControllers.
有了这个结构,我在 AppDelegate 中实例化了我的堆栈,并从那里将它传播到 TabBar 的 3 个元素;从这些中,我再次将上下文传播到我呈现的每个其他 ViewController。我将上下文记录到控制台,我可以确认它始终是相同的。
事实上,使用此代码的应用程序可以正常工作。
我不能说它是完美的,因为正如我所说,有时一些对象不同步,但我怀疑这些对象不同步的原因是另一个(简单地说,我有 2 个NSManagedObject
子类; subclass1 的对象具有 subclass2 的对象作为属性;如果我使用现有的 subclass2 对象作为属性创建一个新的 subclass1 对象,则同步很好;如果我还创建了一个新的 subclass2 对象,请将其保存并立即将其设置为 subclass1 的属性,有时 subclass2 对象不会在其他设备上同步,而 subclass1 会然后错过该属性...我可以稍后再处理)。
在深入研究这个同步问题之前,我真的很想知道到目前为止我对堆栈所做的工作是否有意义,或者它是否很糟糕并且需要被屏蔽。
然后,如果上面的所有代码都不可怕,并且偶尔丢失对象同步的原因是我怀疑的原因,那么另一个问题来了,这是一个大问题:在哪里当用户从 iCloud(NSPersistentStoreCoordinatorStoresWillChangeNotification
和 NSPersistentStoreCoordinatorStoresDidChangeNotification
)登录或注销时,我应该放置代码来处理发生的通知吗?
我尝试在我的 AppDelegate 和我的 CoreDataStack 类中基于 Core Data by Tutorials 书将我编写的方法(没有实际功能,目前我只在控制台上记录一些内容以知道我到达了那里),但在两者中案例当我在应用程序运行时从 iCloud 登录或注销时,应用程序在控制台中没有一行就崩溃了,所以我不知道这个问题。
也许我应该将处理这些通知的方法放在所有 ViewController 中,因为提取请求发生在那里并且 UI 是从这些类更新的,但我没有传递整个 coreDataStack 对象,只有上下文......所以我错过了一些东西。 我应该传递整个堆栈,而不仅仅是上下文吗?可以从我的 CoreDataStack 处理这些通知,还是应该从 AppDelegate 处理这些通知?
任何帮助将不胜感激......
提前谢谢,如果我的问题不清楚,请原谅(我是个初学者,英语不是我的主要语言......)。
另外,感谢您抽出宝贵时间阅读这个长问题!
@cdf1982
【问题讨论】:
我建议您将问题分解成更容易回答的小块。 【参考方案1】:我认为问题在于 iCloud + CD 从未正常工作。这不是开发人员代码问题,问题在于 Apple 的 iCloud + CD 实现根本失败。
【讨论】:
以上是关于UITabBarController 应用程序中 iCloud 同步的核心数据堆栈实现 (Swift 1.2)的主要内容,如果未能解决你的问题,请参考以下文章
保持 UITabbarController 在每个视图中可见
从 UITabBarController 应用程序中的另一个 UIViewController 调用 UISplitViewController
UISplitViewController 和 UITabBarController 在通用应用程序的 MasterViewController 中