【问题标题】:move ios sqlite database when updating app更新应用程序时移动 ios sqlite 数据库
【发布时间】:2013-08-03 13:40:51
【问题描述】:

我在我的应用程序中使用核心数据,我想在下次更新时启动 iTunes 文件共享,但需要先移动我的应用程序 sqlite 数据库。我已尝试使用下面的代码,但应用程序在启动时崩溃。我想我可以在 NSPersistentStoreCoordinator 中用新的商店 url 替换旧商店 url,其中“newDatabasePath”与新商店 url 匹配。

然后将sqlite文件替换为

- (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 堆栈加载数据时,该应用程序仍然会引发异常,但随后会重新加载一切正常,并使用“图书馆/私人文档”中新位置的 sqlite 文件"

【问题讨论】:

  • 崩溃日志对回答您的问题真的很有帮助吗?
  • 崩溃日志不是很有帮助,只是说我的应用程序无法加载我知道的所需数据,否则它不会崩溃 *** 由于未捕获的异常而终止应用程序'NSRangeException',原因:'*** -[__NSArrayM objectAtIndex:]:空数组的索引 0 超出范围'*** 第一次抛出调用堆栈:

标签: ios sqlite core-data nsfilemanager


【解决方案1】:

我遇到了同样的问题,并找到了一个非常简单的解决方案。基本上,在初始化 NSPersistentStoreCoordinator 之前,就像在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 的顶部一样,将数据库文件从当前位置移动到新位置。如果新位置尚不存在,请确保创建新位置。在下面的示例代码中,我使用 Library/Application Support 目录下的 Database 子目录。我没有显示“数据库”目录的创建;但是,这也必须在移动文件之前发生。确保您的商店初始化使用新位置。

NSURL *storeURL = [NSURL fileURLWithPath:[self getFullPrivatePath:@"<Database file name>"]];



这里有一些代码可以帮助您弄清楚这一点。我也包含了我的辅助方法,因此对于这些方法的来源或它们的工作方式没有任何混淆。请注意,必须移动 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;
}

【讨论】:

    【解决方案2】:

    事实证明,答案很简单!

    我刚刚将复制应用程序原始数据库所需的代码移动到 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 在添加存储之前而不是尝试从 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions.

    又浪费了一天!

    - (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;
    }
    

    【讨论】:

      【解决方案3】:

      既然你说错误信息是:

      *** 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。所以,dirPaths 是空的。我不知道为什么,因为你没有发布你给它一个值的代码,但这就是这次崩溃的原因。

      【讨论】:

      • 嗨,汤姆,不确定是不是这样。当根视图控制器尝试通过 NSFetchRequest 加载它的原始核心数据时,就会发生抛出。据我了解,当我更改 NSPersistentStoreCoordinators storeUrl 时,它会在空目录中创建一个新的 sqlite 数据库。然后我想删除那个 sqlite 数据库并从应用程序更新到这个新目录之前复制原始 sqlite 数据库。我已经调试了删除和复制操作,它们也在按我的意愿进行。
      • 那么你应该在你的问题中发布更多信息,因为有了可用的详细信息,没有其他合理的结论。尝试设置一个异常断点,它会告诉你是哪一行代码导致了崩溃。
      猜你喜欢
      • 2011-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-05
      • 1970-01-01
      • 1970-01-01
      • 2013-03-07
      • 1970-01-01
      相关资源
      最近更新 更多