iOS应用SQLite数据库上传下载后无法读写的技术问询
解决iPhone Documents目录SQLite文件上传下载后无法读写的问题
嘿,我来帮你搞定这个问题!你遇到的“下载恢复后文件被加密无法读写”的情况,大概率是两个原因导致的:iOS系统的文件数据保护机制,或者是你没把SQLite的附属文件(WAL/Shm)一起上传。下面给你拆解问题并给出具体的修复方案:
一、问题根源拆解
- iOS数据保护坑:默认情况下,Documents目录里的文件会被系统自动加密(绑定用户的设备密码)。你把文件转成
NSData上传没问题,但下载回来写入新文件时,新文件的默认保护级别可能和原文件不匹配,导致App没法解密访问,看起来就像“文件被加密”了。 - SQLite附属文件遗漏:如果你的SQLite用了默认的WAL(写前日志)模式,会生成
xxx.sqlite-wal和xxx.sqlite-shm两个辅助文件。只传主.sqlite文件的话,恢复后的数据库会因为缺了日志/共享内存文件而打不开,也容易被误判成加密损坏。
二、具体修复方案
1. 搞定文件数据保护问题
在下载写入文件时,咱们显式把文件的保护属性设为NSFileProtectionNone(或者和原文件一致的级别),确保App能正常访问:
修改你的写入下载文件代码,替换原来的writeToFile:atomically:,用带保护选项的写入方法:
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSData *DBData = [NSData dataWithContentsOfURL:location]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *chatDatabasePath = [documentsDirectory stringByAppendingPathComponent:@"ChatDB.sqlite"]; NSString *contactDatabasePath = [documentsDirectory stringByAppendingPathComponent:@"ContactDB.sqlite"]; NSError *writeError = nil; if ([self.activeDownload isEqualToString:@"chat"]){ // 写入时关闭文件保护,避免加密限制 BOOL success = [DBData writeToFile:chatDatabasePath options:NSDataWritingFileProtectionNone error:&writeError]; if (!success) { NSLog(@"写入ChatDB失败: %@", writeError); } self.activeDownload = @"contact"; [self startDownload:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",pingovaURL,self.ContactDB_URL]]]; }else{ BOOL success = [DBData writeToFile:contactDatabasePath options:NSDataWritingFileProtectionNone error:&writeError]; if (!success) { NSLog(@"写入ContactDB失败: %@", writeError); } } }
要是你想保持和原文件一样的保护级别,可以先读取原文件的属性再应用到新文件:
// 获取原文件的保护属性 NSFileManager *fm = [NSFileManager defaultManager]; NSDictionary *originalAttrs = [fm attributesOfItemAtPath:chatDatabasePath error:nil]; NSString *protectionLevel = originalAttrs[NSFileProtectionKey]; // 写入新文件后设置属性 [fm setAttributes:@{NSFileProtectionKey: protectionLevel} ofItemAtPath:chatDatabasePath error:&writeError];
2. 上传SQLite的完整文件组
一定要把SQLite的所有相关文件都上传,包括:
ChatDB.sqliteChatDB.sqlite-walChatDB.sqlite-shmContactDB.sqliteContactDB.sqlite-walContactDB.sqlite-shm
修改你的获取待上传文件代码,加上对附属文件的处理:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSFileManager *fm = [NSFileManager defaultManager]; // 聊天数据库的所有文件路径 NSString *chatDBPath = [documentsDirectory stringByAppendingPathComponent:@"ChatDB.sqlite"]; NSString *chatDBWalPath = [documentsDirectory stringByAppendingPathComponent:@"ChatDB.sqlite-wal"]; NSString *chatDBShmPath = [documentsDirectory stringByAppendingPathComponent:@"ChatDB.sqlite-shm"]; // 联系人数据库的所有文件路径 NSString *contactDBPath = [documentsDirectory stringByAppendingPathComponent:@"ContactDB.sqlite"]; NSString *contactDBWalPath = [documentsDirectory stringByAppendingPathComponent:@"ContactDB.sqlite-wal"]; NSString *contactDBShmPath = [documentsDirectory stringByAppendingPathComponent:@"ContactDB.sqlite-shm"]; // 读取文件数据(先判断文件是否存在) if ([fm fileExistsAtPath:chatDBPath]) self.ChatDBData = [NSData dataWithContentsOfFile:chatDBPath]; if ([fm fileExistsAtPath:chatDBWalPath]) self.ChatDBWalData = [NSData dataWithContentsOfFile:chatDBWalPath]; if ([fm fileExistsAtPath:chatDBShmPath]) self.ChatDBShmData = [NSData dataWithContentsOfFile:chatDBShmPath]; if ([fm fileExistsAtPath:contactDBPath]) self.ContactDBData = [NSData dataWithContentsOfFile:contactDBPath]; if ([fm fileExistsAtPath:contactDBWalPath]) self.ContactDBWalData = [NSData dataWithContentsOfFile:contactDBWalPath]; if ([fm fileExistsAtPath:contactDBShmPath]) self.ContactDBShmData = [NSData dataWithContentsOfFile:contactDBShmPath];
然后修改你的上传代码,支持一次性上传多个文件(或者分多次上传对应附属文件),下载的时候也要把这些文件都恢复到Documents目录里哦。
3. 可选方案:关闭SQLite的WAL模式
如果不想麻烦处理附属文件,可以在打开数据库时关闭WAL模式,这样SQLite只会生成主.sqlite文件:
// 打开数据库后执行这条SQL语句 sqlite3_exec(db, "PRAGMA journal_mode=DELETE;", NULL, NULL, NULL);
注意:关闭WAL模式会降低SQLite的并发读写性能,你得根据App的实际需求权衡要不要这么做。
三、验证步骤
- 上传前确认所有SQLite相关文件都已经读取并上传;
- 下载后检查Documents目录里是否有完整的文件组;
- 打开数据库前,确认文件的保护属性设置正确;
- 用SQLite工具(比如SQLiteBrowser)打开恢复后的文件,验证能不能正常读取数据。
内容的提问来源于stack exchange,提问作者Harsh Srivastava
相关产品推荐
相关产品推荐

