You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是目录,系统无法直接向目录写入字节流,因此创建写流失败。

排查步骤

  1. 验证目标路径类型:打印TGConstant.assetDirectory的属性,确认它是目录而非文件。
  2. 捕获底层错误:修改代码,通过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)")
    }
    
  3. 确认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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 03:50:02