【发布时间】:2011-01-21 07:19:49
【问题描述】:
我想在新版本的应用中包含更新的 SQLite 数据库。我的应用程序在启动时将数据库文件复制到 Documents 目录中。进行这种版本控制的最佳方法是什么(除了使用 Core Data)?
我假设 SQLite 文件中的特殊“版本”表或带有版本号的小文本文件是可行的方法,但我想征求其他人的意见。
【问题讨论】:
标签: iphone cocoa-touch sqlite versioning
我想在新版本的应用中包含更新的 SQLite 数据库。我的应用程序在启动时将数据库文件复制到 Documents 目录中。进行这种版本控制的最佳方法是什么(除了使用 Core Data)?
我假设 SQLite 文件中的特殊“版本”表或带有版本号的小文本文件是可行的方法,但我想征求其他人的意见。
【问题讨论】:
标签: iphone cocoa-touch sqlite versioning
我这样做的方法是查看文件戳。如果 .app 包中 SQLite DB 文件的修改日期比本地文档目录中的更新日期,那么我将 .app 包中的那个复制过来...这是我使用的代码。
sqlite3 *dbh; // Underlying database handle
NSString *name; // Database name (this is the basename part, without the extension)
NSString *pathBundle; // Path to SQLite DB in the .app folder
NSString *pathLocal; // Path to SQLite DB in the documents folder on the device
- (BOOL)automaticallyCopyDatabase { // Automatically copy DB from .app bundle to device document folder if needed
ES_CHECK(!dbh, NO, @"Can't autoCopy an already open DB")
ES_CHECK(name!=nil, NO, @"No DB name specified")
ES_CHECK(pathBundle!=nil, NO, @"No .app bundle path found, this is a cache DB")
ES_CHECK(pathLocal!=nil, NO, @"No local document path found, this is a read-only DB")
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *localAttr = [fileManager fileAttributesAtPath:pathLocal traverseLink:YES];
BOOL needsCopy = NO;
if (localAttr == nil) {
needsCopy = YES;
} else {
NSDate *localDate;
NSDate *appDBDate;
if (localDate = [localAttr objectForKey:NSFileModificationDate]) {
ES_CHECK([fileManager fileExistsAtPath:pathBundle], NO, @"Internal error: file '%@' does not exist in .app bundle", pathBundle)
NSDictionary *appDBAttr = [fileManager fileAttributesAtPath:pathBundle traverseLink:YES];
ES_CHECK(appDBAttr!=nil, NO, @"Internal error: can't get attributes for '%@'", pathBundle)
appDBDate = [appDBAttr objectForKey:NSFileModificationDate];
ES_CHECK(appDBDate!=nil, NO, @"Internal error: can't get last modification date for '%@'", pathBundle)
needsCopy = [appDBDate compare:localDate] == NSOrderedDescending;
} else {
needsCopy = YES;
}
}
if (needsCopy) {
NSError *error;
BOOL success;
if (localAttr != nil) {
success = [fileManager removeItemAtPath:pathLocal error:&error];
ES_CHECK(success, NO, @"Can't delete file '%@'" ,pathLocal)
}
success = [fileManager copyItemAtPath:pathBundle toPath:pathLocal error:&error];
ES_CHECK(success, NO, @"Can't copy database '%@' to '%@': %@", pathBundle, pathLocal, [error localizedDescription])
ES_TRACE(@"Copied DB '%@' to '%@'", pathBundle, pathLocal)
return success;
}
return YES;
}
ES_CHECK 只是宏,在发布模式下扩展为空,在调试模式下引发异常......它们看起来像这样:
#if ES_DEBUG
#define ES_ASSERT(cond) assert(cond);
#define ES_LOG(msg...) NSLog(msg);
#define ES_TRACE(msg...) NSLog(msg);
#else
#define ES_ASSERT(cond)
#define ES_LOG(msg...)
#define ES_TRACE(msg...)
#endif
#define ES_CHECK(cond, ret, msg...) if (!(cond)) { ES_LOG(msg) ES_ASSERT(cond) return (ret); } // Check with specified return value (when condition fails)
【讨论】:
ES_CHECK 宏很有趣...在调试模式下,您会因断言而失败,但在发布模式下,您会返回错误代码,因此实际代码在调试和发布模式下的行为截然不同.所以只能通过开启所有调试来测试调用代码是否可以处理返回的错误码?
不需要专门的桌子。 SQLite 对此有一个编译指示,称为user_version。 SQLite 不会将此值用于任何事情,它完全留给应用程序。
阅读版本:
#pragma user_version;
设置版本:
#pragma user_version=1;
【讨论】:
在尝试了一些技术之后,我最终在我的数据库中添加了一个表以获取元信息并放入一个时间戳列。每次更新我的应用程序时,我都会根据复制数据库的时间戳(即在 Documents 目录中)检查捆绑数据库的时间戳。这意味着我必须记住在更新时更改时间戳值,但这很简单并且有效。
使用文件时间戳不起作用,因为用户有可能在 App Review 时间窗口中下载应用,并最终得到一个具有比捆绑包中的时间戳更新的时间戳的复制数据库。
【讨论】: