ZipUtilities
简介
ZipUtilities (前缀为 NOZ
,用于 Nolan O'Brien ZipUtilities),是一个用于iOS和Mac OS X的zip和unzip实用程序的库。
背景
有时可能需要从简单的API进行轻松压缩和解压缩数据。市面上有很多压缩/解压缩实用程序,但它们在某个方面或另一个方面都会有些不尽如人意。
- 太底层
- 太复杂
- 代码质量差
- 古老的编程实践风格(基本上是非常古老的)
- 缺乏面向服务的架构(请求、操作和响应模式)
目标是为存档和解压缩zip文件提供易于使用的现代接口。作为一个特定重点,提供面向服务的架构可以为NSOperation组合提供强大的支持。
安装
《ZipUtilities》Xcode项目包含用于构建iOS和OSX动态框架的目标。您可以手动构建这些并添加到项目中,或者在Xcode中添加一个子项目。
或者,您可以使用以下依赖管理器之一
CocoaPods
将ZipUtilities添加到您的Podfile
pod 'ZipUtilities', '~> 1.11.1'
Carthage
将ZipUtilities添加到您的Cartfile
github "NSProgrammer/ZipUtilities"
文档
您可以在本地使用appledoc构建文档,或者访问http://cocoadocs.org/docsets/ZipUtilities在线文档
概述
面向服务的接口(NSOperations)
《ZipUtilities》的主要价值在于它提供了一个简单的接口,以便将数据或文件存档到单个zip存档中,并将zip存档解档到包含的文件中。《ZipUtilities》的主要方法是为压缩和解压缩提供面向服务的模式。
NOZCompress.h
NOZCompress.h
包含与压缩到zip存档相关的面向服务的接口。
NOZCompressRequest
是一个封装压缩操作要对其执行的什么和如何的对象NOZCompressOperation
是执行压缩的NSOperation
子类对象。作为NSOperation
,消费者可以利用取消、优先级和依赖关系。操作还会提供进度,可以通过观察 KVO 上的progress
属性或通过代理回调来观察。NOZCompressDelegate
是NOZCompressOperation
的代理。它提供进度和完成的回调。NOZCompressResult
是封装压缩操作结果的对象。它包含操作是否成功,没有成功时的错误,创建的 zip 存档的路径以及其他信息性指标,如持续时间和压缩率。
示例
- (NSOperation *)startCompression
{
NOZCompressRequest *request = [[NOZCompressRequest alloc] initWithDestinationPath:self.zipFilePath];
[request addEntriesInDirectory:self.sourceDirectoryPath
filterBlock:^BOOL(NSString *filePath) {
return [filePath.lastPathComponent hasPrefix:@"."];
}
compressionSelectionBlock:NULL];
[request addDataEntry:self.data name:@"Aesop.txt"];
for (id<NOZZippableEntry> entry in self.additionalEntries) {
[request addEntry:entry];
}
NOZCompressionOperation *op = [[NOZCompressOperation alloc] initWithRequest:request delegate:self];
[self.operationQueue addOperation:op];
// return operation so that a handle can be maintained and cancelled if necessary
return op;
}
- (void)compressOperation:(NOZCompressOperation *)op didCompleteWithResult:(NOZCompressResult *)result
{
dispatch_async(dispatch_get_main_queue(), ^{
self.completionBlock(result.didSuccess, result.operationError);
});
}
- (void)compressOperation:(NOZCompressOperation *)op didUpdateProgress:(float)progress
{
dispatch_async(dispatch_get_main_queue(), ^{
self.progressBlock(progress);
});
}
func startCompression() -> NSOperation
{
let request = NOZCompressRequest.init(destinationPath: self.zipFilePath)
request.addEntriesInDirectory(self.sourceDirectoryPath, filterBlock: { (filePath: String) -> Bool in
return ((filePath as NSString).lastPathComponent as NSString).hasPrefix(".")
}, compressionSelectionBlock: nil)
request.addDataEntry(self.data name:"Aesop.txt")
for entry in self.additionalEntries {
request.addEntry(entry)
}
let operation = NOZCompressOperation.init(request: request, delegate: self)
zipQueue?.addOperation(operation)
// return operation so that a handle can be maintained and cancelled if necessary
return operation
}
func compressOperation(op: NOZCompressOperation, didCompleteWithResult result: NOZCompressResult)
{
dispatch_async(dispatch_get_main_queue(), {
self.completionBlock(result.didSuccess, result.operationError);
})
}
func compressOperation(op: NOZCompressOperation, didUpdateProgress progress: Float)
{
dispatch_async(dispatch_get_main_queue(), {
self.progressBlock(progress);
})
}
NOZDecompress.h
NOZDecompress.h
包含相关于从 zip 存档中解压缩的服务性接口。
NOZDecompressRequest
是封装解压缩操作的 what 和 how 的对象。NOZDecompressOperation
是执行解压缩的NSOperation
子类对象。作为NSOperation
,消费者可以利用取消、优先级和依赖关系。操作还会提供进度,可以通过观察 KVO 上的progress
属性或通过代理回调来观察。NOZDecompressDelegate
是NOZDecompressOperation
的代理。它提供进度、覆盖输出文件和完成的回调。NOZDecompressResult
是封装解压缩操作结果的对象。它包含操作是否成功,没有成功时的错误,输出未解压文件的路径以及其他信息性指标,如持续时间和压缩率。
示例
- (NSOperation *)startDecompression
{
NOZDecompressRequest *request = [[NOZDecompressRequest alloc] initWithSourceFilePath:self.zipFilePath];
NOZDecompressOperation *op = [[NOZDecompressOperation alloc] initWithRequest:request delegate:self];
[self.operationQueue addOperation:op];
// return operation so that a handle can be maintained and cancelled if necessary
return op;
}
- (void)decompressOperation:(NOZDecompressOperation *)op didCompleteWithResult:(NOZDecompressResult *)result
{
dispatch_async(dispatch_get_main_queue(), ^{
self.completionBlock(result.didSuccess, result.destinationFiles, result.operationError);
});
}
- (void)decompressOperation:(NOZDecompressOperation *)op didUpdateProgress:(float)progress
{
dispatch_async(dispatch_get_main_queue(), ^{
self.progressBlock(progress);
});
}
func startDecompression() -> NSOperation
{
let request = NOZDecompressRequest.init(sourceFilePath: self.zipFilePath)
let operation = NOZDecompressOperation.init(request: request, delegate: self)
zipQueue?.addOperation(operation)
return operation
}
func decompressOperation(op: NOZDecompressOperation, didCompleteWithResult result: NOZDecompressResult)
{
dispatch_async(dispatch_get_main_queue(), {
self.completionBlock(result.didSuccess, result.destinationFiles, result.operationError);
})
}
func decompressOperation(op: NOZDecompressOperation, didUpdateProgress progress: Float)
{
dispatch_async(dispatch_get_main_queue(), {
self.progressBlock(progress);
})
}
手动压缩和解压缩
另外,如果不需要 NSOperation 支持,公开了 zipping 和 unzipping 的底层对象以供直接使用。
NOZZipper.h
NOZZipper
是封装将源(NSData、流和/或文件)压缩成磁盘中 zip 存档文件工作的对象。
示例
- (BOOL)zipThingsUpAndReturnError:(out NSError **)error
{
NOZZipper *zipper = [[NOZZipper alloc] initWithZipFile:pathToCreateZipFile];
if (![zipper openWithMode:NOZZipperModeCreate error:error]) {
return NO;
}
__block int64_t totalBytesCompressed = 0;
NOZFileZipEntry *textFileZipEntry = [[NOZFileZipEntry alloc] initWithFilePath:textFilePath];
textFileZipEntry.comment = @"This is a heavily compressed text file.";
textFileZipEntry.compressionLevel = NOZCompressionLevelMax;
NSData *jpegData = UIImageJPEGRepresentation(someImage, 0.8f);
NOZDataZipEntry *jpegEntry = [[NOZDataZipEntry alloc] initWithData:jpegData name:@"image.jpg"];
jpegEntry.comment = @"This is a JPEG so it doesn't need more compression.";
jpegEntry.compressionMode = NOZCompressionModeNone;
if (![zipper addEntry:textFileZipEntry
progressBlock:^(int64_t totalBytes, int64_t bytesComplete, int64_t bytesCompletedThisPass, BOOL *abort) {
totalBytesCompressed = bytesCompletedThisPass;
}
error:error]) {
return NO;
}
if (![zipper addEntry:jpegEntry
progressBlock:^(int64_t totalBytes, int64_t bytesComplete, int64_t bytesCompletedThisPass, BOOL *abort) {
totalBytesCompressed = bytesCompletedThisPass;
}
error:error]) {
return NO;
}
zipper.globalComment = @"This is a global comment for the entire archive.";
if (![zipper closeAndReturnError:error]) {
return NO;
}
int64_t archiveSize = (int64_t)[[[NSFileManager defaultFileManager] attributesOfItemAtPath:zipper.zipFilePath] fileSize];
NSLog(@"Compressed to %@ with compression ratio of %.4f:1", zipper.zipFilePath, (double)totalBytesCompressed / (double)archiveSize);
return YES;
}
NOZUnzipper.h
NOZUnzipper
是封装从磁盘中 zip 存档文件解压缩到目的地(NSData、流和/或文件)工作的对象。
示例
- (BOOL)unzipThingsAndReturnError:(out NSError **)error
{
NSAssert(![NSThread isMainThread]); // do this work on a background thread
NOZUnzipper *unzipper = [[NOZUnzipper alloc] initWithZipFile:zipFilePath];
if (![unzipper openAndReturnError:error]) {
return NO;
}
if (nil == [unzipper readCentralDirectoryAndReturnError:error]) {
return NO;
}
__block NSError *enumError = nil;
[unzipper enumerateManifestEntriesUsingBlock:^(NOZCentralDirectoryRecord * record, NSUInteger index, BOOL * stop) {
NSString *extension = record.name.pathExtension;
if ([extension isEqualToString:@"jpg"]) {
*stop = ![self readImageFromUnzipper:unzipper withRecord:record error:&enumError];
} else if ([extension isEqualToString:@"json"]) {
*stop = ![self readJSONFromUnzipper:unzipper withRecord:record error:&enumError];
} else {
*stop = ![self extractFileFromUnzipper:unzipper withRecord:record error:&enumError];
}
}];
if (enumError) {
*error = enumError;
return NO;
}
if (![unzipper closeAndReturnError:error]) {
return NO;
}
return YES;
}
- (BOOL)readImageFromUnzipper:(NOZUnzipper *)unzipper withRecord:(NOZCentralDirectoryRecord *)record error:(out NSError **)error
{
CGImageSourceRef imageSource = CGImageSourceCreateIncremental(NULL);
custom_defer(^{ // This is Obj-C equivalent to 'defer' in Swift. See http://www.openradar.me/21684961 for more info.
if (imageSource) {
CFRelease(imageSource);
}
});
NSMutableData *imageData = [NSMutableData dataWithCapacity:record.uncompressedSize];
if (![unzipper enumerateByteRangesOfRecord:record
progressBlock:NULL
usingBlock:^(const void * bytes, NSRange byteRange, BOOL * stop) {
[imageData appendBytes:bytes length:byteRange.length];
CGImageSourceUpdate(imageSource, imageData, NO);
}
error:error]) {
return NO;
}
CGImageSourceUpdate(imageSource, (__bridge CFDataRef)imageData, YES);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (!imageRef) {
*error = ... some error ...;
return NO;
}
custom_defer(^{
CFRelease(imageRef);
});
UIImage *image = [UIImage imageWithCGImage:imageRef];
if (!image) {
*error = ... some error ...;
return NO;
}
self.image = image;
return YES;
}
- (BOOL)readJSONFromUnzipper:(NOZUnzipper *)unzipper withRecord:(NOZCentralDirectoryRecord *)record error:(out NSError **)error
{
NSData *jsonData = [unzipper readDataFromRecord:record
progressBlock:NULL
error:error];
if (!jsonData) {
return NO;
}
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData
options:0
error:error];
if (!jsonObject) {
return NO;
}
self.json = jsonObject;
return YES;
}
- (BOOL)extractFileFromUnzipper:(NOZUnzipper *)unzipper withRecord:(NOZCentralDirectoryRecord *)record error:(out NSError **)error
{
if (record.isZeroLength || record.isMacOSXAttribute || record.isMacOSXDSStore) {
return YES;
}
return [self saveRecord:record toDirectory:someDestinationRootDirectory options:NOZUnzipperSaveRecordOptionsNone progressBlock:NULL error:error];
}
可扩展性 - 模块化压缩编码器/解码器
NOZEncoder
和 NOZDecoder
ZipUtilities 提供了一种模块化方法来压缩和解压 zip 存档的单个条目。zip 文件格式指定了在存档中任何给定条目所使用的压缩方法。zip 存档和归档程序中两种最常用的算法是 Deflate 和 Raw。鉴于它们是最常见的两种,ZipUtilities 内置了这两种算法,其中 Deflate 来自于 iOS 和 OS X 上的 zlib 库,Raw 则是未经修改的字节(无压缩)。通过结合使用 NOZCompressionLevel
和 NOZCompressionMethod
,您可以优化压缩多个文件条目的方式。例如:您可能有一个要存档的文本文件、图片和二进制文件。您可以添加文本文件使用 NOZCompressionLevelDefault
和 NOZCompressionMethodDeflate
,图片使用 NOZCompressionMethodNone
,二进制文件使用 NOZCompressionLevelVeryLow
和 NOZCompressionMethodDeflate
(即快速)。
由于 ZipUtilities 对压缩方法采取模块化方法,因此添加对额外的压缩编码器和解码器的支持非常简单。您只需实现 NOZEncoder
和 NOZDecoder
协议,并将它们注册为相关的 NOZCompressionMethod
(包括 sharedInstance
)和 NOZCompressionLibrary
。例如,您可能希望添加 BZIP2 支持:只需实现 MyBZIP2Encoder<NOZEncoder>
和 MyBZIP2Decoder<NOZDecoder>
,然后在开始使用 NOZCompressionLibrary
压缩或解压之前更新 ZipUtilities 中已知 NOZCompressionMethodBZip2
编码器和解码器。
示例
[[NOZCompressionLibrary sharedInstance] setEncoder:[[MyBZIP2Encoder alloc] init] forMethod:NOZCompressionMethodBZip2];
[[NOZCompressionLibrary sharedInstance] setDecoder:[[MyBZIP2Decoder alloc] init] forMethod:NOZCompressionMethodBZip2];
Apple 压缩库作为额外补充
NOZXAppleCompressionCoder
作为如何构造自己的编码器的示例。支持 libcompression 提供的所有算法,包括 ZIP 存档格式中指定为已知压缩方法的 LZMA。
注册 Apple 压缩库编码器的示例
- (BOOL)updateRegisteredCodersWithAppleCompressionCoders
{
if (![NOZXAppleCompressionCoder isSupported]) {
// Apple's Compression Lib is only supported on iOS 9+ and Mac OS X 10.11+
return NO;
}
NOZCompressionLibrary *library = [NOZCompressionLibrary sharedInstance];
// DEFLATE
// Replace existing default DEFLATE coders with Apple Compression variant
[library setEncoder:[NOZXAppleCompressionCoder encoderWithAlgorithm:COMPRESSION_ZLIB]
forMethod:NOZCompressionMethodDeflate];
[library setDecoder:[NOZXAppleCompressionCoder decoderWithAlgorithm:COMPRESSION_ZLIB]
forMethod:NOZCompressionMethodDeflate];
// LZMA
[library setEncoder:[NOZXAppleCompressionCoder encoderWithAlgorithm:COMPRESSION_LZMA]
forMethod:NOZCompressionMethodLZMA];
[library setDecoder:[NOZXAppleCompressionCoder decoderWithAlgorithm:COMPRESSION_LZMA]
forMethod:NOZCompressionMethodLZMA];
// The following coders are not defined as known ZIP compression methods,
// however that doesn't mean we can't extend the enumeration of ZIP methods
// to have custom compression methods.
//
// Since compression_algorithm enum values are all beyond the defined ZIP methods values
// and are all within 16 bits, we can just use the values directly.
// Puts the burden on the decoder to know that these non-ZIP compression methods
// are for their respective algorithm.
// LZ4
[library setEncoder:[NOZXAppleCompressionCoder encoderWithAlgorithm:COMPRESSION_LZ4]
forMethod:(NOZCompressionMethod)COMPRESSION_LZ4];
[library setDecoder:[NOZXAppleCompressionCoder decoderWithAlgorithm:COMPRESSION_LZ4]
forMethod:(NOZCompressionMethod)COMPRESSION_LZ4];
// Apple LZFSE - the new hotness for compression from Apple
[library setEncoder:[NOZXAppleCompressionCoder encoderWithAlgorithm:COMPRESSION_LZFSE]
forMethod:(NOZCompressionMethod)COMPRESSION_LZFSE];
[library setDecoder:[NOZXAppleCompressionCoder decoderWithAlgorithm:COMPRESSION_LZFSE]
forMethod:(NOZCompressionMethod)COMPRESSION_LZFSE];
return YES;
}
待定
最终
- 64 位文件支持(大文件存档)
- 添加密码支持
- 这是低优先级,因为加密 zip 文件内容并非保护数据的安全方式
- 在存档中添加对每个条目的“额外信息”的支持
- 扩展进度信息
- 状态转换
- 正在存档/解档的文件是什么
- 每个文件的进度
依赖项
ZStandard
ZipUtilities 包含 Facebook 的 ZStandard (zstd)压缩库。www.zstd.net
Brotli
ZipUtilities 包含 Google 的 Brotli (br)压缩库。Brotli Github
测试文件用于压缩/解压缩
作为 Aesop 的寓言单元测试的一部分,使用《星球大战:原力觉醒》预告片和《疯劫》进行单元测试。《伊索寓言》和《疯劫》已经不再受版权保护,可以被自由分发,包括不寻常地用作单元测试zip存档和解压缩文件。《星球大战:原力觉醒》预告片可以自由分发,并提供了一个有助于测试的大型文件。