Compress a string, write it to the file system, and decompress the same file using buffer compression.
Framework
- Compression
Overview
In this article, you'll learn how to compress and decompress a String
instance.
Create the Source Data
Typically, your app would dynamically generate the source data that it compresses, but for this example, the source data is a hard-coded string:
let sourceString = """
Lorem ipsum dolor sit amet consectetur adipiscing elit mi
nibh ornare proin blandit diam ridiculus, faucibus mus
dui eu vehicula nam donec dictumst sed vivamus bibendum
aliquet efficitur. Felis imperdiet sodales dictum morbi
vivamus augue dis duis aliquet velit ullamcorper porttitor,
lobortis dapibus hac purus aliquam natoque iaculis blandit
montes nunc pretium.
"""
var sourceBuffer = Array(sourceString.utf8)
On return, source
is an array of UInt8
values containing the UTF-8 representation of the source string.
Create the Destination Buffer
Create an Unsafe
structure and allocate it with a capacity of the source string's count to receive the encoded data:
let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: sourceString.count)
Select a Compression Algorithm
If speed and compression ratio are important, use COMPRESSION
:
let algorithm = COMPRESSION_LZFSE
If you require interoperability with non-Apple devices, use COMPRESSION
instead. For more information about other compression algorithms, see compression
.
Compress the Data
The compression
function compresses the data, writing the result to the destination buffer and returning the size of the encoded data:
let compressedSize = compression_encode_buffer(destinationBuffer, sourceString.count,
&sourceBuffer, sourceString.count,
nil,
algorithm)
When working with small files, the compression may fail and compression
returns 0
:
if compressedSize == 0 {
fatalError("Encoding failed.")
}
You may elect to handle this situation differently, for example, by displaying a warning to the user that the compression failed.
Write the Encoded Data to a File
The following code writes the encoded data to a file in the the app's temporary directory on macOS:
let encodedFileName = "stringEncoded.LZFSE"
let tempDirURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
guard
let encodedFileURL = tempDirURL.appendingPathComponent(encodedFileName) else {
return
}
FileManager.default.createFile(atPath: encodedFileURL.path,
contents: nil,
attributes: nil)
guard let destinationFileHandle = try? FileHandle(forWritingTo: encodedFileURL) else {
print("destinationFileHandle fail.")
return
}
let encodedData = NSData(bytesNoCopy: destinationBuffer,
length: compressedSize)
destinationFileHandle.write(encodedData as Data)
destinationFileHandle.closeFile()
Read the Encoded Data from a File
To read the encoded file, create a file handle for reading for the encoded file's URL:
guard
let encodedFileHandle = try? FileHandle(forReadingFrom: encodedFileURL) else {
print("encodedFileHandle fail.")
return
}
Use the file handle to read the entire encoded data and populate encoded
:
let encodedSourceData = encodedFileHandle.readDataToEndOfFile()
Decompress the Data
Allocate memory to contain the decoded data. Typically, the encoded payload would be part of a larger structure containing additional metadata such as the uncompressed size, and you'd use that to define the buffer capacity. However, for this example, allocate 8 MB:
let decodedCapacity = 8_000_000
let decodedDestinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: decodedCapacity)
Use compression
to decode the raw bytes of the encoded source data, writing the result to decoded
. You can create a string from the destination buffer using the init(c
initializer:
let decodedString = encodedSourceData.withUnsafeBytes {
(encodedSourceBuffer: UnsafePointer<UInt8>) -> String in
let decodedCharCount = compression_decode_buffer(decodedDestinationBuffer,
decodedCapacity,
encodedSourceBuffer,
encodedSourceData.count,
nil,
algorithm)
if decodedCharCount == 0 {
fatalError("Decoding failed.")
}
return String(cString: decodedDestinationBuffer)
}
The compression
function returns the size of the decoded data. If the decompression fails, the size returned is zero. This may indicate that the memory allocated to the destination buffer is insufficient and you should switch to the stream API or retry with a larger buffer.