我正在寻求有关以有限方式使用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");
}
}];
}
现在我的问题是:
我已经用Xcode创建了一个自定义容器,该容器出现在我的权利文件和开发门户中,作为App ID。我可以从两个设备(iPhone和iPad)中的任何一个将文档保存到iCloud。但是,我的两个设备看不到彼此的文件。关于此的随机解决方案有很多老话题,但是没有任何一个可以解决问题。
iCloud文档说文件是增量更新的,但是我不知道这种情况是否适用于我的情况。如果我为MyDocument
(上面是我的iCloudFileName
)使用新的文件名,我将意识到一个完整的新文件将被发送到iCloud。但是,如果我重复使用以前的文件名并每次发送更新的NSData(就像我在loadFromContents:ofType:error:
中所做的那样),iOS是否只会发送自上次保存文件以来发生更改的部分?
答案 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];
}