Skip to content

Instantly share code, notes, and snippets.

@atierian
Last active August 5, 2022 20:18
Show Gist options
  • Save atierian/c431ef8abdc656b34b52341508e80742 to your computer and use it in GitHub Desktop.
Save atierian/c431ef8abdc656b34b52341508e80742 to your computer and use it in GitHub Desktop.
Compressing and Decompressing Data
import Foundation
import Compression
import XCTest
extension Encodable {
/// Returns a string object describing the encodable object's byte size using `ByteCountFormatter`. Or a description of the `DecodingError` generated during the decoding process
var size: String {
do {
return try JSONEncoder().encode(self).size
} catch {
return String(describing: error)
}
}
}
extension Data {
/// Returns a string object describing the data's byte size using `ByteCountFormatter`.
var size: String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useAll]
return formatter.string(fromByteCount: Int64(count))
}
}
extension NSData.CompressionAlgorithm: CustomDebugStringConvertible, CaseIterable {
public static var allCases: [NSData.CompressionAlgorithm] {
[.lz4, .lzfse, .lzma, .zlib]
}
public var debugDescription: String {
switch self {
case .lz4: return "lz4"
case .lzfse: return "lzfse"
case .lzma: return "lzma"
case .zlib: return "zlib"
@unknown default: return "unknown"
}
}
}
extension Encodable {
/// Encodes an `Encodable` object and a data object by compressing data object’s bytes.
///
/// - `.lz4` recommended for fast compression.
/// - `.lzfse` recommended for use on Apple platforms.
/// - `.lzma` recommended for high-compression ratio.
/// - `.zlib` recommended for cross-platform compression.
/// ~~~
/// // usage
/// let myCompressedData = try myEncodableObject.compressed()
/// let myCompressedData = try myEncodableObject.compressed(using: .lz4)
/// ~~~
/// - Parameter algorithm: An algorithm used to compress the data. For a list of available algorithms, see `NSData.CompressionAlgorithm`. (default parameter: `.lzfse`)
/// - Throws: Error thrown during the encoding or compression process
/// - Returns:An Data instance that contains the compressed buffer data.
/// - Important: You must use the same compression algorithm that was used to compress this data.
func encodedAndCompressed(using compression: NSData.CompressionAlgorithm = .lzfse) throws -> Data {
let encodedData = try JSONEncoder().encode(self)
return try (encodedData as NSData).compressed(using: compression) as Data
}
}
extension Data {
/// Returns a new data object by decompressing data object’s bytes.
///
/// - `.lz4` recommended for fast compression.
/// - `.lzfse` recommended for use on Apple platforms.
/// - `.lzma` recommended for high-compression ratio.
/// - `.zlib` recommended for cross-platform compression.
/// ~~~
/// // usage
/// let myDecompressedData = try myCompressedData.decompressed()
/// let myDecompressedData = try myCompressedData.decompressed(using: .lz4)
/// ~~~
/// - Parameter algorithm: An algorithm used to decompress the data. For a list of available algorithms, see `NSData.CompressionAlgorithm`. (default parameter: `.lzfse`)
/// - Throws: Error thrown during decompression process
/// - Returns:An Data instance that contains the decompressed buffer data.
/// - Important: You must use the same compression algorithm that was used to compress this data.
func decompressed(using algorithm: NSData.CompressionAlgorithm = .lzfse) throws -> Data {
try (self as NSData).decompressed(using: algorithm) as Data
}
/// Decompress data in place - **MUTATING**
///
/// - `.lz4` recommended for fast compression.
/// - `.lzfse` recommended for use on Apple platforms.
/// - `.lzma` recommended for high-compression ratio.
/// - `.zlib` recommended for cross-platform compression.
/// ~~~
/// // usage
/// try myCompressedData.decompress()
/// try myCompressedData.decompress(using: .lz4)
/// ~~~
/// - Parameter algorithm: An algorithm used to decompress the data. For a list of available algorithms, see `NSData.CompressionAlgorithm`. (default parameter: `.lzfse`)
/// - Throws: Error thrown during decompression process
/// - Important: You must use the same compression algorithm that was used to compress this data.
mutating func decompress(using algorithm: NSData.CompressionAlgorithm = .lzfse) throws {
self = try decompressed(using: algorithm)
}
/// Returns a new data object by compressing data object’s bytes.
///
/// - `.lz4` recommended for fast compression.
/// - `.lzfse` recommended for use on Apple platforms.
/// - `.lzma` recommended for high-compression ratio.
/// - `.zlib` recommended for cross-platform compression.
/// ~~~
/// // usage
/// let myCompressedData = try myData.compressed()
/// let myCompressedData = try myData.compressed(using: .lz4)
/// ~~~
/// - Parameter algorithm: An algorithm used to compress the data. For a list of available algorithms, see `NSData.CompressionAlgorithm`. (default parameter: `.lzfse`)
/// - Throws: Error thrown during compression process
/// - Returns: Data instance that contains the compressed buffer data.
/// - Important: You must use the same compression algorithm that was used to compress this data.
func compressed(using algorithm: NSData.CompressionAlgorithm = .lzfse) throws -> Data {
try (self as NSData).compressed(using: algorithm) as Data
}
/// Compress data in place - **MUTATING**
///
/// - `.lz4` recommended for fast compression.
/// - `.lzfse` recommended for use on Apple platforms.
/// - `.lzma` recommended for high-compression ratio.
/// - `.zlib` recommended for cross-platform compression.
/// ~~~
/// // usage
/// try myData..compress()
/// try myData.compress(using: .lz4)
/// ~~~
/// - Parameter algorithm: An algorithm used to compress the data. For a list of available algorithms, see `NSData.CompressionAlgorithm`. (default parameter: `.lzfse`)
/// - Throws: Error thrown during compression process
mutating func compress(using algorithm: NSData.CompressionAlgorithm = .lzfse) throws {
self = try compressed(using: algorithm)
}
}
// MARK: Tests
class CompressionTests: XCTestCase {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
func testCompressedAndDecompressed() throws {
let foo = Foo()
let data = try encoder.encode(foo)
let compressedData = try data.compressed(using: .lz4)
XCTAssertLessThan(compressedData.count, data.count)
let decompressedData = try compressedData.decompressed(using: .lz4)
let newFoo = try decoder.decode(Foo.self, from: decompressedData)
XCTAssertEqual(foo, newFoo)
}
func testCompressAndDecompress() throws {
let foo = Foo()
var data = try encoder.encode(foo)
try data.compress()
try data.decompress()
let newFoo = try decoder.decode(Foo.self, from: data)
XCTAssertEqual(foo, newFoo)
}
}
CompressionTests.defaultTestSuite.run()
// MARK: See Compression Results
struct Foo: Codable, Equatable, CustomDebugStringConvertible {
var bar = "hello"
var baz = "world"
var array = (1...1000).map { $0 }
var array2 = (1...1000).map { $0 }
var debugDescription: String {
"""
bar = \(bar)
baz = \(baz)
array.count = \(array.count)
array2.count = \(array2.count)
"""
}
}
func testCompressionAndDecompression(with compressionAlgo: NSData.CompressionAlgorithm) throws {
let foo = Foo()
print("[START: \(compressionAlgo)]")
print("----- ORIGINAL FOO -----")
print(foo)
print("\nfooSize -", foo.size)
let compressedFooData = try foo.encodedAndCompressed(using: compressionAlgo)
print("\nencoding & compressing using algorithm: \(compressionAlgo) ...")
print("Compressed Foo Data Size -", compressedFooData.size)
print("\nSending compressed data with size: \(compressedFooData.size), \noriginal size was: \(foo.size) ...")
print("\nReceiving compressed data...")
let decompressedFooData = try compressedFooData.decompressed(using: compressionAlgo)
print("\ndecompressing...")
print("Decompressed Foo Data Size -", decompressedFooData.size)
let decompressedFoo = try JSONDecoder().decode(Foo.self, from: decompressedFooData)
print("\ndecoding decompressed data with algorithm: \(compressionAlgo)...\n")
print("----- DECOMPRESSED / DECODED FOO -----")
print(decompressedFoo)
print("---------------------------------------", "\n\n\n")
}
NSData.CompressionAlgorithm.allCases.forEach {
try! testCompressionAndDecompression(with: $0)
}
// MARK: USAGES
// Encode object to data, compress data, decompress data, decode data to object
do {
let object = Foo()
let data = try JSONEncoder().encode(object)
let compressedData = try data.compressed()
let decompressedData = try compressedData.decompressed()
let receivedObject = try JSONDecoder().decode(Foo.self, from: decompressedData)
_ = receivedObject
} catch { }
// Encode and compress data, decompress data, decode data to object
do {
let object = Foo()
let compressedData = try object.encodedAndCompressed(using: .lzma)
_ = compressedData
// ...
} catch { }
// Using mutating methods
do {
let object = Foo()
var data = try JSONEncoder().encode(object)
try data.compress()
try data.decompress()
let receivedObject = try JSONDecoder().decode(Foo.self, from: data)
_ = receivedObject
} catch { }
// Convert throwing method to Result
do {
let object = Foo()
if case let .success(compressedData) = Result(catching: { try object.encodedAndCompressed() }),
case let .success(decompressedData) = Result(catching: { try compressedData.decompressed() }),
let receivedObject = try? JSONDecoder().decode(Foo.self, from: decompressedData) {
_ = receivedObject
// do something with receivedObject4
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment