正确使用iCloud后备存储

时间:2013-09-24 01:08:05

标签: core-data ios7 icloud uimanageddocument

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

我在很大程度上依赖于在iOS 7中使用后备存储,并尽可能少地关注iCloud的当前状态。因此,根据我对WWDC 2013视频 - 会议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技术的一大进步,但本地存储部分仍然有点不确定。

3 个答案:

答案 0 :(得分:2)

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

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

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

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

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

请参阅APManagedDocument提交: 421aaae

答案 1 :(得分:2)

我今晚终于得到了答复。我不是100%肯定他理解我的问题,但在作出判断之前,我会花一些时间来理解他的答案。

以下是他的回应:

  

感谢您对Apple Worldwide Developer Technical的询问   支持。我回应是让你知道我收到了你的   请求技术援助。

     

核心数据不会自动将您的UIManagedDocument移动到   云为你。您需要在普遍存在的情况下创建一个新文档   然后容器将持久存储从本地沙箱迁移到   你无处不在的容器。迁移是创建所有人的必要条件   事务日志,以便其他设备可以创建该文档。

     

您可以将此类方法实现到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);
            }
        }];
    });
}

答案 2 :(得分:1)

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

我要求的一个好的工作实例就是......

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

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

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

编辑:我们应该通过电话进行讨论吗?无论如何,我的调查结果如下: 我刚刚安装了小牛队,并且经过两次视频后我正在测试以下内容: 仅使用下面的代码创建新文件 - 没有UIManagedDocument或任何东西。 _storeURL指向视频中建议的本地目录。我没有使用任何NSPersistentStoreUbiquitousContentURLKey,因为它不再需要。目前我的文件名没有UUID。

当我在任何设备上执行此操作时,将在iCloud Documents目录之外创建CoreData目录。 CoreData目录中是每个文件名的子目录,其中包括各种zip文件和可能是基线和日志文件的东西。没有任何DocumentMetaData.plist的迹象。所以这看起来很有希望,除了我无法弄清楚如何“发现”出现的新文件。我希望我只需要注册一些通知并完成...现在回到视频,因为我无法回想起发送通知的详细信息以及如何对它们作出反应。至少两种平台上的行为都是一致的。奇怪的是,这些文档中没有一个出现在Mac Apps File-Open对话框中,该对话框列出了iCloud Documents目录中的所有文档,我想这并不奇怪。

enter image description here

_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];

...