My iOS app fetches compressed data from the backend and then decompresses it into a human-readable string. This is the function I use for decompression:
import Compression
func decompress(_ data: Data) -> String? {
guard let string = String(data: data, encoding: .utf8),
let data = Data(base64Encoded: string)
else { return nil }
let headerSize = 2
let size = compression_decode_scratch_buffer_size(COMPRESSION_ZLIB)
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
defer { buffer.deallocate() }
let result = data.subdata(in: headerSize..<data.count).withUnsafeBytes {
guard let baseAddress = $0.baseAddress else {
return ""
}
let decompressedSize = compression_decode_buffer(
buffer,
size,
baseAddress.bindMemory(to: UInt8.self, capacity: 1),
data.count - headerSize,
nil,
COMPRESSION_ZLIB
)
guard decompressedSize > 0 else {
return ""
}
return String(
decoding: Data(bytes: buffer, count: decompressedSize),
as: UTF8.self
)
} as String
guard !result.isEmpty else {
return nil
}
return result
}
I need a reverse function, that returns compressed data for a given uncompressed string:
func compress(_ string: String) -> Data?
I'm struggling with the implementation. Here is what I've tried:
import Compression
func compress(_ string: String) -> Data? {
guard let data = string.data(using: .utf8) else {
return nil
}
let bufferSize = data.count + data.count / 2 + 12
var buffer = [UInt8](repeating: 0, count: bufferSize)
let compressedSize = data.withUnsafeBytes {
guard let baseAddress = $0.baseAddress else {
return 0
}
return compression_encode_buffer(
&buffer,
buffer.count,
baseAddress.assumingMemoryBound(to: UInt8.self),
data.count,
nil,
COMPRESSION_ZLIB
)
}
guard compressedSize > 0 else {
return nil
}
return Data(bytes: buffer, count: compressedSize)
}
While it does compress the string, it's not a reverse operation to the decompress
function that I use:
let string = "Hello, World!"
let compressedData = compress(string)! // Data(74 bytes)
let decompressedString = decompress(compressedData) // nil
The decompress
function is proven to work fine given the data returned by the backend. I'm using Swift 5.8 and target iOS 16.
Thanks in advance for any hints and suggestions.
Update 1.
After investigating the problem, it occurred to me that the functions can be simplified. I can remove the base64 string conversion and operate just on raw Data
types.
I made it to work with the following implementation:
import Foundation
import Compression
func compress(_ data: Data) -> Data? {
var buffer = [UInt8](
repeating: 0,
count: compression_encode_scratch_buffer_size(COMPRESSION_ZLIB)
)
let compressedSize = data.withUnsafeBytes {
guard let baseAddress = $0.baseAddress else {
return 0
}
return compression_encode_buffer(
&buffer,
buffer.count,
baseAddress.assumingMemoryBound(to: UInt8.self),
data.count,
nil,
COMPRESSION_ZLIB
)
}
guard compressedSize > 0 else {
return nil
}
return Data(bytes: buffer, count: compressedSize)
}
func decompress(_ data: Data) -> Data? {
let headerSize = 0
var buffer = [UInt8](
repeating: 0,
count: compression_decode_scratch_buffer_size(COMPRESSION_ZLIB)
)
let subdata = data.subdata(in: headerSize..<data.count)
let decompressedSize = subdata.withUnsafeBytes { sourcePtr in
guard let baseAddress = sourcePtr.baseAddress else {
return 0
}
return compression_decode_buffer(
&buffer,
buffer.count,
baseAddress.assumingMemoryBound(to: UInt8.self),
subdata.count,
nil,
COMPRESSION_ZLIB
)
}
guard decompressedSize > 0 else {
return nil
}
return Data(bytes: buffer, count: decompressedSize)
}
It works for the following example:
let string = "Hello, World!"
let compressed = compress(string.data(using: .utf8)!)!
let decompressed = decompress(compressed)!
let decompressedString = String(data: decompressed, encoding: .utf8)
decompressedString == string // ✅ true
However, this won't work with the data the backend is returning to me. In order to make it work, I need to change the headerSize
value to 2
in the decompress
function. When I do so, I can decompress data from the backend, but my compress
function is no longer working correctly. I don't really understand what is the header size, and how to update the compress
function so it's symmetrical to my decompress
function with headerSize
equal 2
.