Вопрос

I am working on a lightweight wrapper for UIManagedDocument which manages the existence of multiple documents in a person's iCloud account. See: APManagedDocument

I am relying heavily on the use of fallback stores in iOS 7 and caring as little as possible about the current state of iCloud. Thus letting core data do what it does best in regards the fallback stores based on my understanding from the WWDC 2013 Video - Session 207 What’s New in Core Data and iCloud

First a quick overview of how my manager works: I create all my UIManagedDocuments in the local sandbox and set the appropriate persistent store options to enable iCloud sync. I never move the UIManagedDocument package from the sandbox.

  • When I want to know what documents exist in the cloud I perform a metadata query.
  • When I want to open one of those documents I check to see if it exists in the local sandbox first and if not create it in the local sandbox. (This requires that the app needs to wait for the notification corresponding to the Using local storage: 0 message.)
  • With this set up I never need to know if iCloud is enabled or logged in. I just work locally and let core data do its thing with iCloud.

So far everything is working great but I ran into a little pickle with the scenario where the user creates a new document prior to logging into iCloud and I am presented with the following issues:

  • I cannot perform a metadata query because there is no iCloud to query.
  • Because of 1 I have to fall back to doing an intelligent local scan looking for packages that have 'local/store/persistentStore' in their path and listing those as valid documents.
  • Later when the user logs in it was my understanding that core data would move my local store data to the cloud but I am not seeing that. What I am seeing instead is that a new persistent store is created for the iCloud account and no data.

My big question is what is the proper approach when it comes to the local fallback store? Where are my assumptions wrong?

This is one of the final pieces I need to ship my iOS 7 update. Any and all feedback would be greatly appreciated, and will be reflected in my github project so others can learn from my mistakes.

I have a duplicate of this question in the Apple Developer Forums. I will update this thread with any findings I get from there. I think this question is important and remains unresolved with the release of iOS 7. Fallback stores are a huge advancement in iCloud technology but the local storage part is still a little undefined.

Это было полезно?

Решение

I have worked around this for now since I cannot seem to get information as to how fallback stores are supposed to work in this scenario.

Basically what I do now is if the user is not logged in I create the document w/o iCloud sync options enabled.

Then at startup if iCloud is enabled I perform a scan for documents that need to be migrated and migrate them simply by opening them with the iCloud options enabled. Once opened I close the document as that is enough to get them migrated and scannable via a meta data scan.

Finally after the migration is done I kick off a new scan for documents.

It works but it is a bit of a hack.

Refer to the APManagedDocument commit: 421aaae

Другие советы

I finally got a reply tonight. I am not 100% certain he understood my question, but I am going to spend some time understanding his answer before I render a judgement.

Here is his response:

Thank you for your inquiry to Apple Worldwide Developer Technical Support. I am responding to let you know that I have received your request for technical assistance.

Core Data will not automatically move your UIManagedDocument to the cloud for you. You need to create a new document in the ubiquity container then migrate the persistent store from your local sandbox to your ubiquity container. The migration is necessary to create all the transaction logs so that other devices can create that document.

You could implement this class method to your UIManagedDocument subclass:

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

That method would essentially create a new document at "ubiquityContainerURL", and you migrate the store from "sourceDocumentURL" to "ubiquityContainerURL". You would use "migratePersistentStore" to perform the migration.

Here's an example:

// 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);
            }
        }];
    });
}

Isn't his response consistent with what you are seeing, I.e. No data because you don't put the transaction logs out in the cloud so that other devices can recreate the document from the logs. By migrating I guess you have the log files automatically generated in the appropriate iCloud directory. But then the 207 video seems to indicate that any use of .sync folder is no longer required.

One good working example from them is all I ask for...

Any idea how one would access these files from OSX?

Btw your stuff looks pretty good, I hope to try using it in a couple of days. Am real keen to see how these files would be accessed from OSX. As I understand it NSPersistentDocument is not iCloud aware.

EDIT: I just had a closer look at your APManagedDocumentManager and you don't seem to be including the iCloud path in the NSPersistentStoreUbiquitousContentURLKey value. Unless I missed something you are just using the subdirectory not the full iCloud path, also you're using a NSString rather than a URL (not sure whether this makes a difference).

EDIT: We should have a discussion over the phone perhaps? Anyway some more of my findings below: I just installed Mavericks and after having gone over the video twice am testing the following: Create new files using only the code below - no UIManagedDocument or anything. _storeURL is pointing to the local directory as suggested in the video. And I am not using any NSPersistentStoreUbiquitousContentURLKey because its not longer necessary. At the moment my filename has no UUID.

When I do this on any device then a CoreData directory is created outside the iCloud Documents directory. Inside the CoreData directory are subdirectories for each of the fileNames, and inside these are various zip files and things which are presumably baseline and log files. No sign of any DocumentMetaData.plist. So this all looks quite promising, except I can't figure out how one would "Discover" new files that appear. I am hoping that I just need to register for some notifications and am done... Going back to the video now because I can't recall the detail on what notifications are sent and how to react to them. At least the behaviour is consistent on both platforms. Strangely enough none of these documents show up in the Mac Apps File-Open dialog which lists all documents in the iCloud Documents directory, well not so strange I guess.

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

...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top