Singleton UIManagedDocument不会将事务日志写入云

时间:2014-01-23 12:21:55

标签: ios singleton icloud uimanageddocument

我有一个应用程序使用单个UIManagedDocument作为数据存储,我使用Justin Driscoll's blog中的方法通过单例访问。

这很好用,但是当我为persistentStoreOptions设置我的普遍性密钥并更改状态通知时,应用程序只在沙箱中运行,并且没有网络活动。

这是执行流程...... 1.应用程序打开到创建单例的登陆屏幕。 2.一旦UIManagedDocument打开,我检查云权限,如果一切都好,我们有一个无处不在的容器,我用cloud contentNameKey和contentUrlKey选项覆盖persistentStoreOptions。

这是代码......

//  DataStore.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

typedef void (^OnDocumentReady) (UIManagedDocument *appDataStore);

@interface DataStore : NSObject

@property (strong, nonatomic) UIManagedDocument *appDataStore;

+ (DataStore *)sharedDocumentHandler;
- (void)performWithDocument:(OnDocumentReady)onDocumentReady;

@end


//  DataStore.m
#import "DataStore.h"
#import "HashDefines.h"

@interface DataStore ()

@property (nonatomic)BOOL preparingDocument;
@property (nonatomic, strong) NSFileCoordinator *coordinator;

@property (strong, nonatomic) NSString *localDocumentsPath;
@property (strong, nonatomic) NSURL *localDocumentsURL;
@property (strong, nonatomic) NSURL *localFileURL;

@end;

@implementation DataStore

@synthesize appDataStore = _appDataStore;
@synthesize localDocumentsPath = _localDocumentsPath;
@synthesize localDocumentsURL = _localDocumentsURL;
@synthesize localFileURL = _localFileURL;

static DataStore *_sharedInstance;
@synthesize coordinator = _coordinator;

#define LOCAL_DATA_STORE_FILE_NAME @“QR App DataStore"

#pragma mark - synthesiser overiders

+ (DataStore *)sharedDocumentHandler
{
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        _sharedInstance = [[self alloc] init];
    });

    return _sharedInstance;
}

#pragma mark - Key Paths

#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
    if (!_localDocumentsPath) {
        _localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    }

    return _localDocumentsPath;
}

- (NSURL *) localDocumentsURL
{
    if (!_localDocumentsURL) {
        _localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
    }

    return _localDocumentsURL;
}

#pragma mark - File URLs

- (NSURL *) localFileURL: (NSString *) filename
{
    if (!filename) return nil;
    NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
    return fileURL;
}


#pragma mark - the juicy bits


#pragma mark - initialisers

- (id)init
{
    self = [super init];
    if (self) {

        NSLog(@"appDataStore does NOT exist ... building one now");

        [self initCoreDataInTheSandbox];

    }
    return self;
}

- (void)initCoreDataInTheSandbox
{
    NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
    self.appDataStore = [[UIManagedDocument alloc] initWithFileURL:localURL];

    // Set our document up for automatic migrations
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    self.appDataStore.persistentStoreOptions = options;

    NSLog(@"1. DS persistentStoreOptions: %@", self.appDataStore.persistentStoreOptions);
}

- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
    NSLog(@"1. DS Begin performWithDocument: %@", self.appDataStore);

    void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
        onDocumentReady(self.appDataStore);
        NSLog(@"2. DS into the block ... onDocumentReady:");
        self.preparingDocument = NO; // release in completion handler
    };

    if(!self.preparingDocument) {
        NSLog(@"3. DS preparing document: dataStore.appDataStore: %@", self.appDataStore);

        // "lock", so no one else enter here
        self.preparingDocument = YES;

        if(![[NSFileManager defaultManager] fileExistsAtPath:[self.appDataStore.fileURL path]]) {
            NSLog(@"4. DS creating document: dataStore.appDataStore: %@", self.appDataStore);
            [self.appDataStore saveToURL:self.appDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
            [self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
        } else if (self.appDataStore.documentState == UIDocumentStateClosed) {
            NSLog(@"5. DS dataStore.appDataStore.documentState: %d ... closed", self.appDataStore.documentState);
            [self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
        } else if (self.appDataStore.documentState == UIDocumentStateSavingError) {
            NSLog(@"6. DS dataStore.appDataStore.documentState: %d ... saving error", self.appDataStore.documentState);
            [self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
        } else if (self.appDataStore.documentState == UIDocumentStateNormal) {
            NSLog(@"7. DS dataStore.appDataStore.documentState: %d ... open", self.appDataStore.documentState);
            OnDocumentDidLoad(YES);
        }
    } else {
        // try until document is ready (opened or created by some other call)
        NSLog(@"8. DS preparing document - NOT ... trying again");
        [self performSelector:@selector(performWithDocument:) withObject:onDocumentReady afterDelay:0.5];
    }

    NSLog(@"9. DS Exiting performWithDocument: %@", self.appDataStore);
}

@end


// CloudConnector.h
#import "DataStore.h"

@interface CloudConnector : NSObject

- (void)hookUpCloudForDocument:(UIManagedDocument *)document;

(NSManagedObjectContext*)managedObjectContext;
- (NSString *) documentState: (int) state;

@end

#define ICLOUD_TOKEN_KEY @“com.apple.QR-App.UbiquityIdentityToken"
#define CLOUD_IS_UBIQUITOUS @"sweet ubiquity"
#define LOCAL_DATA_STORE_FILE_NAME @“QR App DataStore"
#define CLOUD_DATA_STORE_FILE_NAME @"com~app~QR-App~cloudstore"


//CloudConnector.m
#import "CloudConnector.h"
#import "HashDefines.h"

@interface CloudConnector ()

@property (strong, nonatomic) NSString *localDocumentsPath;
@property (strong, nonatomic) NSURL *localDocumentsURL;
@property (strong, nonatomic) NSURL *localFileURL;
@property (strong, nonatomic) NSURL *ubiquityDataFileURL;

@property (nonatomic)BOOL preparingDocument;
@property (nonatomic, strong) NSFileCoordinator *coordinator;

- (NSURL *) localFileURL: (NSString *) filename;
- (NSURL *) ubiquityDataFileURL: (NSString *) filename;
- (BOOL) isLocal: (NSString *) filename;
- (NSString *) documentState: (int) state;

@end;

@implementation CloudConnector

@synthesize coordinator = _coordinator;
@synthesize localDocumentsPath = _localDocumentsPath;
@synthesize localDocumentsURL = _localDocumentsURL;
@synthesize localFileURL = _localFileURL;
@synthesize  ubiquityDataFileURL = _ubiquityDataFileURL;

#pragma mark - synthesiser overiders




#pragma mark - the juicy bits

- (void)checkUbiquityStatus
{
    id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];

    if (currentiCloudToken) {
        NSData *newTokenData =
        [NSKeyedArchiver archivedDataWithRootObject: currentiCloudToken];
        [[NSUserDefaults standardUserDefaults]
         setObject: newTokenData
         forKey: ICLOUD_TOKEN_KEY];
    } else {
        [[NSUserDefaults standardUserDefaults]
         removeObjectForKey: ICLOUD_TOKEN_KEY];
    }
}

#pragma mark - Key Paths

#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
    if (!_localDocumentsPath) {
        _localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    }

    return _localDocumentsPath;
}

- (NSURL *) localDocumentsURL
{
    if (!_localDocumentsURL) {
        _localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
    }

    return _localDocumentsURL;
}

#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
    if (!filename) return nil;
    NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
    return fileURL;
}

- (NSURL *) ubiquityDataFileURL: (NSString *) filename forContainer: (NSString *) container
{
    if (!filename) return nil;
    NSURL *fileURL = [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:filename];
    return fileURL;
}


- (NSURL *) ubiquityDataFileURL: (NSString *) filename
{
    return [self ubiquityDataFileURL:filename forContainer:nil];
}


#pragma mark Ubiquity Data
- (NSURL *) ubiquityDataURLForContainer: (NSString *) container
{
    return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container];
}

- (NSArray *) contentsOfUbiquityDataFolderForContainer: (NSString *) container
{
    NSURL *targetURL = [self ubiquityDataURLForContainer:container];
    if (!targetURL) return nil;

    NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:targetURL.path error:nil];
    return array;
}

- (BOOL) isLocal: (NSString *) filename
{
    if (!filename) return NO;
    NSURL *targetURL = [self localFileURL:filename];
    if (!targetURL) return NO;
    return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path];
}


- (NSString *) documentState: (int) state
{
    if (!state) return @"Document state is normal";

    NSMutableString *string = [NSMutableString string];
    if ((state & UIDocumentStateClosed) != 0)
        [string appendString:@"Document is closed\n"];
    if ((state & UIDocumentStateInConflict) != 0)
        [string appendString:@"Document is in conflict"];
    if ((state & UIDocumentStateSavingError) != 0)
        [string appendString:@"Document is experiencing saving error"];
    if ((state & UIDocumentStateEditingDisabled) != 0)
        [string appendString:@"Document editing is disbled" ];

    return string;
}


#pragma mark - initialisers


- (void)hookUpCloudForDocument:(UIManagedDocument *)document
{
    // checking for ubiquity
    NSLog(@"checking for ubiquity");

    [self checkUbiquityStatus];

    // THE FOLLOWING CODE DOESN'T WORK
    if ([[NSUserDefaults standardUserDefaults] objectForKey:ICLOUD_TOKEN_KEY]) {
        // cloud permissions are good to go.
        NSLog(@"cloud permissions are good to go.");

        dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
            NSURL *url = [[NSFileManager defaultManager]
                          URLForUbiquityContainerIdentifier: nil];

            NSLog(@"bikky url: %@", url);

            if (url) { // != nil) {
                // Your app can write to the ubiquity container

                dispatch_async (dispatch_get_main_queue (), ^(void) {
                    // On the main thread, update UI and state as appropriate

                    [self setupCoreDataInTheCloudForDocument:document];
                });
            }
        });
    }
}

- (void) setupCoreDataInTheCloudForDocument:(UIManagedDocument *)document
{
    NSLog(@"1. cc.setupCoreDataInTheCloudForDocument: %@", document);

    NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
    NSURL *cloudURL = [self ubiquityDataFileURL:CLOUD_DATA_STORE_FILE_NAME];

    // Set the persistent store options to point to the cloud
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
//                             [document.fileURL lastPathComponent], NSPersistentStoreUbiquitousContentNameKey,
                             localURL, NSPersistentStoreUbiquitousContentNameKey,
                             cloudURL, NSPersistentStoreUbiquitousContentURLKey,
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                             nil];

    document.persistentStoreOptions = options;
    NSLog(@"2. cc.document.persistentStoreOptions: %@", document.persistentStoreOptions);

    // Register as presenter
    self.coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:document];
    [NSFileCoordinator addFilePresenter:document];


//    // THIS CODE IS INLINE FROM iOS 5 COOK BOOK
//    // I EXECUTE THIS IN (void)performWithDocument:(OnDocumentReady)onDocumentReady
//
//    // Check at the local sandbox
//    if ([self isLocal:LOCAL_DATA_STORE_FILE_NAME])
//    {
//        NSLog(@"Attempting to open existing file");
//        [document openWithCompletionHandler:^(BOOL success){
//            if (!success) {NSLog(@"Error opening file"); return;}
//            NSLog(@"File opened");
//        }];
//    }
//    else
//    {
//        NSLog(@"Creating file.");
//        // 1. save it out, 2. close it, 3. read it back in.
//        [document saveToURL:localURL
//                    forSaveOperation:UIDocumentSaveForCreating
//                   completionHandler:^(BOOL success){
//                       if (!success) { NSLog(@"7. Error creating file"); return; }
//                       NSLog(@"File created");
//                       [document closeWithCompletionHandler:^(BOOL success){
//                           NSLog(@"Closed new file: %@", success ? @"Success" : @"Failure");
//
//                           [document openWithCompletionHandler:^(BOOL success){
//                               if (!success) {NSLog(@"Error opening file for reading."); return;}
//                               NSLog(@"File opened for reading.");
//                           }];
//                       }];
//                   }];
//    }
//}

@end

我按照以下方式调用单身人士......

- (void)viewDidLoad
{
    [super viewDidLoad];

    if (!self.codeGeneratorDataStore) {

        NSLog(@"MVC.viewDidLoad: Grab local instance of document from data store singleton");

        [[DataStore sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
            self.codeGeneratorDataStore = document;
            // Do stuff with the document, set up a fetched results controller, whatever.
//            NSLog(@"IN:  _codeGeneratorDataStore.documentState : %u", _codeGeneratorDataStore.documentState);

            if (![self.codeGeneratorDataStore.persistentStoreOptions objectForKey:NSPersistentStoreUbiquitousContentNameKey]) {
                NSLog(@"MVC.viewDidLoad: We have a document, hooking up cloud now ... \n%@", document);
                [self.cloudConnector hookUpCloudForDocument:self.codeGeneratorDataStore];
            }

            [self setupFetchedResultsController];
        }];

        NSLog(@"GVC.viewDidLoad: Subscribing to notifications");
        [self subscribeToCloudNotifications];

    }

    [self checkDocumentStatus];    
}

然后我检查文件状态......

- (void)checkDocumentStatus {

    NSLog(@"MVC.checkDocumentStatus\n Document: %@\n persistentStoreOptions: %@", self.codeGeneratorDataStore, self.codeGeneratorDataStore.persistentStoreOptions);
    if (![[
           NSFileManager defaultManager] fileExistsAtPath:[self.codeGeneratorDataStore.fileURL path]]) {
        [self.codeGeneratorDataStore saveToURL:self.codeGeneratorDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            NSLog(@"1. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);
        }];
    } else if (self.codeGeneratorDataStore.documentState == UIDocumentStateClosed) {
        [self.codeGeneratorDataStore openWithCompletionHandler:^(BOOL succes) {
            NSLog(@"2. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);               
        }];
    } else if (self.codeGeneratorDataStore.documentState == UIDocumentStateNormal) {
        NSLog(@"3. MVC self.codeGeneratorDataStore.documentState: %@", [NSString stringWithFormat:@"%u", self.codeGeneratorDataStore.documentState]);
    }
}

设置并注册通知......

- (void)subscribeToCloudNotifications
{
    // subscribe to the NSPersistentStoreDidImportUbiquitousContentChangesNotification notification
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentStateChanged:)
                                                 name:UIDocumentStateChangedNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentContentsDidUpdate:)
                                                 name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
                                               object:nil];

    NSLog(@"MVC.subscribeToCloudNotifications: notification subscriptions are good to go");
}

- (void) documentContentsDidUpdate: (NSNotification *) notification
{
    NSLog(@"CMVC notification: documentContentsDidUpdate");

    NSDictionary *userInfo = notification.userInfo;
    [self.codeGeneratorDataStore.managedObjectContext performBlock:^{[self mergeiCloudChanges:userInfo forContext:self.codeGeneratorDataStore.managedObjectContext];}];
}

// When notified about a cloud update, start merging changes
- (void)documentStateChanged: (NSNotification *)notification
{
    NSLog(@"CMVC notification: documentStateChanged");
//    NSLog(@"Document state change: %@", [CloudHelper documentState:self.codeGeneratorDataStore.documentState]);

    UIDocumentState documentState = self.codeGeneratorDataStore.documentState;
    if (documentState & UIDocumentStateInConflict)
    {
        // This application uses a basic newest version wins conflict resolution strategy
        NSURL *documentURL = self.codeGeneratorDataStore.fileURL;
        NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:documentURL];
        for (NSFileVersion *fileVersion in conflictVersions) {
            fileVersion.resolved = YES;
        }
        [NSFileVersion removeOtherVersionsOfItemAtURL:documentURL error:nil];
    }
}

// Merge the iCloud changes into the managed context
- (void)mergeiCloudChanges:(NSDictionary*)userInfo forContext:(NSManagedObjectContext*)managedObjectContext
{
    @autoreleasepool
    {
        //        NSLog(@"Merging changes from cloud");

        NSMutableDictionary *localUserInfo = [NSMutableDictionary dictionary];

        NSSet *allInvalidations = [userInfo objectForKey:NSInvalidatedAllObjectsKey];
        NSString *materializeKeys[] = { NSDeletedObjectsKey, NSInsertedObjectsKey };

        if (nil == allInvalidations)
        {

            // (1) we always materialize deletions to ensure delete propagation happens correctly, especially with
            // more complex scenarios like merge conflicts and undo.  Without this, future echoes may
            // erroreously resurrect objects and cause dangling foreign keys
            // (2) we always materialize insertions to make new entries visible to the UI

            int c = (sizeof(materializeKeys) / sizeof(NSString *));
            for (int i = 0; i < c; i++)
            {
                NSSet *set = [userInfo objectForKey:materializeKeys[i]];
                if ([set count] > 0)
                {
                    NSMutableSet *objectSet = [NSMutableSet set];
                    for (NSManagedObjectID *moid in set)
                        [objectSet addObject:[managedObjectContext objectWithID:moid]];

                    [localUserInfo setObject:objectSet forKey:materializeKeys[i]];
                }
            }

            // (3) we do not materialize updates to objects we are not currently using
            // (4) we do not materialize refreshes to objects we are not currently using
            // (5) we do not materialize invalidations to objects we are not currently using

            NSString *noMaterializeKeys[] = { NSUpdatedObjectsKey, NSRefreshedObjectsKey, NSInvalidatedObjectsKey };
            c = (sizeof(noMaterializeKeys) / sizeof(NSString*));
            for (int i = 0; i < 2; i++)
            {
                NSSet *set = [userInfo objectForKey:noMaterializeKeys[i]];
                if ([set count] > 0)
                {
                    NSMutableSet *objectSet = [NSMutableSet set];
                    for (NSManagedObjectID *moid in set)
                    {
                        NSManagedObject *realObj = [managedObjectContext objectRegisteredForID:moid];
                        if (realObj)
                            [objectSet addObject:realObj];
                    }

                    [localUserInfo setObject:objectSet forKey:noMaterializeKeys[i]];
                }
            }

            NSNotification *fakeSave = [NSNotification notificationWithName:NSManagedObjectContextDidSaveNotification object:self userInfo:localUserInfo];
            [managedObjectContext mergeChangesFromContextDidSaveNotification:fakeSave];

        }
        else
            [localUserInfo setObject:allInvalidations forKey:NSInvalidatedAllObjectsKey];

        [managedObjectContext processPendingChanges];

        //        [self performSelectorOnMainThread:@selector(performFetch) withObject:nil waitUntilDone:NO];
    }
}

我们很高兴 - 应用程序运行但没有网络活动。为什么不上传交易日志?

这里非常感谢任何帮助。它现在已经开了一个星期了,我现在已经结束了。

1 个答案:

答案 0 :(得分:0)

我之前在电子邮件中回答了这个问题,但我也在这里提出我的答案,以便完整。

这是仅限iOS 7的应用,还是需要支持iOS 5或6?

我不建议将iCloud和Core Data混合在任何仍然需要支持iOS 5和6的内容中。但是,对iOS 7的新支持看起来非常可靠。我没有用它部署一个数百万的用户应用程序 - 所以我不会说它经过了战斗测试,但它似乎更加强大。在我的初步测试中,它处理了我扔的所有内容,没有任何问题。

此外,您是否关注iOS 7的最新示例代码?看起来它正在遵循一些较旧的模式。这可能会导致很多问题,因为iOS 7中系统运行的方式发生了很大变化。

例如,在iOS 7中,我们通常只需要设置NSPersistentStoreUbiquitousContentNameKey。除非我们尝试支持旧版核心数据堆栈,否则我们无需设置内容URL密钥。

此外,iOS 7下核心数据堆栈的设置存在巨大差异。当我们首次设置iCloud持久存储时,iOS 7将自动为我们创建本地副本(后备存储)。我们可以使用商店 - 但更改只会存储在本地。当iCloud版本准备就绪后,它将发送NSPersistentStoreCoordinatorStoresWillChangeNotification。我们应该保存所有更改,然后重置我们的托管对象上下文。

同样,您的代码似乎是手动设置自己的后备存储 - 这可能是问题的一部分。

如果您正在使用旧示例,我实际上建议您抛弃该代码并重新开始。观看WWDC 2013上的“Core Data和iCloud中的新功能”视频。它对新技术和当前的最佳实践做了很好的了解。

我的猜测是你正在获得后备商店,但它永远不会切换到iCloud商店。运行应用程序时查看控制台日志。你应该看到如下内容:

-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](754): CoreData: p Ubiquity:
 mobile~C9C8554A-BD44-43C3-AC54-603046EF0162:com~freelancemad p science~HealthBeat

Using local storage: 1

此处,“使用本地存储:1”表示您正在使用后备存储。在一两分钟(或有时更长)中,您将看到类似的消息“使用本地存储:0”。这意味着您现在正在使用iCloud商店。

此外,运行应用程序时,请务必在Xcode中打开Debug Navigator。它应该有一个iCloud的分析器,它会在数据上传到云或从云下载时显示。

我希望有所帮助,

富 -