1

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.

Darrarski
  • 3,882
  • 6
  • 37
  • 59

0 Answers0