使用iCloud进行应用数据库备份,但不同步

时间:2019-08-26 03:08:54

标签: ios sqlite backup icloud fmdb

我正在寻求有关以有限方式使用iCloud的建议。该文档专注于在多个设备之间进行同步,这是我不需要的。当我说iCloud时,如果重要的话,我也包括iCloud Drive。

我的iPhone应用程序将其数据和状态存储在一个SQLite数据库文件中,该文件我想保存到iCloud(或iCloud Drive)以用于备份,而不是用于在多个设备之间同步更改。在启动时,SQLite打开数据库文件并同步使用它(我正在使用FMDB)。数据库文件应保留在设备上,因此,如果iCloud关闭,SQLite将不会知道或不在意。 iCloud将具有卷影副本。

iCloud副本不一定总是最新的-我可以以编程方式启动对iCloud的更新,并在必要时进行还原。如果本地数据库文件已损坏,或者用户删除了该应用程序,我将从iCloud还原该文件。该文件通常会小于1 MB,但可能会很大(100 MB),但大部分都是不变的。

由于共享整个SQLite文件具有数据完整性风险,因此我不进行同步,因此我无法转换为Core Data(这是使用FMDB的大型应用程序)。

我知道iOS每天都会备份到iCloud,如果我可以仅从iCloud(而不是整个设备)恢复我的应用程序,那就足够了。

我需要有关使用iCloud进行卷影备份和还原但不同步的建议。

更新:我在这个问题上取得了进展;我可以将真正的SQLite文件保存到iCloud或从iCloud恢复。我用以下两个替代方法将UIDocument划分为子类:

@implementation MyDocument

// Override this method to return the document data to be saved.
- (id)contentsForType:(NSString *)typeName error:(NSError * _Nullable *)outError
{
    NSString *databasePath = ... // path to my SQLite file in Documents;
    NSData *dbData = [NSData dataWithContentsOfFile: databasePath];
    return dbData; // the entire database file
}

// Override this method to load the document data into the app’s data model.
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError * _Nullable *)outError
{
    NSString *databasePath = ... // path to my SQLite file in Documents;
    NSData *dbData = contents;
    BOOL ret = [dbData writeToFile: databasePath atomically:YES];
    return YES;
}

@end

我的SQLite数据库文件位于文档中,并且保留在其中,但是我将卷影副本因此写入了iCloud:

- (void)sendToICloud
{
    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];

    NSString *iCloudFileName = @"..."; // a file name I choose
    NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"]
                                URLByAppendingPathComponent:iCloudFileName];

    MyDocument *myDoc = [[MyDocument alloc] initWithFileURL:ubiquitousPackage];

    [myDoc saveToURL:[myDoc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        if ( success ) {
            [myDoc closeWithCompletionHandler:^(BOOL success) {
            }];
        } else {
            NSLog(@"iCloud write Syncing FAILED with iCloud");
        }
    }];
}

现在我的问题是:

  1. 我已经用Xcode创建了一个自定义容器,该容器出现在我的权利文件和开发门户中,作为App ID。我可以从两个设备(iPhone和iPad)中的任何一个将文档保存到iCloud。但是,我的两个设备看不到彼此的文件。关于此的随机解决方案有很多老话题,但是没有任何一个可以解决问题。

  2. iCloud文档说文件是增量更新的,但是我不知道这种情况是否适用于我的情况。如果我为MyDocument(上面是我的iCloudFileName)使用新的文件名,我将意识到一个完整的新文件将被发送到iCloud。但是,如果我重复使用以前的文件名并每次发送更新的NSData(就像我在loadFromContents:ofType:error:中所做的那样),iOS是否只会发送自上次保存文件以来发生更改的部分?

1 个答案:

答案 0 :(得分:0)

现在的另一主题行是:使用iCloud在多个设备上进行文件备份。

我已经解决了看到多个设备发送的iCloud数据的主要问题。根据WWDC 2015“基于建筑文档的应用程序”,要查看所有文件(甚至来自其他设备的文件),请使用NSMetadataQuery,而不是NSFileManager。此外,视频演示还包含有关使用NSURL的警告,因为文档可能会移动(使用书签),但这不会影响我的用例。

我仍然不知道发送部分更改的NSData是否会导致增量更新。

我的UIDocument覆盖和我的sendToICloud方法保持不变。新功能是我的iCloud搜索。

如果您不写入默认的iCloud Documents目录,那么如果该目录不存在,则写入将失败。 因此,每次创建它都比较容易,

- (void)writeToiCloud
{
    // ...
#define UBIQUITY_ID         @"iCloud.TEAMID.bundleID.appName"
    NSFileManager *defaultManager = [NSFileManager defaultManager];
    NSURL *ubiq = [defaultManager URLForUbiquityContainerIdentifier:UBIQUITY_ID];
    NSURL *ubiquitousFolder = [ubiq URLByAppendingPathComponent:@"MyFolder"];
    [defaultManager createDirectoryAtURL:ubiquitousFolder
             withIntermediateDirectories:YES
                              attributes:nil error:nil];
    // ...
    // ... forSaveOperation:UIDocumentSaveForCreating ...
}

- (void)updateFileList
{
    // Use an iVar so query stays in scope
    // Instance variables maintain a strong reference to the objects by default
    query = [[NSMetadataQuery alloc] init];
    [query setSearchScopes:@[ // use both for testing, then only DocumentsScope
                             NSMetadataQueryUbiquitousDataScope, // not in Documents
                             NSMetadataQueryUbiquitousDocumentsScope, // in Documents
                             ]
     ];
    // add a predicate if desired to restrict the search.
    // No predicate finds everything, but Apple says:
    // a query can’t be started if ... no predicate has been specified.

    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"%K like '*'", NSMetadataItemFSNameKey];
    // NSPredicate *predicate = [NSPredicate predicateWithFormat: @"%K CONTAINS %@", NSMetadataItemPathKey, @"/MyFolder/"];
    // NSPredicate *predicate = [NSPredicate predicateWithFormat: @"%K CONTAINS %@", NSMetadataItemPathKey, @"/Documents/"];
    [query setPredicate:predicate];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(queryDidFinish:)
                                                 name:NSMetadataQueryDidFinishGatheringNotification
                                               object:query];
    [query startQuery];

}

- (void)queryDidFinish:(NSNotification *)notification
{
    // iVar: NSMetadataQuery *query
    for ( NSMetadataItem *item in [query results] ) {
        NSURL *itemURL = [item valueForAttribute:NSMetadataItemURLKey];
        NSLog(@"fileName: %@",itemURL.lastPathComponent);

        // can't use getResourceValue:forKey:error
        //   that applies to URLs that represent file system resources.

        if ( [itemURL.absoluteString hasSuffix:@"/"] ) {
            continue; // skip directories
        }

        // process the itemURL here ...
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [self.tableView reloadData];
    });

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSMetadataQueryDidFinishGatheringNotification
                                                  object:query];
}