硬核 CoreData + iCloud 场景

3
我有一个问题很难解决。我的应用程序使用CoreData存储对象,我想实现设备之间的iCloud同步......而我的应用程序需要一个初始填充的数据库。
当我第一次启动应用程序时,它将在云上填充我的数据库,并将某些db字段标记为“databaseInstalled”。这些字段也会在云上同步。
现在,当另一个设备首次启动应用程序时,我希望检索字段“databaseInstalled”,以检查是否注入数据,但是结果错误......
如果databaseInstalled为false,则注入数据;如果databaseInstalled为true,则等待iCloud同步。
问题是,由于我不想阻塞正在等待从iCloud下载数据的应用程序,我异步地检索了persistentStoreCoordinator,所以我怎么能预先知道是否需要填充数据库,或者它已经被其他设备填充好了,我只需从iCloud下载填充的内容?
- (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 = @"this is a secret!";

        // ** Note: if you adapt this code for your own use, you should change this variable:        
        NSString *dataFileName = @"you do not have to know.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);

            // da rimuovere

            //[fileManager removeItemAtURL:iCloudLogsPath error:nil];
            #warning to remove

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

            //[fileManager removeItemAtPath:iCloudData error:nil];
#warning to remove

            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");
            NSLog(@"Local store: %@", localStore.path);
            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(), ^{
            NSLog(@"iCloud routine completed.");

            Setup *install = [[Setup alloc] init];

            if([install shouldMigrate]) {
                HUD = [[MBProgressHUD alloc] initWithView:self.window.rootViewController.view];
                HUD.delegate = self;
                HUD.labelText = NSLocalizedString(@"Sincronizzazione del database", nil);
                [self.window.rootViewController.view addSubview:HUD];
                [HUD showWhileExecuting:@selector(installDatabase) onTarget:install withObject:nil animated:YES];
            }
            else {
                [[NSNotificationCenter defaultCenter] postNotificationName:@"setupCompleted" object:self];
            }

            //[[NSNotificationCenter defaultCenter] postNotificationName:@"icloudCompleted" object:self userInfo:nil];
            [[NSNotificationCenter defaultCenter] postNotificationName:@"setupCompleted" object:self];
        });
    });

    return __persistentStoreCoordinator;
}
3个回答

0

在与iCloud同步完成之前,您无法确定是否会有可用的数据。这意味着您有两个选择:

  • 让用户等待同步完成。

  • 使用默认数据库启动,并在可能时合并来自iCloud的更改。

使用iCloud时,您需要一些解决本地数据和云数据冲突的策略,因为您必须处理用户可能同时在多个设备上更改数据的事实。一旦您完成了这个步骤,似乎很明显以上第二个选项是更好的:用户可以立即开始使用您的应用程序,并在可用时合并来自云端的数据。


预填充数据库的数据量很大,从1k到4k个对象(记录)。第二个选项是目前的工作方式,但我遇到了另一个问题...应用程序在第一次注入对象以填充数据库的同时,正在与iCloud合并数据...而且我在我的NSFetchedResultsController中遇到了异常(集合被静音)...仍然有困扰... - Progeny
我差点忘了...选项2的主要问题是我的数据库被复制了,因为两个设备在iCloud上同步本地数据库...从iCloud合并本地数据库不会删除现有记录,而是会重复它们! - Progeny
你的回答涉及基于文档的应用程序,而不是CoreData + iCloud。解决预填充数据库方案并没有简单的方法。至少我还没有找到一个。主要问题是,一旦你使用数据预填充DB,就会出现重复项。对象ID将是不同的。 - Kostiantyn Sokolinskyi

0

我曾经遇到过完全相同的问题。

请查看我的问题和我的答案iCloud + CoreData - 如何避免预填充数据重复?

实际上,它并不完全正常工作。如果你敢尝试,我可以向你解释如何使它百分之百地正确运行(虽然我还没有尝试过)。

考虑到您有大量要预填充的数据,我的解决方案可能不适合您。


很抱歉,但我真的不喜欢你的解决方案。这些天我想了很多,可能找到了更好的解决方法。我们可以使用预填充的CoreData数据库(使用像CoreData Editor/Manager这样的编辑器),并将整个数据库复制到.nosyc目录中,因为我们需要所有记录都具有相同的UUID。 .nosync目录的内容在云中不被同步,但是对这些记录进行新操作的事务日志将被同步。我认为这是最好的方法。最终问题是:如果Apple改变了CoreData预先(生成和复制)填充的数据库怎么办? - Progeny
我的解决方案并不完美。这只是我在有限时间内设法解决的问题。你的解决方案是否完全适用于你的任务?我的预填数据是“类别”,我需要在不同设备上添加“项目”以属于同一类别。根据你的经验,这会发生吗?我没有完全理解你的最后一个问题。你是否担心苹果会更改核心数据bd格式? - Kostiantyn Sokolinskyi
我是3个月前在开发这个案例,所以现在不记得每一个细节了。但我想我也尝试过你的方法,但我没有得到数据一致性 - 从iCloud的角度来看,不同设备上的“类别”确实是不同的实体。如果您能报告您的结果,我将不胜感激。 - Kostiantyn Sokolinskyi
是的,我担心苹果可能会更改核心数据数据库文件。我在网上读到过,苹果已经从iOS 4更改为iOS 5存储CoreData中整数的方式。 - Progeny
嗯,其实我不知道。但是苹果可以很容易地改变它,确实如此。那么你的方法完全奏效了吗? - Kostiantyn Sokolinskyi

0

无法确定数据存储是否首次打开。至少在iCloud Core Data存储中不行。想一想,iCloud也应该可以离线工作 - 也就是说,当用户断开与互联网的连接时,所有更改都应该被缓冲,然后在恢复连接时上传。没有办法检查数据存储是否初始化,而不会让用户等待几分钟(甚至无限期地等待,如果设备处于离线状态),以询问iCloud的数据存储副本。

为了解决这个问题,您需要遵循以下四个简单的指南:

  • 有一种方法来去重预填充记录。
  • 有一种方法来识别预填充记录并将其与用户输入的记录区分开。
  • 每次从iCloud收到新的交易记录时运行去重复过程。
  • 仅对每个设备/帐户组合播种数据记录一次。

您可以在此处阅读更多详细信息:http://cutecoder.org/programming/seeding-icloud-core-data/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接