从非活动

时间:2016-04-12 08:43:52

标签: ios swift core-data icloud nsmanagedobjectcontext

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

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

问题情景:

  1. 启动应用
  2. NSManagedObject读取/更新
  3. 按" Home" (让app无效)
  4. 还原应用
  5. NSManagedObject读取/更新
  6. 如果我已经在我的设备上登录iCloud,那么这样可以正常工作。

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

    我的问题:

    1. 为什么会出现问题场景?
    2. 如何解决这个问题,以便如果应用程序变为非活动状态,然后处于活动状态,而没有登录iCloud用户,CoreData会继续工作吗?
    3. 我的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. 所有这些都在Simulator上,无论版本如何:iOS 9.2,iOS 9.3。
      2. 当我登录iCloud时,一切正常。
      3. 我注意到DB.sqlite文件实际上并不存在于路径storeURL上,它在路径上创建,如下所示,但它与iCloud登录相同或没有,所以我不知道它是否应该那样。
      4. 当我恢复应用时,我会看到以下一系列操作:
      5.   

        @ 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当前令牌(null)

             

        @ 2016-04-12 11:30:37:CoreDataStack:persistentStoreCoordinatorWillChangeStores:203:NSConcreteNotification 0x7fd3e8d8fdc0 {name = NSPersistentStoreCoordinatorStoresWillChangeNotification; object =; userInfo = {       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 /应用%20Support / iCloud.com.myCompany.myAppID / CoreDataUbiquitySupport /无人〜sim7CC36E42-82CB-5152-91BE-4DD26FE0A420 / DB /本地/存储/ 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 /应用%20Support / iCloud.com.myCompany.myAppID / CoreDataUbiquitySupport /无人〜sim7CC36E42-82CB-5152-91BE-4DD26FE0A420 / DB /本地/存储/ DB.sqlite)"       );   }}   (主题):{number = 12,name = main}

             

        @ 2016-04-12 11:30:37:AppDelegate:applicationDidBecomeActive:152:context:   modelName: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 /无人〜sim7CC36E42-82CB-5152-91BE-4DD26FE0A420 / DB /本地/存储/ DB.sqlite

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

2 个答案:

答案 0 :(得分:0)

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

当你投入背景然后带来没有发生的前锋时,商店和上下文都仍然存在。这里看起来正在发生的是持久性存储文件被更改并且上下文与它断开连接(这是一个猜测,我还没有对此进行测试)。

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

答案 1 :(得分:0)

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

基本上,通过检查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天的调试和搜索。