iOS 核心数据存储在 iCloud 中以与其他设备同步

Posted

技术标签:

【中文标题】iOS 核心数据存储在 iCloud 中以与其他设备同步【英文标题】:iOS core data store in iCloud to sync with other devices 【发布时间】:2012-07-12 15:05:58 【问题描述】:

我一直在考虑让 Core Data 与 iCloud 一起使用,以便可以从多个设备访问人们的数据。 我一直在尽可能地学习本教程 (http://timroadley.com/) 并使用我的应用程序,但我遇到了一些问题。

问题 1:当应用关闭(不仅仅是暂停)并重新打开时,表格中没有显示任何内容,因此之前的数据似乎没有被加载

问题 2:我不知道是 iCloud 还是应用程序,但同步更改需要时间。我在一台设备上进行了更改,需要 2-3 分钟才能在另一台设备上显示

谁能帮我解决这两个问题

此代码在我的 AppDelegate 中

- (void)saveContext

    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) 
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) 
             // Replace this implementation with code to handle the error appropriately.
             // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
         
    


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
    
    if((__persistentStoreCoordinator != nil)) 
        return __persistentStoreCoordinator;
    

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
    NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator;

    // Set up iCloud in another thread:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^

        // ** Note: if you adapt this code for your own use, you MUST change this variable:
        NSString *iCloudEnabledAppID = @"MF4HVVX5DS.Desbrina.Medicine-Tracker";

        // ** Note: if you adapt this code for your own use, you should change this variable:
        NSString *dataFileName = @"Medicine-Tracker.sqlite";

        // ** Note: For basic usage you shouldn't need to change anything else

        NSString *iCloudDataDirectoryName = @"Data.nosync";
        NSString *iCloudLogsDirectoryName = @"Logs";
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName];
        NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];

        if (iCloud) 

            NSLog(@"iCloud is working");

            NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];

            NSLog(@"iCloudEnabledAppID = %@",iCloudEnabledAppID);
            NSLog(@"dataFileName = %@", dataFileName);
            NSLog(@"iCloudDataDirectoryName = %@", iCloudDataDirectoryName);
            NSLog(@"iCloudLogsDirectoryName = %@", iCloudLogsDirectoryName);
            NSLog(@"iCloud = %@", iCloud);
            NSLog(@"iCloudLogsPath = %@", iCloudLogsPath);

            if([fileManager fileExistsAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]] == NO) 
                NSError *fileSystemError;
                [fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]
                       withIntermediateDirectories:YES
                                        attributes:nil
                                             error:&fileSystemError];
                if(fileSystemError != nil) 
                    NSLog(@"Error creating database directory %@", fileSystemError);
                
            

            NSString *iCloudData = [[[iCloud path]
                                     stringByAppendingPathComponent:iCloudDataDirectoryName]
                                    stringByAppendingPathComponent:dataFileName];

            NSLog(@"iCloudData = %@", iCloudData);

            NSMutableDictionary *options = [NSMutableDictionary dictionary];
            [options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
            [options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
            [options setObject:iCloudEnabledAppID            forKey:NSPersistentStoreUbiquitousContentNameKey];
            [options setObject:iCloudLogsPath                forKey:NSPersistentStoreUbiquitousContentURLKey];

            [psc lock];

            [psc addPersistentStoreWithType:NSSQLiteStoreType
                              configuration:nil
                                        URL:[NSURL fileURLWithPath:iCloudData]
                                    options:options
                                      error:nil];

            [psc unlock];
        
        else 
            NSLog(@"iCloud is NOT working - using a local store");
            NSMutableDictionary *options = [NSMutableDictionary dictionary];
            [options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
            [options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];

            [psc lock];

            [psc addPersistentStoreWithType:NSSQLiteStoreType
                              configuration:nil
                                        URL:localStore
                                    options:options
                                      error:nil];
            [psc unlock];

        

        dispatch_async(dispatch_get_main_queue(), ^
            [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged" object:self userInfo:nil];
        );
    );

    return __persistentStoreCoordinator;


- (NSManagedObjectContext *)managedObjectContext 

    if (__managedObjectContext != nil) 
        return __managedObjectContext;
    

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil) 
        NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

        [moc performBlockAndWait:^
            [moc setPersistentStoreCoordinator: coordinator];
            [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
        ];
        __managedObjectContext = moc;
    

    return __managedObjectContext;


- (void)mergeChangesFrom_iCloud:(NSNotification *)notification 

    NSLog(@"Merging in changes from iCloud...");

    NSManagedObjectContext* moc = [self managedObjectContext];

    [moc performBlock:^

        [moc mergeChangesFromContextDidSaveNotification:notification];

        NSNotification* refreshNotification = [NSNotification notificationWithName:@"SomethingChanged"
                                                                            object:self
                                                                          userInfo:[notification userInfo]];

        [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
    ];

这是在主视图控制器中

- (void)reloadFetchedResults:(NSNotification*)note 
    NSLog(@"Underlying data changed ... refreshing!");
    [self fetchedResultsController];
    NSLog(@"%@", note.object);


- (void)viewDidLoad

    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(reloadFetchedResults:)
                                                 name:@"SomethingChanged"
                                               object:[[UIApplication sharedApplication] delegate]];


- (void)viewDidUnload

    [super viewDidUnload];
    // Release any retained subviews of the main view.

    [[NSNotificationCenter defaultCenter] removeObserver:self];

【问题讨论】:

【参考方案1】:

问题 1:当应用关闭(不仅仅是暂停)并重新打开时,表格中没有显示任何内容,因此之前的数据似乎没有被加载

检查是否将所有数据保存到托管对象上下文中。这不应该是核心数据问题

问题 2:我不知道是 iCloud 还是应用程序,但同步更改需要时间。我在一台设备上进行了更改,需要 2-3 分钟才能在另一台设备上显示

2-3 分钟真的很好。有时可能需要数小时才能使数据处于完全同步状态。至少我半年前经历过。

请记住,iCloud 不适合在多台设备上同时使用。它宁可提供最终一致性之类的东西。

【讨论】:

在进行更改或退出应用程序时,一切都会调用保存上下文。我已将保存上下文添加到第一篇文章【参考方案2】:

在您的示例中: NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil]; 用你的 NSString *iCloudEnabledAppID = @"MF4HVVX5DS.Desbrina.Medicine-Tracker"; 作为 URLForUbiquityContainerIdentifier

应该是这样的:

NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier: iCloudEnabledAppID];

【讨论】:

以上是关于iOS 核心数据存储在 iCloud 中以与其他设备同步的主要内容,如果未能解决你的问题,请参考以下文章

iCloud 和现有的“鞋盒”应用核心数据存储

IOS - iCloud中的核心数据备份

如何将现有核心数据 iOS 7 应用程序的数据迁移到 iCloud?

使用 iCloud 进行核心数据同步

iCloud核心数据不传输图像

仅为 OS X 10.8.x 和 iOS 6.x 在 iCloud 中启用核心数据同步