1

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

4

3 回答 3

2

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

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

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

最后,在迁移完成后,我开始对文档进行新的扫描。

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

参考 APManagedDocument 提交: 421aaae

于 2013-09-28T20:31:47.557 回答
2

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

以下是他的回应:

感谢您向 Apple 全球开发者技术支持咨询。我正在回复通知您,我已收到您的技术援助请求。

Core Data 不会自动为您将您的 UIManagedDocument 移动到云端。您需要在 ubiquity 容器中创建一个新文档,然后将持久存储从本地沙箱迁移到您的 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);
            }
        }];
    });
}
于 2013-10-03T00:30:00.203 回答
1

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

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

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

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

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

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

当我在任何设备上执行此操作时,会在 iCloud Documents 目录之外创建一个 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];

...

于 2013-10-05T13:29:24.057 回答