Tinkering with CompressionLib (Part 3)

18 Jul

Continuing my series on Tinkering with CompressionLib; if you haven’t read my previous posts you can check them out here – Part 1 and Part 2.

Last time when working with CompressionLib I introduced a category on NSData to allow for easy compression / decompression. I initially wrote this in Objective-C.

While working with C libraries in Swift is fairly easy, it’s still nowhere near as easy as dealing with them in Objective-C; largely since you can mix C and Objective-C in the same source file and the fact that C couldn’t hardly care less about type safety.

This time we’re going to take a look at working with CompressionLib from Swift.

// alloc some memory for the stream
let streamPtr = UnsafeMutablePointer<compression_stream>.alloc(1)
var stream    = streamPtr.memory

// the stream’s status
var status : compression_status

// we want to compress so we use the ENCODE option
let op = COMPRESSION_STREAM_ENCODE

// COMPRESSION_STREAM_FINALIZE is used to indicate that no further input will be added. Since we have the entire input data we can finalize right away
// compression_stream_process() expects flags to be an Int32, so we need to convert our flag to that type
let flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)

// we want to use the super awesome LZFSE algorithm
let algorithm = COMPRESSION_LZFSE

// init the stream for compression
status = compression_stream_init(&stream, op, algorithm)
guard status != COMPRESSION_STATUS_ERROR else {
    // FIXME: Shame on you for not handling this error properly
    return
}

// setup the stream's source
let inputData   = // get some data
stream.src_ptr  = UnsafePointer<UInt8>(inputData.bytes)
stream.src_size = inputData.length

// setup the stream's output buffer
// we use a temporary buffer to store the data as it's compressed
let dstBufferSize : size_t = 4096
let dstBufferPtr  = UnsafeMutablePointer<UInt8>.alloc(dstBufferSize)
stream.dst_ptr    = dstBufferPtr
stream.dst_size   = dstBufferSize
// and we store the output in a mutable data object
let outputData = NSMutableData()


repeat {
    // try to compress some data
    status = compression_stream_process(&stream, flags)
    
    switch status.rawValue {
    case COMPRESSION_STATUS_OK.rawValue:
        // Going to call _process at least once more
        if stream.dst_size == 0 {
            // Output buffer full...
            
            // Write out to mutableData
            outputData.appendBytes(dstBufferPtr, length: dstBufferSize)
            
            // Re-use dstBuffer
            stream.dst_ptr = dstBufferPtr
            stream.dst_size = dstBufferSize
        }
        
    case COMPRESSION_STATUS_END.rawValue:
        // We are done, just write out the output buffer if there's anything in it
        if stream.dst_ptr > dstBufferPtr {
            outputData.appendBytes(dstBufferPtr, length: stream.dst_ptr - dstBufferPtr)
        }
        
    case COMPRESSION_STATUS_ERROR.rawValue:
        // FIXME: Eat your vegetables; handle your errors.
        return
        
    default:
        break
    }
    
} while status == COMPRESSION_STATUS_OK

// We’re done with the stream - so free it
compression_stream_destroy(&stream)

// Finally we get our compressed data
let compressedData = outputData.copy()

And just like before, when you want to decompress the data you just need to change two variables.

// set the stream operation to decode instead of encode
let op = COMPRESSION_STREAM_ENCODE

// set the flags to 0, we don't need any flags here
let flags = 0

Correction, Stephen Canon points out that you actually can import CompressionLib directly; no need for a bridging header or module work around.

Just use import Compression

Thanks Stephen!

Now there’s one small catch. To be able to work with CompressionLib from Swift you have two options.

The simple way:

#import <compression.h> in an Objective-C bridging header.

The module way:

You can use a module to provide an interface between Swift and the C library. To do this first create a folder in your Project directory. You can name it whatever you like; I named it “CompressionLibModule”.

Next create a text file in the new directory and name it “module.map”

Add the following to the text file

module libcompression [system] {
    header "/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/compression.h"
    export *
}

This creates a module named “libcompression” that contains the compression.h header.

There’s one last step. In the Build Settings for your Project (or Target) under Swift Compiler - Search Paths you need to add the path to your module’s directory to the Import Paths

$(SRCROOT)/CompressionLibModule/

Now in any Swift file you want to use CompressionLib just import libcompression

The module option is quite a bit more work, and I’m sure this will get easier in the future.

So to wrap things up – I’ve rewritten the compression extension in Swift (don’t worry the Objective-C version is still available if you want it).

Here are the Swift versions of the NSData category’s public methods

// Returns a NSData object created by compressing the receiver using the given compression algorithm.
func compressedDataUsingCompression(compression: Compression) -> NSData?

// Returns a NSData object by uncompressing the receiver using the given compression algorithm.
func uncompressedDataUsingCompression(compression: Compression) -> NSData?

To compress some data you just call:

let compressedData = someUncompressedData.compressedDataUsingCompression(Compression.LZFSE)

And to uncompress some data:

let uncompressedData = someCompressedData.uncompressedDataUsingCompression(Compression.LZFSE)

Here’s the repo