iOS/Swift无法原生解压多文件Zip包,求原生支持的替代格式
iOS/Swift 原生多文件打包解压方案(无第三方库)
1. 优先推荐:.tar.gz 格式(基于 libarchive + Compression 框架)
iOS 原生通过 Foundation 间接支持 libarchive,可以直接处理 .tar 多文件归档,配合系统 Compression 框架实现压缩,完全满足多文件打包+原生解压需求,无需第三方库。
打包流程
- 第一步:将多个本地文件打包为
.tar归档
使用Archive类(导入Foundation即可),指定归档路径与写入模式,通过addEntry方法逐个添加文件,同时保留文件名、目录结构等元数据。 - 第二步:对
.tar文件做 gzip 压缩
利用Compression框架的compression_encode_buffer函数,将.tar的二进制数据压缩为 gzip 格式,最终生成.tar.gz包。
解压流程
- 第一步:解码 gzip 数据
用compression_decode_buffer函数将.tar.gz的二进制数据还原为原始.tar文件数据。 - 第二步:解析
.tar归档并提取文件
再次使用Archive类读取.tar数据,遍历每个归档条目,将内容写入对应路径的本地文件,自动还原原目录结构。
2. 自定义容器 + Accelerate 流压缩(复杂度较高)
如果一定要用 Accelerate 框架的流压缩能力,需要自己定义简单的容器格式来存储多文件元数据:
- 打包时:
- 先写入自定义头部,包含文件总数、每个文件的名称、大小、偏移量;
- 用 Accelerate 的
vImage压缩API逐个处理文件内容,将压缩后的数据依次写入输出文件。
- 解压时:
- 先读取头部解析出所有文件的元信息;
- 按偏移量读取对应压缩数据,用 Accelerate 解压后写入本地文件。
这种方式需要手动处理容器解析,复杂度远高于.tar.gz方案,仅适合特殊场景。
3. 不推荐:NSKeyedArchiver 归档(仅适用于原生对象)
如果打包的是Swift/OC原生对象(如自定义模型、图片数据),可以用 NSKeyedArchiver 将多个对象归档为单个文件,但这种方式仅支持可归档对象,对视频、文档等通用文件兼容性差,不适合作为通用多文件打包方案。
核心代码示例(.tar.gz 解压)
import Foundation import Compression func extractTarGzip(from data: Data, to directoryURL: URL) throws { // 第一步:解压gzip数据 let decompressedTarData = try data.decompressed(using: .gzip) // 第二步:解析tar归档并提取文件 let archive = try Archive(data: decompressedTarData, accessMode: .read) for entry in archive { let targetURL = directoryURL.appendingPathComponent(entry.path) // 创建父目录 try FileManager.default.createDirectory(at: targetURL.deletingLastPathComponent(), withIntermediateDirectories: true) if entry.type == .directory { try FileManager.default.createDirectory(at: targetURL, withIntermediateDirectories: false) } else { try entry.readData().write(to: targetURL) } } } // Data扩展:实现gzip解压 extension Data { func decompressed(using algorithm: CompressionAlgorithm) throws -> Data { let bufferSize = 4096 var decompressedData = Data() let sourceBuffer = self.withUnsafeBytes { $0.bindMemory(to: UInt8.self) } var sourceIndex = sourceBuffer.startIndex let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize) defer { destinationBuffer.deallocate() } var stream = compression_stream() var status = compression_stream_init(&stream, COMPRESSION_STREAM_DECODE, algorithm.rawValue) guard status != COMPRESSION_STATUS_ERROR else { throw NSError(domain: "CompressionError", code: -1, userInfo: nil) } defer { compression_stream_destroy(&stream) } stream.src_ptr = sourceBuffer.baseAddress! + sourceIndex stream.src_size = self.count stream.dst_ptr = destinationBuffer stream.dst_size = bufferSize repeat { status = compression_stream_process(&stream, COMPRESSION_STREAM_FINALIZE) switch status { case COMPRESSION_STATUS_OK, COMPRESSION_STATUS_END: let writtenBytes = bufferSize - stream.dst_size decompressedData.append(destinationBuffer, count: writtenBytes) stream.dst_ptr = destinationBuffer stream.dst_size = bufferSize case COMPRESSION_STATUS_ERROR: throw NSError(domain: "CompressionError", code: -1, userInfo: nil) default: break } } while status == COMPRESSION_STATUS_OK return decompressedData } } enum CompressionAlgorithm: Int32 { case gzip = COMPRESSION_GZIP case lzfse = COMPRESSION_LZFSE case zlib = COMPRESSION_ZLIB }
内容的提问来源于stack exchange,提问作者Fattie
相关产品推荐
相关产品推荐

