Compressing NSData objects using zlib
This entirely iPhone friendly tutorial highlights how easy it is to compress our familiar NSData objects using zlib – the most commonly used compression library on the planet. This is the same library that compresses the PNGs and PDFs, ZIPs and GZIPed files that make up your daily life.
The zlib c library, which is bundled with all Mac OS X and iPhone 2.0+ devices offers an extensive but non obtrusive API for compressing data. The most basic functions are the stream API which serve nicely when, for instance, dealing with a large files, or matching a specific compression requirement. The method presented here is one which is utility provided by the atop the streaming API. Readers familiar with python will recognize this if ever you’ve had to handle data with the zlib module.
First, Allocate a data structure of type const ByteF containing the bytes in our uncompressed NSData structure.
const Bytef *inBytes = (const Bytef *)[uncompressedData bytes];
Allocate enough data to start compressing data. The zlib manual recommends allocating 1% more than the length of your uncompressed data, plus twelve bytes. This gives zlib enough room to start compressing data, resizing the output buffer as needed.
uLongf outLength = ([uncompressedData length] * 1.1) + 12; Bytef *outBytes = (Bytef *)malloc(outLength);
Now simply call compress:
int z_result = compress(outBytes, &outLength, inBytes, [uncompressedData length]);
compress will return one of three constants. To handle errors, check for the return value. Depending on your situation, you could return an NSError. In this case, I return an empty NSData and check for return value length.
NSData *ret; if (z_result == Z_OK) { ret = [NSData dataWithBytesNoCopy:outBytes length:outLength freeWhenDone:YES]; } else { ret = [NSData data]; switch (z_result) { case Z_MEM_ERROR: printf("compressData got Z_MEM_ERROR out of memory. :("); case Z_BUF_ERROR: printf("compressData got Z_BUF_ERROR output buffer wasn't larege enough :("); default: break; } } }
compressData function
NSData* compressData(NSData *uncompressedData) { if ([uncompressedData length] == 0) return uncompressedData; const Bytef *inBytes = (const Bytef *)[uncompressedData bytes]; uLongf outLength = ([uncompressedData length] * 1.1) + 12; Bytef *outBytes = (Bytef *)malloc(outLength); int z_result = compress(outBytes, &outLength, inBytes, [uncompressedData length]); NSData *ret; if (z_result == Z_OK) { ret = [NSData dataWithBytesNoCopy:outBytes length:outLength freeWhenDone:YES]; } else { ret = [NSData data]; switch (z_result) { case Z_MEM_ERROR: printf("compressData got Z_MEM_ERROR out of memory. :("); case Z_BUF_ERROR: printf("compressData got Z_BUF_ERROR output buffer wasn't larege enough :("); default: break; } } return ret; }
compressData that returns an NSError.
#define ZLIB_COMPRESS_DOMAIN @"zlib_compress_domain" NSError* compressData2(NSData *inData, NSData **outData) { if ([inData length] == 0) { *outData = inData; return nil; } const Bytef *inBytes = (const Bytef *)[inData bytes]; uLongf outLength = ([inData length] * 1.1) + 12; Bytef *outBytes = (Bytef *)malloc(outLength); int z_result = compress(outBytes, &outLength, inBytes, [inData length]); if (z_result == Z_OK) { *outData = [NSData dataWithBytesNoCopy:outBytes length:outLength freeWhenDone:YES]; free(outBytes); return nil; } return [NSError errorWithDomain:ZLIB_COMPRESS_DOMAIN code:z_result userInfo:[NSDictionary dictionary]]; }
NSData category
@interface NSData (CompressionAdditions) - (NSData *)compress; - (NSData *)compress:(NSError **)errorOut; @end @implementation NSData (CompressionAdditions) - (NSData *)compress { return compressData(self); } - (NSData *)compress:(NSError **)errorOut { NSData *ret; *errorOut = compressData2(self, &ret); return ret; } @end