iOS中ArchiveByteStream写入App Group容器目录失败问题排查
问题排查:ArchiveByteStream写入App Group目录失败,SSZipArchive却正常的原因与修复方案
问题背景
你在iOS中使用ArchiveByteStream写入App Group容器目录时失败,报错Unable to create writeFileStream,但此前用SSZipArchive可以成功解压到同一目录。
成功的SSZipArchive代码:
static func unzipDatFilesToAssetDirectory() { let archiveFileName = "Archive.zip" guard let sourceURL = Bundle.main.url(forResource: archiveFileName, withExtension: nil) else { LogService.shared.logCritical("Failed to find \(archiveFileName) in the bundle") return } do { try SSZipArchive.unzipFile(atPath: sourceURL.path, toDestination: TGConstant.assetDirectory.path, overwrite: true, password: nil) LogService.shared.logInfo("Successfully unzipped \(archiveFileName) to \(TGConstant.assetDirectory.path)") } catch { LogService.shared.logCritical("Error unzipping \(archiveFileName) to \(TGConstant.assetDirectory.path): \(error)") } }
失败的ArchiveByteStream代码:
static func unpackDatFilesToAssetDirectory() { let archiveFileName = "archive.aar" guard let sourceURL = Bundle.main.url(forResource: archiveFileName, withExtension: nil) else { LogService.shared.logCritical("DAT: Failed to find \(archiveFileName) in the bundle") return } let sourcePath = sourceURL.path let archiveFilePath = FilePath(sourcePath) guard let readFileStream = ArchiveByteStream.fileStream( path: archiveFilePath, mode: .readOnly, options: [ ], permissions: FilePermissions(rawValue: 0o644)) else { LogService.shared.logCritical("DAT: Unable to create readFileStream.") return } let destinationFilePath = FilePath(TGConstant.assetDirectory.path) LogService.shared.logInfo("DAT: Source path: \(sourcePath)") LogService.shared.logInfo("DAT: Destination path: \(TGConstant.assetDirectory.path)") guard let writeFileStream = ArchiveByteStream.fileStream( path: destinationFilePath, mode: .writeOnly, options: [ .create ], permissions: FilePermissions(rawValue: 0o644)) else { LogService.shared.logCritical("DAT: Unable to create writeFileStream.") return } guard let decompressStream = ArchiveByteStream.decompressionStream(readingFrom: readFileStream) else { LogService.shared.logCritical("DAT: Unable to create decompress stream.") return } do { _ = try ArchiveByteStream.process(readingFrom: decompressStream, writingTo: writeFileStream) LogService.shared.logInfo("DAT: Successfully uncompressed \(archiveFileName) to \(TGConstant.assetDirectory.path)") } catch { LogService.shared.logCritical("DAT: Error uncompressing \(archiveFileName) to \(TGConstant.assetDirectory.path): \(error)") } defer { try? decompressStream.close() try? writeFileStream.close() try? readFileStream.close() } }
报错信息:
CRITICAL FileManagerHelper.unpackDatFilesToAssetDirectory():66 - DAT:
Unable to create writeFileStream.
核心差异分析
问题出在路径类型的误解:
SSZipArchive的toDestination参数接受目录路径,它会自动在该目录下创建解压后的文件(根据zip包内的结构)。ArchiveByteStream.fileStream在.writeOnly模式下,必须传入具体的文件路径,而不能是目录路径。你当前传入的TGConstant.assetDirectory.path是目录,系统无法直接向目录写入字节流,因此创建写流失败。
排查步骤
- 验证目标路径类型:打印
TGConstant.assetDirectory的属性,确认它是目录而非文件。 - 捕获底层错误:修改代码,通过
do-catch获取fileStream创建失败的具体错误(原代码仅判断nil,未捕获错误),比如:do { let writeFileStream = try ArchiveByteStream.fileStream( path: destinationFilePath, mode: .writeOnly, options: [.create], permissions: FilePermissions(rawValue: 0o644) ) } catch { LogService.shared.logCritical("DAT: Failed to create write stream with error: \(error)") } - 确认App Group权限:检查项目配置中是否已正确启用App Group,且
TGConstant.assetDirectory是通过FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:)获取的合法路径。
修复建议
1. 指定具体的目标文件路径
将目标路径从目录改为具体文件名,比如在App Group目录下生成unpacked_data.dat文件:
// 替换原有的destinationFilePath定义 let destinationFileURL = TGConstant.assetDirectory.appendingPathComponent("unpacked_data.dat") let destinationFilePath = FilePath(destinationFileURL.path)
2. 修正权限与选项(可选)
如果目标文件可能已存在,添加.truncate选项覆盖原有文件:
guard let writeFileStream = ArchiveByteStream.fileStream( path: destinationFilePath, mode: .writeOnly, options: [.create, .truncate], permissions: FilePermissions(rawValue: 0o644)) else { // ...错误处理 }
3. 完整修正后的代码片段
static func unpackDatFilesToAssetDirectory() { let archiveFileName = "archive.aar" guard let sourceURL = Bundle.main.url(forResource: archiveFileName, withExtension: nil) else { LogService.shared.logCritical("DAT: Failed to find \(archiveFileName) in the bundle") return } let sourcePath = sourceURL.path let archiveFilePath = FilePath(sourcePath) guard let readFileStream = ArchiveByteStream.fileStream( path: archiveFilePath, mode: .readOnly, options: [], permissions: FilePermissions(rawValue: 0o644)) else { LogService.shared.logCritical("DAT: Unable to create readFileStream.") return } // 关键修改:指定具体文件路径,而非目录 let destinationFileURL = TGConstant.assetDirectory.appendingPathComponent("unpacked_data.dat") let destinationFilePath = FilePath(destinationFileURL.path) LogService.shared.logInfo("DAT: Source path: \(sourcePath)") LogService.shared.logInfo("DAT: Destination file path: \(destinationFileURL.path)") guard let writeFileStream = ArchiveByteStream.fileStream( path: destinationFilePath, mode: .writeOnly, options: [.create, .truncate], // 添加truncate覆盖已有文件 permissions: FilePermissions(rawValue: 0o644)) else { LogService.shared.logCritical("DAT: Unable to create writeFileStream.") return } guard let decompressStream = ArchiveByteStream.decompressionStream(readingFrom: readFileStream) else { LogService.shared.logCritical("DAT: Unable to create decompress stream.") return } do { _ = try ArchiveByteStream.process(readingFrom: decompressStream, writingTo: writeFileStream) LogService.shared.logInfo("DAT: Successfully uncompressed \(archiveFileName) to \(destinationFileURL.path)") } catch { LogService.shared.logCritical("DAT: Error uncompressing \(archiveFileName): \(error)") } defer { try? decompressStream.close() try? writeFileStream.close() try? readFileStream.close() } }
额外说明
如果你的archive.aar是多文件归档,ArchiveByteStream的单流处理方式不适用,需要使用Archive类遍历归档内的条目,逐个写入目录中的对应文件。
内容的提问来源于stack exchange,提问作者Houman
相关产品推荐
相关产品推荐

