This repository has been archived by the owner on Jun 12, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Data+Compressor.swift
216 lines (182 loc) · 8.22 KB
/
Data+Compressor.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//
// Data+Compressor.swift
// SwiftCompressor
//
// Created by Piotr Sochalewski on 22.02.2016.
// Copyright © Droids on Roids. All rights reserved.
//
import Foundation
import Compression
/**
Compression algorithm
- `.lz4`: Fast compression
- `.zlib`: Balances between speed and compression
- `.lzma`: High compression
- `.lzfse`: Apple-specific high performance compression
*/
@available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
public enum CompressionAlgorithm {
/**
LZ4 is an extremely high-performance compressor.
*/
case lz4
/**
ZLIB encoder at level 5 only. This compression level provides a good balance between compression speed and compression ratio. The ZLIB decoder supports decoding data compressed with any compression level.
*/
case zlib
/**
LZMA encoder at level 6 only. This is the default compression level for open source LZMA, and provides excellent compression. The LZMA decoder supports decoding data compressed with any compression level.
*/
case lzma
/**
Apple’s proprietary compression algorithm. LZFSE is a new algorithm, matching the compression ratio of ZLIB level 5, but with much higher energy efficiency and speed (between 2x and 3x) for both encode and decode operations.
LZFSE is only present in iOS and OS X, so it can’t be used when the compressed payload has to be shared to other platforms (Linux, Windows). In all other cases, LZFSE is recommended as a replacement for ZLIB.
*/
case lzfse
}
@available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
public enum CompressionError: Error {
/**
The error received when trying to compress/decompress empty data (when length equals zero).
*/
case emptyData
/**
The error received when `compression_stream_init` failed. It also fails when trying to decompress `Data` compressed with different compression algorithm or uncompressed raw data.
*/
case initError
/**
The error received when `compression_stream_process` failed.
*/
case processError
}
@available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
extension Data {
/**
Returns a `Data` object created by compressing the receiver using the LZFSE algorithm.
- returns: A `Data` object created by encoding the receiver's contents using the LZFSE algorithm.
*/
public func compress() throws -> Data? {
return try compress(algorithm: .lzfse, bufferSize: 4096)
}
/**
Returns a `Data` object created by compressing the receiver using the given compression algorithm.
- parameter algorithm: one of four compression algorithms to use during compression
- returns: A `Data` object created by encoding the receiver's contents using the provided compression algorithm.
*/
public func compress(algorithm compression: CompressionAlgorithm) throws -> Data? {
return try compress(algorithm: compression, bufferSize: 4096)
}
/**
Returns a Data object created by compressing the receiver using the given compression algorithm.
- parameter algorithm: one of four compression algorithms to use during compression
- parameter bufferSize: the size of buffer in bytes to use during compression
- returns: A `Data` object created by encoding the receiver's contents using the provided compression algorithm.
*/
public func compress(algorithm compression: CompressionAlgorithm, bufferSize: size_t) throws -> Data? {
return try compress(compression, operation: .compression, bufferSize: bufferSize)
}
/**
Returns a `Data` object by uncompressing the receiver using the LZFSE algorithm.
- returns: A `Data` object created by decoding the receiver's contents using the LZFSE algorithm.
*/
public func decompress() throws -> Data? {
return try decompress(algorithm: .lzfse, bufferSize: 4096)
}
/**
Returns a `Data` object by uncompressing the receiver using the given compression algorithm.
- parameter algorithm: one of four compression algorithms to use during decompression
- returns: A `Data` object created by decoding the receiver's contents using the provided compression algorithm.
*/
public func decompress(algorithm compression: CompressionAlgorithm) throws -> Data? {
return try decompress(algorithm: compression, bufferSize: 4096)
}
/**
Returns a `Data` object by uncompressing the receiver using the given compression algorithm.
- parameter algorithm: one of four compression algorithms to use during decompression
- parameter bufferSize: the size of buffer in bytes to use during decompression
- returns: A `Data` object created by decoding the receiver's contents using the provided compression algorithm.
*/
public func decompress(algorithm compression: CompressionAlgorithm, bufferSize: size_t) throws -> Data? {
return try compress(compression, operation: .decompression, bufferSize: bufferSize)
}
fileprivate enum Operation {
case compression
case decompression
}
fileprivate func compress(_ compression: CompressionAlgorithm, operation: Operation, bufferSize: size_t) throws -> Data? {
// Throw an error when data to (de)compress is empty.
guard count > 0 else { throw CompressionError.emptyData }
// Variables
var status: compression_status
var op: compression_stream_operation
var flags: Int32
var algorithm: compression_algorithm
// Output data
let outputData = NSMutableData()
switch compression {
case .lz4:
algorithm = COMPRESSION_LZ4
case .zlib:
algorithm = COMPRESSION_ZLIB
case .lzma:
algorithm = COMPRESSION_LZMA
case .lzfse:
algorithm = COMPRESSION_LZFSE
}
// Setup stream operation and flags depending on compress/decompress operation type
switch operation {
case .compression:
op = COMPRESSION_STREAM_ENCODE
flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
case .decompression:
op = COMPRESSION_STREAM_DECODE
flags = 0
}
// Allocate memory for one object of type compression_stream
let streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
defer {
streamPointer.deallocate(capacity: 1)
}
// Stream and its buffer
var stream = streamPointer.pointee
let dstBufferPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
dstBufferPointer.deallocate(capacity: bufferSize)
}
// Create the compression_stream and throw an error if failed
status = compression_stream_init(&stream, op, algorithm)
guard status != COMPRESSION_STATUS_ERROR else {
throw CompressionError.initError
}
defer {
compression_stream_destroy(&stream)
}
// Stream setup after compression_stream_init
withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
stream.src_ptr = bytes
}
stream.src_size = count
stream.dst_ptr = dstBufferPointer
stream.dst_size = bufferSize
repeat {
status = compression_stream_process(&stream, flags)
switch status {
case COMPRESSION_STATUS_OK:
if stream.dst_size == 0 {
outputData.append(dstBufferPointer, length: bufferSize)
stream.dst_ptr = dstBufferPointer
stream.dst_size = bufferSize
}
case COMPRESSION_STATUS_END:
if stream.dst_ptr > dstBufferPointer {
outputData.append(dstBufferPointer, length: stream.dst_ptr - dstBufferPointer)
}
case COMPRESSION_STATUS_ERROR:
throw CompressionError.processError
default:
break
}
} while status == COMPRESSION_STATUS_OK
return outputData.copy() as? Data
}
}