Vice

Sometimes you need to compress data between your backend and frontend, and simply using HTTP gzip compression isn’t an option for whatever reason. Or maybe you want to compress data for storage locally on your device, and decompress it when loading. Or whatever you reason may be: I hope I can help here.

Apple’s been shipping Compression.framework with iOS since iOS9. This has been mostly ignored, because they also ship zlib which most people use instead. But zlib is trickier to use, so most people import some kind of Cocoapod/Carthage framework to wrap it. However, Apple recommends keeping your framework count as low as possible to not impact launch times, which should give you pause every time you consider adding one. Instead, I’d like to show how instead you can easily use Compression.framework with pure Swift.

Swift compression / decompression

import Foundation
import Compression

extension Data {
	func compression(isEncode: Bool, algorithm: compression_algorithm) -> Data? {
		return withUnsafeBytes { (urbp: UnsafeRawBufferPointer) in
			let ubp: UnsafeBufferPointer<UInt8> = urbp.bindMemory(to: UInt8.self)
			let up: UnsafePointer<UInt8> = ubp.baseAddress!
			let destCapacity = 1_000_000 // Note: Increase/decrease as you require.
			let destBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity:
				destCapacity)
			defer { destBuffer.deallocate() }
			let destBytes = isEncode ?
				compression_encode_buffer(destBuffer, destCapacity, up, count, nil,
					algorithm) :
				compression_decode_buffer(destBuffer, destCapacity, up, count, nil,
					algorithm)
			guard destBytes != 0 else { return nil } // Error, or not enough size.
			return Data(bytes: destBuffer, count: destBytes)
		}
	}
	var deflated: Data? {
		// ZLIB has no headers, so it is effectively DEFLATE.
		return compression(isEncode: true, algorithm: COMPRESSION_ZLIB)
	}
	var inflated: Data? {
		return compression(isEncode: false, algorithm: COMPRESSION_ZLIB)
	}
}

Note that my wrappers here only support the one-shot [de]compression methods which require you to know upfront a maximum size the destination will be. This might not suit you, if so there are methods for ‘stream de/compression’ in Compression.framework which you’ll have to modify my code to use, however my code should at least serve as a starting point for you.

Algorithm options

  • LZ4 - Fast, not as compressed.
  • ZLIB aka DEFLATE - Good balance of speed/compression. NOTE!!! This isn’t like typical zlib compression, this is a raw DEFLATE stream, keep that in mind when implementing it on your backend!
  • LZFSE - Compresses a bit better and a bit faster than ZLIB, but it’s Apple-proprietary so might be tricky to find a library for in your backend’s language.
  • LZMA - Best compression, slowest.

Go compression / decompression

If your backend uses Go (as mine typically do), here’s a snippet to [de]compress interoperably with the above Swift code:

import "bytes"
import "compress/flate"

func deflate(source []byte) ([]byte, error) {
	var buffer bytes.Buffer
	w, err := flate.NewWriter(&buffer, 5)
	if err != nil {
		return nil, err
	}
	_, err = w.Write(source)
	w.Close()
	return buffer.Bytes(), err
}

func inflate(source []byte) ([]byte, error) {
	reader := flate.NewReader(bytes.NewBuffer(source))
	var buffer bytes.Buffer
	_, err := io.Copy(&buffer, reader)
	reader.Close()
	return buffer.Bytes(), err
}

Thanks for reading, I hope this helps someone, and have a great week!

MIT license applies. No warranties.

Photo by Clark Young on Unsplash

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

iOS Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]
github.com/chrishulbert
linkedin



 Subscribe via RSS