【问题标题】:managedObjectContext for NSManagedObject becomes nil after restoring app from inactive从非活动状态恢复应用程序后,NSManagedObject 的 managedObjectContext 变为 nil
【发布时间】:2016-08-02 18:03:54
【问题描述】:

也许你可以帮忙,我在其他问题中找不到类似的东西,所以我想我可能会错过一些明显的东西。

我有 Swift 中的 CoreData + iCloud 应用程序。

问题场景:

  1. 启动应用程序
  2. NSManagedObject 读取/更新
  3. 按“主页”(使应用处于非活动状态)
  4. 恢复应用
  5. NSManagedObject 读取/更新

如果我已经在我的设备上登录了 iCloud,那么这一切正常。

如果我从 iCloud 注销,那么所有应用程序都可以正常工作,只要我执行问题场景,那么在第 5 步中,NSManagedObject 的 managedObjectContext 为 nil,因此我无法对其进行任何更改,并且由于上下文丢失当我需要现有对象的上下文时,它当然会崩溃。

我的问题:

  1. 为什么会出现问题场景中的这种情况?
  2. 如何解决此问题,以便如果应用程序已变为非活动状态,然后又变为活动状态,且 iCloud 用户未登录,CoreData 仍可继续工作?

我的 CoreDataStack:

class CoreDataStack: CustomStringConvertible
{
    static let sharedManager = CoreDataStack()
    static let applicationDocumentsDirectoryName = "iCloud.com.myCompany.myAppID"
    static let errorDomain = "CoreDataStack"

    static let modelName = "DB"
    static let storeName = "DB"
    static var storeFileName: String
    {
        return storeName + ".sqlite"
    }
    var options : [String : AnyObject]?

    var inMemory: Bool = false


    var description: String
    {
        var desc = "context: \(self.managedObjectContext)\n" +
            "modelName: \(CoreDataStack.modelName)" +
            "storeURL: \(self.storeURL)"

        desc += "\nPersistent Stores:\n"
        for store in persistentStoreCoordinator.persistentStores
        {
            desc += "* \(store.URL!.absoluteString)"
        }

        return desc
    }


    lazy var managedObjectModel: NSManagedObjectModel =
    {
        let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd")!
        return NSManagedObjectModel(contentsOfURL: modelURL)!
    }()


    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator =
    {
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

        do
        {
            if self.inMemory
            {
                try coordinator.addPersistentStoreWithType(
                    NSInMemoryStoreType,
                    configuration: nil,
                    URL: nil,
                    options: nil)
            } else
            {
                try coordinator.addPersistentStoreWithType(
                    NSSQLiteStoreType,
                    configuration: nil,
                    URL: self.storeURL,
                    options: self.options)
            }
        } catch var error as NSError
        {
            VTLog.error("Persistent Store Error: \(error)")
        } catch
        {
            fatalError("Error creating Persistent Store!")
        }
        return coordinator
    }()


    /// The directory the application uses to store the Core Data store file.
    lazy var applicationSupportDirectory: NSURL =
    {
        let fileManager = NSFileManager.defaultManager()
        let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)
        let applicationSupportDirectoryURL = urls.last!
        let applicationSupportDirectory =
            applicationSupportDirectoryURL.URLByAppendingPathComponent(applicationDocumentsDirectoryName)

        do
        {
            let properties = try applicationSupportDirectory.resourceValuesForKeys([NSURLIsDirectoryKey])

            if let isDirectory = properties[NSURLIsDirectoryKey] as? Bool where isDirectory == false
            {
                let description = NSLocalizedString("Could not access the application data folder.",
                                                    comment: "Failed to initialize applicationSupportDirectory.")
                let reason = NSLocalizedString("Found a file in its place.",
                                               comment: "Failed to initialize applicationSupportDirectory.")

                throw NSError(domain: errorDomain, code: 201, userInfo:
                [
                    NSLocalizedDescriptionKey: description,
                    NSLocalizedFailureReasonErrorKey: reason
                ])
            }
        } catch let error as NSError where error.code != NSFileReadNoSuchFileError
        {
            fatalError("Error occured: \(error).")
        } catch
        {
            let path = applicationSupportDirectory.path!

            do
            {
                try fileManager.createDirectoryAtPath(path, withIntermediateDirectories:true, attributes:nil)
            }
            catch
            {
                fatalError("Could not create application documents directory at \(path).")
            }
        }

        return applicationSupportDirectory
    }()


    /// URL for the main Core Data store file.
    lazy var storeURL: NSURL =
    {
        return self.applicationSupportDirectory.URLByAppendingPathComponent(storeFileName)
    }()


    lazy var managedObjectContext: NSManagedObjectContext =
    {
        let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        context.persistentStoreCoordinator = self.persistentStoreCoordinator
        return context
    }()


    // ****************************************
    // MARK: - iCloud Sync
    // ****************************************

    var updateContextWithUbiquitousContentUpdates: Bool = false
    {
        willSet
        {
            ubiquitousChangesObserver = newValue ? NSNotificationCenter.defaultCenter() : nil
        }
    }


    private var ubiquitousChangesObserver: NSNotificationCenter?
    {
        didSet
        {
            oldValue?.removeObserver(
                self,
                name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
                object: persistentStoreCoordinator)

            ubiquitousChangesObserver?.addObserver(
                self,
                selector: #selector(self.persistentStoreDidImportUbiquitousContentChanges(_:)),
                name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
                object: persistentStoreCoordinator)


            oldValue?.removeObserver(
                self,
                name: NSPersistentStoreCoordinatorStoresWillChangeNotification,
                object: persistentStoreCoordinator)

            ubiquitousChangesObserver?.addObserver(
                self,
                selector: #selector(self.persistentStoreCoordinatorWillChangeStores(_:)),
                name: NSPersistentStoreCoordinatorStoresWillChangeNotification,
                object: persistentStoreCoordinator)
        }
    }


    @objc func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification)
    {
        VTLog.debug("Merging ubiquitous content changes")
        VTLog.debug(notification)

        self.managedObjectContext.performBlock
        {
            self.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
        }
    }


    @objc func persistentStoreCoordinatorWillChangeStores(notification: NSNotification)
    {
        VTLog.debug(notification)

        if managedObjectContext.hasChanges
        {
            do
            {
                try managedObjectContext.save()
            } catch let error as NSError
            {
                print("Error saving: \(error)", terminator: "")
            }
        }
        managedObjectContext.reset()
    }


    // ***********************************************
    // * Data: iCloud Container Actions
    // ***********************************************

    func deleteiCloudContainer()
    {
        VTLog.debug("Deleting iCloud Container...")

        let currentStore = managedObjectContext.persistentStoreCoordinator!.persistentStores.last!

        VTLog.debug("Located data store [\(currentStore)]")

        managedObjectContext.reset()
        VTLog.debug("managedObjectContext.reset() - OK")

        do
        {
            try managedObjectContext.persistentStoreCoordinator?.removePersistentStore(currentStore)
            VTLog.debug("removePersistentStore() - OK")
        } catch let error as NSError
        {
            VTLog.error("Could not remove persistent store [\(currentStore)]: \(error)")
        }

        do
        {
            try NSPersistentStoreCoordinator.removeUbiquitousContentAndPersistentStoreAtURL(
                currentStore.URL!, options: currentStore.options)
            VTLog.debug("removeUbiquitousContentAndPersistentStoreAtURL() - OK")
        } catch let error as NSError
        {
            VTLog.error("Could not remove Ubiquitous Content and Persistent Store at URL [\(currentStore)]: \(error)")
        }
    }


    //*******************************************
    // MARK: - Init
    //*******************************************

    init(inMemory:Bool = false)
    {
        self.inMemory = inMemory

        self.options = [NSMigratePersistentStoresAutomaticallyOption: true,
            NSInferMappingModelAutomaticallyOption: true,
            NSPersistentStoreUbiquitousContentNameKey: CoreDataStack.storeName]
    }

}

可能有帮助的其他信息:

  1. 所有这些都在模拟器上,无论版本如何:iOS 9.2、iOS 9.3。
  2. 当我登录 iCloud 后,一切正常。
  3. 我注意到路径 storeURL 上实际上不存在 DB.sqlite 文件,它是在路径上创建的,如下所示,但无论是否使用 iCloud 登录都是一样的,所以我不知道是否应该这样方式。
  4. 当我恢复应用程序时,我看到以下操作序列:

@ 2016-04-12 11:30:36: AppDelegate: applicationDidEnterBackground:133: (线程): {number = 10, name = main}

@ 2016-04-12 11:30:37: AppDelegate: applicationWillEnterForeground:141: (线程):{number = 11,name = main}

2016-04-12 11:30:37.150 数我自己[57886:19968276] -PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:: CoreData: Ubiquity: nobody~sim7CC36E42-82CB-5152-91BE-4DD26FE0A420:DB 使用本地存储:1 表示新的 NSFileManager 当前令牌(空)

@ 2016-04-12 11:30:37: CoreDataStack: persistentStoreCoordinatorWillChangeStores:203: NSConcreteNotification 0x7fd3e8d8fdc0 {name = NSPersistentStoreCoordinatorStoresWillChangeNotification;对象 = ;用户信息 = { NSPersistentStoreUbiquitousTransitionTypeKey = 2; 添加=( " (URL: file:///Users/maris/Library/Developer/CoreSimulator/Devices/F9A852DA-595C-4DE2-ADD7-7DECD7D814AD/data/Containers/Data/Application/107B6DB1-C4DC-4626-8933-DACD0575F184/Library /Application%20Support/iCloud.com.myCompany.myAppID/CoreDataUbiquitySupport/nobody~sim7CC36E42-82CB-5152-91BE-4DD26FE0A420/DB/local/store/DB.sqlite)" ); 删除 = ( " (URL: file:///Users/maris/Library/Developer/CoreSimulator/Devices/F9A852DA-595C-4DE2-ADD7-7DECD7D814AD/data/Containers/Data/Application/107B6DB1-C4DC-4626-8933-DACD0575F184/Library /Application%20Support/iCloud.com.myCompany.myAppID/CoreDataUbiquitySupport/nobody~sim7CC36E42-82CB-5152-91BE-4DD26FE0A420/DB/local/store/DB.sqlite)" ); }} (线程): {number = 12, name = main}

@ 2016-04-12 11:30:37: AppDelegate: applicationDidBecomeActive:152: context: 模型名称:DBstoreURL:file:///Users/maris/Library/Developer/CoreSimulator/Devices/F9A852DA-595C-4DE2-ADD7-7DECD7D814AD/data/Containers/Data/Application/107B6DB1-C4DC-4626-8933-DACD0575F184/Library /Application%20Support/iCloud.com.myCompany.myAppID/DB.sqlite 持久存储: * file:///Users/maris/Library/Developer/CoreSimulator/Devices/F9A852DA-595C-4DE2-ADD7-7DECD7D814AD/data/Containers/Data/Application/436959B5-7850-4156-AB3D-A11BE72FF1AF/Library/Application% 20Support/iCloud.com.myCompany.myAppID/CoreDataUbiquitySupport/nobody~sim7CC36E42-82CB-5152-91BE-4DD26FE0A420/DB/local/store/DB.sqlite

  1. 设置时出现问题: stack.updateContextWithUbiquitousContentUpdates = true 但如果我不将此设置为 true,我想我不会立即从 iCloud 获取更新。
  2. 在 GitHub 上查看演示问题的 Xcode 项目:https://github.com/marisveide/iCloudCoreDataProblem(阅读顶部的 AppDelegate.swift 文件 cmets) 如果在单独的分支中看到代码更改以不丢失上下文的解决方案,那将是非常棒的。

【问题讨论】:

  • persistentStoreCoordinatorWillChangeStores 基本上是说存储被删除并重新创建,所以我猜上下文不再连接到新的持久存储并且托管对象都是孤立的......
  • 好的,所以在 iCloud 登录上下文中,这是否意味着商店是从 iCloud 容器重新创建的。因此,当没有 iCloud 用户时,就没有 iCloud 容器可以恢复吗?在那种情况下,它不能解释为什么应用程序在我退出并重新启动时可以正常工作并保持一切正常。
  • 附加信息:当我设置此问题时出现问题:stack.updateContextWithUbiquitousContentUpdates = true 但如果我不将此设置为 true,我想我不会立即从 iCloud 获取更新。那么,正常的工作流程是什么?帮助,伙计们......这是我第二天找到解决方案。
  • 添加到 GitHub 的项目清楚地表明了问题:github.com/marisveide/iCloudCoreDataProblem

标签: ios swift core-data icloud nsmanagedobjectcontext


【解决方案1】:

好的,我找到了解决方案 - 也许这对其他人也有帮助。

基本上,通过检查是否启用了 iCloud,然后定制 CoreDataStack 设置。

像这样:

init(inMemory:Bool = false)
{
    self.inMemory = inMemory

    self.options = [NSMigratePersistentStoresAutomaticallyOption: true,
                    NSInferMappingModelAutomaticallyOption: true]

    if iCloudEnabled
    {
        self.options?[NSPersistentStoreUbiquitousContentNameKey] = CoreDataStack.storeName
        self.monitorUbiquitousContentUpdatesIfiCloudEnabled()
    }

}

可以在 GitHub 项目示例中找到更多详细信息 - 提交的差异解决了我的问题: https://github.com/marisveide/iCloudCoreDataProblem/commit/1d2da6c000bee7a66274192da2a637ba7c8cabf5#diff-8efc4cba62b5f1efad252e6f27b5b30b

在这里下载整个项目: https://github.com/marisveide/iCloudCoreDataProblem

哇...过去 3 天的调试和搜索“很有趣”。

【讨论】:

    【解决方案2】:

    当您退出并重新启动时,应用程序会从内存中删除,因此会再次创建持久存储和上下文并将它们链接在一起。

    当您将其置于后台,然后将未发生的情况提前提出时,商店和上下文都仍然存在。这里看起来发生的事情是持久存储文件已更改并且上下文与它断开连接(这有点猜测,我还没有测试过)。

    所以,在persistentStoreCoordinatorWillChangeStores 中,您应该真的销毁上下文并创建一个新上下文。这也意味着销毁所有来自旧上下文的托管对象并从新上下文中获取新版本(假设它们仍然存在)。

    【讨论】:

    • 您通常如何解决这些问题?我应该重新加载 applicationDidBecomeActive() 中的所有对象(并且在应用程序刚刚启动时不要这样做)?这是一个相当大的问题,因为应用程序可以处于任何 UI 状态,并且 NSManagedObjects 绑定到 UI 元素。这意味着找到当前的 ViewController 及其状态,并刷新每个 UI 元素以从 CoreData 绑定到新的对象实例。听起来像是总开销。这种场景的典型工作流程是什么:应用非活动 -> 活动?
    • 我之前没有遇到过类似的问题,但我之前也没有在 iCloud 中使用过 core-data。通常,当我拆除核心数据堆栈时,我会将应用程序返回到初始屏幕类型状态并从那里重建。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多