Last active
August 5, 2022 20:18
-
-
Save atierian/c431ef8abdc656b34b52341508e80742 to your computer and use it in GitHub Desktop.
Compressing and Decompressing Data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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