更新应用程序时移动ios sqlite数据库
我正在我的应用程序中使用核心数据,我想在下一次更新中启动iTunes文件共享,但需要先移动我的应用程序sqlite数据库。我尝试了下面的代码,但应用程序在启动时崩溃。我想我可以在NSPersistentStoreCoordinator中用一个新的存储url替换旧的存储url,其中“newDatabasePath”匹配新的存储url 然后将sqlite文件替换为更新应用程序时移动ios sqlite数据库,ios,sqlite,core-data,nsfilemanager,Ios,Sqlite,Core Data,Nsfilemanager,我正在我的应用程序中使用核心数据,我想在下一次更新中启动iTunes文件共享,但需要先移动我的应用程序sqlite数据库。我尝试了下面的代码,但应用程序在启动时崩溃。我想我可以在NSPersistentStoreCoordinator中用一个新的存储url替换旧的存储url,其中“newDatabasePath”匹配新的存储url 然后将sqlite文件替换为 - (BOOL)application:(UIApplication *)application didFinishLaunchingW
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//check app has run before and has a current db
if ([saveData boolForKey:@"hasRunBefore"]) {
NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];
NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];
NSError *error;
if ([fileMgr fileExistsAtPath:newDatabasePath]) {
[fileMgr removeItemAtPath:newDatabasePath error:&error];
[fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath error:&error];
}
[fileMgr removeItemAtPath:oldDatabasePath error:&error];
BOOL databaseMoved = YES;
[saveData setBool:databaseMoved forKey:@"databaseMoved"];
}
}
谢谢
在阅读了这里的一个类似问题后,我尝试了一种新的方法来解决这个问题,并取得了一些成功。我试着像这样重置coredata堆栈
- (void)resetDatabase {
NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
// release context and model
__managedObjectModel = nil;
__managedObjectContext = nil;
//[__persistentStoreCoordinator removePersistentStore:store error:nil];
__persistentStoreCoordinator = nil;
NSFileManager* fileMgr = [NSFileManager defaultManager];
[fileMgr removeItemAtPath:storeURL.path error:&error];
if (error) {
NSLog(@"filemanager error %@", error);
}
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//create Private Documents Folder
NSArray *libPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libDir = [libPaths objectAtIndex:0];
NSString *privateDocsPath = [libDir stringByAppendingPathComponent:@"Private Documents"];
if (![fileMgr fileExistsAtPath:privateDocsPath])
[fileMgr createDirectoryAtPath:privateDocsPath withIntermediateDirectories:YES attributes:nil error:nil];
NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];
NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];
BOOL removedItemAtPath = NO;
BOOL copiedItemToPath = NO;
if ([fileMgr fileExistsAtPath:newDatabasePath]) {
DLog(@"DATABASE EXISTS AT PATH");
removedItemAtPath = [fileMgr removeItemAtPath:newDatabasePath error:&error];
if (removedItemAtPath) {
DLog(@"ITEM REMOVED");
}
else
DLog(@"FAILED TO REMOVE ITEM: %@", error);
copiedItemToPath = [fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath error:&error];
if (copiedItemToPath) {
DLog(@"ITEM COPIED");
}
else
DLog(@"FAILED TO COPY ITEM: %@", error);
}
// recreate the stack
__managedObjectContext = [self managedObjectContext];
}
使用这种方法,当我第一次启动coredata堆栈时,应用程序在尝试从该堆栈加载数据时仍会引发异常,但随后会重新加载一切正常,并使用“Library/Private Documents”中新位置的sqlite文件,因为您说错误消息是:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array' *** First throw call stack:
手指非常有力地指向这条线:
NSString *oldDatabasePath = [[dirPaths objectAtIndex:0] stringByAppendingPathComponent:@"AppData.sqlite"];
错误消息也会告诉您原因——您正在空数组上使用索引0。因此,
dirpath
为空。我不知道为什么,因为您没有将代码发布到指定值的位置,但这就是导致此崩溃的原因。答案与通常的答案一样简单
我只是移动了将应用程序复制到原始数据库中所需的代码
-(NSPersistentStoreCoordinator*)在添加存储之前,而不是尝试从
-(BOOL)应用程序:(UIApplication*)应用程序使用选项完成启动:(NSDictionary*)启动选项
又浪费了一天
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
//test if app already run before, copy database over and remove old one, finally switch store url to new directory
if ([saveData boolForKey:@"hasRunBefore"]) {
if ([saveData boolForKey:@"databaseUpdated"] != YES) {
DLog(@"MOVING DATABASE");
NSFileManager* fileMgr = [[NSFileManager alloc] init];
fileMgr.delegate = self;
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
//create Private Documents Folder
NSArray *libPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libDir = [libPaths objectAtIndex:0];
NSString *privateDocsPath = [libDir stringByAppendingPathComponent:@"Private Documents"];
if (![fileMgr fileExistsAtPath:privateDocsPath])
[fileMgr createDirectoryAtPath:privateDocsPath withIntermediateDirectories:YES attributes:nil error:nil];
NSString *oldDatabasePath = [docsDir stringByAppendingPathComponent:@"AppData.sqlite"];
NSString *newDatabasePath = [privateDocsPath stringByAppendingPathComponent:@"AppData.sqlite"];
NSError *error;
//BOOL removedItemAtPath = NO;
BOOL copiedItemToPath = NO;
copiedItemToPath = [fileMgr copyItemAtPath:oldDatabasePath toPath:newDatabasePath error:&error];
if (copiedItemToPath) {
DLog(@"ITEM COPIED");
}
else
DLog(@"FAILED TO COPY ITEM: %@", error);
[fileMgr removeItemAtPath:oldDatabasePath error:&error];
[saveData setBool:YES forKey:@"databaseUpdated"];
[saveData synchronize];
}
}
//NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"AppData.sqlite"];
NSURL *storeURL = [[self applicationHiddenDocumentsDirectory] URLByAppendingPathComponent:@"AppData.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
DLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
我也遇到了同样的问题,找到了一个非常简单的解决方案。基本上,在初始化NSPersistentStoreCoordinator之前,如在
-(BOOL)应用程序的顶部:(UIApplication*)应用程序使用选项完成启动:(NSDictionary*)启动选项之前,请将数据库文件从其当前位置移到新位置。如果新位置尚不存在,请确保创建新位置。在下面的示例代码中,我使用了库/应用程序支持目录下的数据库子目录。我没有显示“数据库”目录的创建;但是,这也必须在移动文件之前发生。确保存储初始化使用新位置。
NSURL*storeURL=[NSURL fileURLWithPath:[self-getFullPrivatePath:@”“];
这里有一些代码可以帮助您明确这一点。我还包括了我的助手方法,因此对于这些方法来自何处或如何工作没有任何混淆。请注意,有3个数据库文件必须移动,这就是为什么存在循环的原因。它们是[数据库文件名],[数据库文件名]-wal和[数据库文件名]-shm
- (void)convertPublicFilesToPrivate
{
NSFileManager *fileManager = [NSFileManager defaultManager];
// Move the database files
NSArray<NSString *> *dbFiles = [self filesByPrefix:@"<database file name>" isPublic: YES];
for (NSString *dbFile in dbFiles) {
NSString *fileName = [dbFile lastPathComponent];
NSError *error;
NSString *privatePath = [self getFullPrivatePath:fileName];
[fileManager moveItemAtPath: dbFile toPath: privatePath error:&error];
NSLog(@"Move database file %@ returned %@", fileName, error);
}
}
-(NSString *)getPublicPath
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
}
- (NSString *)getPrivatePath
{
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"Database"];
}
-(NSString*)getFullPublicPath:(NSString *)fileName
{
NSString *documentsDirectory = [self getPublicPath];
if (fileName != nil) {
return [documentsDirectory stringByAppendingPathComponent:fileName];
} else {
return documentsDirectory;
}
}
-(NSString*)getFullPrivatePath:(NSString *)fileName
{
NSString *documentsDirectory = [self getPrivatePath];
if (fileName != nil) {
return [documentsDirectory stringByAppendingPathComponent:fileName];
} else {
return documentsDirectory;
}
}
- (NSArray*)filesByPrefix:(NSString*)prefix isPublic:(BOOL)isPublic
{
NSString *documentsDirectory = isPublic ? [self getPublicPath] : [self getPrivatePath];
NSArray<NSString *> *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:NULL];
NSMutableArray *fileList = [[NSMutableArray alloc] initWithCapacity:1];
for (NSString *fileName in directoryContent) {
if (prefix.length == 0 || [fileName hasPrefix:prefix]) {
[fileList addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
}
return fileList;
}
-(void)convertPublicFilesToPrivate
{
NSFileManager*fileManager=[NSFileManager defaultManager];
//移动数据库文件
NSArray*dbFiles=[self filesByPrefix:@“isPublic:YES];
for(数据库文件中的NSString*dbFile){
NSString*fileName=[dbFile lastPathComponent];
n错误*错误;
NSString*privatePath=[self-getFullPrivatePath:fileName];
[fileManager moveItemAtPath:dbFile-toPath:privatePath错误:&错误];
NSLog(@“移动数据库文件%@返回%@”,文件名,错误);
}
}
-(NSString*)getPublicPath
{
返回NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0];
}
-(NSString*)getPrivatePath
{
返回[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES)[0]stringByAppendingPathComponent:@“Database”];
}
-(NSString*)getFullPublicPath:(NSString*)文件名
{
NSString*documentsDirectory=[self-getPublicPath];
如果(文件名!=nil){
返回[DocumentsDirectoryStringByAppendingPathComponent:fileName];
}否则{
返回文件目录;
}
}
-(NSString*)getFullPrivatePath:(NSString*)文件名
{
NSString*documentsDirectory=[self-getPrivatePath];
如果(文件名!=nil){
返回[DocumentsDirectoryStringByAppendingPathComponent:fileName];
}否则{
返回文件目录;
}
}
-(NSArray*)filesByPrefix:(NSString*)前缀isPublic:(BOOL)isPublic
{
NSString*documentsDirectory=isPublic?[self-getPublicPath]:[self-getPrivatePath];
NSArray*directoryContent=[[NSFileManager defaultManager]目录目录路径:文档目录错误:NULL];
NSMutableArray*文件列表=[[NSMutableArray alloc]initWithCapacity:1];
for(directoryContent中的NSString*文件名){
if(prefix.length==0 | |[文件名hasPrefix:前缀]){
[fileList addObject:[documentsDirectory stringByAppendingPathComponent:fileName];
}
}
返回文件列表;
}
Crashlog对回答您的问题真的很有帮助吗?崩溃日志不是很有帮助,只是说我的应用程序无法加载它所需的数据,我知道这些数据,否则它不会崩溃***由于未捕获的异常“NSRangeException”,终止应用程序,原因:'***-[\u NSArrayM objectAtIndex:]:索引0超出空数组“***第一次抛出调用堆栈”的界限:嗨,汤姆,不确定是否发生了这种情况。当根视图控制器试图通过NSFetchRequest加载其原始coredata时发生抛出。据我所知,当我更改NSPersistentStoreCoordinatorsStoreURL时,它会在空目录中创建一个新的sqlite数据库。然后,我想删除该sqlite数据库,并在应用程序更新之前将其复制到原始sqlite数据库上,然后复制到此新目录。我已经调试了删除和复制操作,它们也按照我的要求进行。然后你应该在问题中发布更多信息,因为有了可用的详细信息,没有其他合理的结论。尝试设置一个异常断点,它将告诉您导致崩溃的代码行。
- (void)convertPublicFilesToPrivate
{
NSFileManager *fileManager = [NSFileManager defaultManager];
// Move the database files
NSArray<NSString *> *dbFiles = [self filesByPrefix:@"<database file name>" isPublic: YES];
for (NSString *dbFile in dbFiles) {
NSString *fileName = [dbFile lastPathComponent];
NSError *error;
NSString *privatePath = [self getFullPrivatePath:fileName];
[fileManager moveItemAtPath: dbFile toPath: privatePath error:&error];
NSLog(@"Move database file %@ returned %@", fileName, error);
}
}
-(NSString *)getPublicPath
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
}
- (NSString *)getPrivatePath
{
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"Database"];
}
-(NSString*)getFullPublicPath:(NSString *)fileName
{
NSString *documentsDirectory = [self getPublicPath];
if (fileName != nil) {
return [documentsDirectory stringByAppendingPathComponent:fileName];
} else {
return documentsDirectory;
}
}
-(NSString*)getFullPrivatePath:(NSString *)fileName
{
NSString *documentsDirectory = [self getPrivatePath];
if (fileName != nil) {
return [documentsDirectory stringByAppendingPathComponent:fileName];
} else {
return documentsDirectory;
}
}
- (NSArray*)filesByPrefix:(NSString*)prefix isPublic:(BOOL)isPublic
{
NSString *documentsDirectory = isPublic ? [self getPublicPath] : [self getPrivatePath];
NSArray<NSString *> *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:NULL];
NSMutableArray *fileList = [[NSMutableArray alloc] initWithCapacity:1];
for (NSString *fileName in directoryContent) {
if (prefix.length == 0 || [fileName hasPrefix:prefix]) {
[fileList addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
}
return fileList;
}