ZipUtilities 1.11.2

ZipUtilities 1.11.2

测试已测试
语言语言 Obj-CObjective C
许可证 MIT
发布最新版本2018年9月

Nolan O'Brien维护。



ZipUtilities

MIT License CocoaPods compatible Carthage compatible

Targets

Build Status

简介

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 属性或通过代理回调来观察。
  • NOZCompressDelegateNOZCompressOperation 的代理。它提供进度和完成的回调。
  • 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 是封装解压缩操作的 whathow 的对象。
  • NOZDecompressOperation 是执行解压缩的 NSOperation 子类对象。作为 NSOperation,消费者可以利用取消、优先级和依赖关系。操作还会提供进度,可以通过观察 KVO 上的 progress 属性或通过代理回调来观察。
  • NOZDecompressDelegateNOZDecompressOperation 的代理。它提供进度、覆盖输出文件和完成的回调。
  • 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];
}

可扩展性 - 模块化压缩编码器/解码器

NOZEncoderNOZDecoder

ZipUtilities 提供了一种模块化方法来压缩和解压 zip 存档的单个条目。zip 文件格式指定了在存档中任何给定条目所使用的压缩方法。zip 存档和归档程序中两种最常用的算法是 DeflateRaw。鉴于它们是最常见的两种,ZipUtilities 内置了这两种算法,其中 Deflate 来自于 iOS 和 OS X 上的 zlib 库,Raw 则是未经修改的字节(无压缩)。通过结合使用 NOZCompressionLevelNOZCompressionMethod,您可以优化压缩多个文件条目的方式。例如:您可能有一个要存档的文本文件、图片和二进制文件。您可以添加文本文件使用 NOZCompressionLevelDefaultNOZCompressionMethodDeflate,图片使用 NOZCompressionMethodNone,二进制文件使用 NOZCompressionLevelVeryLowNOZCompressionMethodDeflate(即快速)。

由于 ZipUtilities 对压缩方法采取模块化方法,因此添加对额外的压缩编码器和解码器的支持非常简单。您只需实现 NOZEncoderNOZDecoder 协议,并将它们注册为相关的 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存档和解压缩文件。《星球大战:原力觉醒》预告片可以自由分发,并提供了一个有助于测试的大型文件。

MANIAC.EXE