【问题标题】:Proper use of iCloud fallback stores正确使用 iCloud 后备存储
【发布时间】:2013-09-29 01:34:52
【问题描述】:

我正在为 UIManagedDocument 开发一个轻量级包装器,它管理一个人的 iCloud 帐户中是否存在多个文档。见:APManagedDocument

我在很大程度上依赖于 iOS 7 中后备存储的使用,并且尽可能少地关心 iCloud 的当前状态。因此,根据我对 WWDC 2013 Video - Session 207 What’s New in Core Data and iCloud的理解,让核心数据在后备存储方面做最好的事情@

首先简要概述一下我的经理的工作方式: 我在本地沙箱中创建了所有UIManagedDocuments,并设置了适当的持久性存储选项以启用 iCloud 同步。我从不从沙盒中移动 UIManagedDocument 包。

  • 当我想知道云中存在哪些文档时,我会执行元数据查询。
  • 当我想打开其中一个文档时,我首先检查它是否存在于本地沙箱中,如果不存在则在本地沙箱中创建它。 (这就要求应用需要等待Using local storage: 0消息对应的通知。)
  • 通过此设置,我永远不需要知道 iCloud 是否已启用或已登录。我只需在本地工作,让核心数据通过 iCloud 完成它的工作。

到目前为止,一切都很好,但我遇到了用户在登录 iCloud 之前创建新文档的场景,我遇到了以下问题:

  • 我无法执行元数据查询,因为没有 iCloud 可供查询。
  • 由于 1,我不得不回退到执行智能本地扫描,以查找路径中包含“local/store/persistentStore”的包并将其列为有效文档。
  • 后来当用户登录时,我的理解是核心数据会将我的本地存储数据移动到云中,但我没有看到。相反,我看到的是为 iCloud 帐户创建了一个新的持久存储,但没有数据。

我最大的问题是,当涉及到本地后备商店时,正确的方法是什么?我的假设哪里错了?

这是我发布 iOS 7 更新所需的最后一部分。任何和所有反馈都将不胜感激,并将反映在我的 github 项目中,以便其他人可以从我的错误中吸取教训。

我在 Apple 开发者论坛中有此问题的副本。我会用我从那里得到的任何发现来更新这个线程。我认为这个问题很重要,并且随着 iOS 7 的发布仍未得到解决。后备存储是 iCloud 技术的巨大进步,但本地存储部分仍然有点不确定。

【问题讨论】:

  • 我已向 Apple 开具支持票,希望能解决此问题。
  • @dtrotzr .... 支持票有任何更新吗?我也有同样的问题....
  • @Max 在下面看到我的答案。 Apple 终于回复了我,但我对这个答案并不兴奋我觉得工程师没有花时间完全理解我的答案,或者我在后备商店做的事情都错了。

标签: core-data ios7 icloud uimanageddocument


【解决方案1】:

我现在已经解决了这个问题,因为我似乎无法获得有关后备商店在这种情况下应该如何工作的信息。

基本上我现在要做的是,如果用户未登录,我会创建未启用 iCloud 同步选项的文档。

然后在启动时,如果启用了 iCloud,我会扫描需要迁移的文档,只需在启用 iCloud 选项的情况下打开它们即可迁移它们。一旦打开,我就会关闭文档,因为这足以让它们通过元数据扫描进行迁移和扫描。

最后,在迁移完成后,我开始重新扫描文档。

它有效,但它有点像黑客。

参考 APManagedDocument 提交: 421aaae

【讨论】:

  • 是的,如果 iCloud 从不可用切换到可用,是否足以重新打开文档并提供将它们迁移到云的选项,但是在用户有文档的相反情况下你会做什么云,他在设置中将iCloud从ON切换到OFF?如何将文档从启用 iCloud 更改为仅限本地?在没有 iCloud 选项的情况下关闭并重新打开它是否足够?
  • 在这种情况下,您甚至无法访问该文档。你清理你的状态并继续前进。
  • 抱歉,我的意思是用户不是在“设置应用程序/文档和数据”中而是在“应用程序设置包”中将 iCloud 设置为关闭,就像他在 iWork 中所做的那样。该设备仍然具有 iCloud 访问权限,如果用户选择将数据保留在设备上,我应该将 UIManagedDocument 从云端移动到本地。
  • 在这种情况下,您将数据移回本地沙箱并从持久存储中删除普遍存在的数据。它实际上不是微不足道的,不推荐。
【解决方案2】:

今晚终于收到回复了。我不能 100% 确定他理解我的问题,但我会花一些时间来理解他的回答,然后再做出判断。

这是他的回应:

感谢您向 Apple Worldwide Developer Technical 咨询 支持。我正在回复让您知道我已收到您的 请求技术援助。

Core Data 不会自动将您的 UIManagedDocument 移动到 云为你。您需要在 ubiquity 中创建一个新文档 容器然后将持久存储从本地沙箱迁移到 你无处不在的容器。迁移对于创建所有 事务日志,以便其他设备可以创建该文档。

你可以在你的 UIManagedDocument 中实现这个类方法 子类:

  • (void)moveDocumentAtURL:(NSURL *)sourceDocumentURL toUbiquityContainer:(NSURL *)ubiquityContainerURL;

该方法本质上会在以下位置创建一个新文档 “ubiquityContainerURL”,然后您从 “sourceDocumentURL”到“ubiquityContainerURL”。你会用 “migratePersistentStore”来执行迁移。

这是一个例子:

// The name of the file that contains the store identifier.
static NSString *DocumentMetadataFileName = @"DocumentMetadata.plist";

// The name of the file package subdirectory that contains the Core Data store when local.
static NSString *StoreDirectoryComponentLocal = @"StoreContent";

// The name of the file package subdirectory that contains the Core Data store when in the cloud. The Core Data store itself should not be synced directly, so it is placed in a .nosync directory.
static NSString *StoreDirectoryComponentCloud = @"StoreContent.nosync";

+ (NSDictionary *)optionsForStoreAtURL:(NSURL *)url {

    NSURL *metadataDictionaryURL = [url URLByAppendingPathComponent:DocumentMetadataFileName];
    NSDictionary __block *storeMetadata = nil;

    /*
     Perform a coordinated read of the store metadata file; the coordinated read ensures it is downloaded in the event that the document is cloud-based.
     */
    NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
    [fileCoordinator coordinateReadingItemAtURL:metadataDictionaryURL options:0 error:NULL byAccessor:^(NSURL *newURL) {
        storeMetadata = [[NSDictionary alloc] initWithContentsOfURL:newURL];
    }];

    NSString *persistentStoreUbiquitousContentName = nil;

    if (storeMetadata != nil) {

        persistentStoreUbiquitousContentName = [storeMetadata objectForKey:PersistentStoreUbiquitousContentNameKey];
        if (persistentStoreUbiquitousContentName == nil) {
            // Should not get here.
            NSLog(@"ERROR in optionsForStoreAtURL:");
            NSLog(@"persistentStoreUbiquitousContentName == nil");
            abort();
        }
    }
    else {

        CFUUIDRef uuid = CFUUIDCreate(NULL);
        CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
        persistentStoreUbiquitousContentName = (__bridge_transfer NSString *)uuidString;
        CFRelease(uuid);
    }

    // NSPersistentStoreUbiquitousContentURLKey should be the TransactionLogs directory.

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             persistentStoreUbiquitousContentName, NSPersistentStoreUbiquitousContentNameKey,
                             [[self URLForUbiquityTransactionLogs] URLByAppendingPathComponent:persistentStoreUbiquitousContentName] , NSPersistentStoreUbiquitousContentURLKey, nil];

    return options;
}

+ (void)moveDocumentAtURL:(NSURL *)sourceDocumentURL toUbiquityContainer:(NSURL *)ubiquityContainerURL {

    if (ubiquityContainerURL == nil) {

        // iCloud isn't configured.
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                              NSLocalizedString(@"iCloud does not appear to be configured.", @""), NSLocalizedFailureReasonErrorKey, nil];
        NSError *error = [NSError errorWithDomain:@"Application" code:404 userInfo:dict];
        NSLog(@"%@", [error localizedFailureReason]);
        return;
    }

    // Move the document to the cloud using its existing filename
    NSManagedObjectModel *model = [self managedObjectModel];
    NSDictionary *ubiquitousOptions = [self optionsForStoreAtURL:sourceDocumentURL];

    NSString *documentName = [[sourceDocumentURL lastPathComponent] stringByDeletingPathExtension];
    documentName = [documentName stringByAppendingPathExtension:@"wwWhat"];
    NSURL *destinationURL = [ubiquityContainerURL URLByAppendingPathComponent:documentName];

    dispatch_queue_t q_default;
    q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(q_default, ^{

        NSError __block *error = nil;

        NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
        [coordinator coordinateWritingItemAtURL:destinationURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *destination) {

            NSFileManager *fileManager = [[NSFileManager alloc] init];
            [fileManager removeItemAtURL:destination error:nil];

            NSURL *destinationStoreDirectoryURL = [destination URLByAppendingPathComponent:StoreDirectoryComponentCloud isDirectory:YES];
            NSURL *destinationStoreURL = [destinationStoreDirectoryURL URLByAppendingPathComponent:StoreFileName isDirectory:NO];

            NSURL *sourceStoreURL = [[sourceDocumentURL URLByAppendingPathComponent:StoreDirectoryComponentLocal isDirectory:YES] URLByAppendingPathComponent:StoreFileName isDirectory:NO];
            NSURL *originalMetadataURL = [sourceDocumentURL URLByAppendingPathComponent:DocumentMetadataFileName isDirectory:NO];
            NSURL *destinationMetadataURL = [destination URLByAppendingPathComponent:DocumentMetadataFileName isDirectory:NO];

            [fileManager createDirectoryAtURL:destinationStoreDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil];
            [fileManager copyItemAtURL:originalMetadataURL toURL:destinationMetadataURL error:nil];

            NSPersistentStoreCoordinator *pscForSave = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model];
            id store = [pscForSave addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:nil error:nil];

            id success = [pscForSave migratePersistentStore:store toURL:destinationStoreURL options:ubiquitousOptions withType:NSSQLiteStoreType error:&error];

            if (success) {
                [fileManager removeItemAtURL:sourceDocumentURL error:NULL];
            }
            else {
                NSLog(@"Failed to migrate store: %@", error);
            }
        }];
    });
}

【讨论】:

    【解决方案3】:

    他的反应与你所看到的不一致吗?没有数据,因为您没有将事务日志放在云中,以便其他设备可以从日志中重新创建文档。通过迁移,我猜您在相应的 iCloud 目录中自动生成了日志文件。但随后 207 视频似乎表明不再需要使用 .sync 文件夹。

    我只要求他们提供一个很好的工作示例......

    知道如何从 OSX 访问这些文件吗?

    顺便说一句,你的东西看起来不错,我希望在几天内尝试使用它。我真的很想看看如何从 OSX 访问这些文件。据我了解, NSPersistentDocument 不支持 iCloud。

    编辑: 我刚刚仔细查看了您的 APManagedDocumentManager,您似乎没有在 NSPersistentStoreUbiquitousContentURLKey 值中包含 iCloud 路径。除非我错过了什么,否则您只是使用子目录而不是完整的 iCloud 路径,而且您使用的是 NSString 而不是 URL(不确定这是否会有所不同)。

    编辑:也许我们应该通过电话进行讨论?无论如何,我的更多发现如下: 我刚刚安装了 Mavericks,在看了两次视频之后,我正在测试以下内容: 仅使用下面的代码创建新文件 - 没有 UIManagedDocument 或任何东西。 _storeURL 指向视频中建议的本地目录。而且我没有使用任何 NSPersistentStoreUbiquitousContentURLKey 因为它不再需要。目前我的文件名没有 UUID。

    当我在任何设备上执行此操作时,会在 iCloud 文档目录之外创建一个 CoreData 目录。 CoreData 目录内部是每个文件名的子目录,其中包含各种 zip 文件和可能是基线和日志文件的东西。没有任何 DocumentMetaData.plist 的迹象。所以这一切看起来很有希望,除了我不知道如何“发现”出现的新文件。我希望我只需要注册一些通知就可以了……现在回到视频,因为我不记得发送了哪些通知以及如何对它们做出反应的详细信息。至少两个平台上的行为是一致的。奇怪的是,这些文档都没有出现在 Mac 应用程序文件打开对话框中,该对话框列出了 iCloud 文档目录中的所有文档,我猜这并不奇怪。

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    //FLOG(@"  got_persistentStoreCoordinator is %@", _persistentStoreCoordinator);
    FLOG(@"  calling addPersistentStoreWithType for path %@", [_storeURL path]);
    NSString *fileName = [[_storeURL URLByDeletingPathExtension] lastPathComponent];
    FLOG(@"  setting NSPersistent for path %@", [_storeURL path]);
    @try {
        store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:_storeURL
                                                                options:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                                                  NSMigratePersistentStoresAutomaticallyOption:@YES,
                                                                  NSInferMappingModelAutomaticallyOption:@YES,
                                                                  NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}
                                                                  error:&error];
    

    ...

    【讨论】:

    • 我使用的是完整的 URL,可能会返回。在 WWDC 2013 207 视频中,他们只提供了一个 NSString 值来表示子文件夹路径组件,因此我正在试验该路径以查看迁移是否可行。
    • 我对他的回应的主要问题是正如您提到的 .nosync 文件夹,并且在视频中他们明确指出要使用后备存储,我们需要将核心数据文件存储在本地存储中,但他提出它在一个无处不在的商店。我看到后备商店功能齐全,只是在应用程序未运行时没有,那时我必须介入并执行迁移,这在我的实现中仍然有点不稳定,我无法判断它是 iCloud 缺陷还是我的实现.
    • 我可能会尝试将文档存储在 ubiquity 存储中,看看后备存储是否仍然有效。我正在尽最大努力避免这种情况,但我一直遇到没有将它们存储在那里的问题,这会导致仅通过元数据查询期间发现的交易收据来检测云中存在哪些文档的问题
    • 好吧,他们肯定会说把它们放在沙箱里,剩下的他们会处理,他们甚至解释了 iCloud 不可用的情况。你在检查 NSPersistentStoreCoordinator 通知吗?它们应包含正在更改的商店的详细信息(WillChange 和 DidChange)。
    • 现在我更困惑了——尽管我将其作为 _storeURL 传递,但我创建的商店并未在本地 /Documents 目录中创建。在四处寻找之后,我在 /tmp 目录中找到了它们。我是否错过了视频中的某些内容 - 他们从未提到过使用 UIManagedDocument 做任何事情。
    猜你喜欢
    • 1970-01-01
    • 2013-06-08
    • 1970-01-01
    • 1970-01-01
    • 2016-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多