Skip to content

Commit 554f5d5

Browse files
committed
Second batch of performance improvements
1 parent df2d07d commit 554f5d5

File tree

12 files changed

+127
-102
lines changed

12 files changed

+127
-102
lines changed

sources/declarative/decodable/Decoder.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,35 +36,39 @@ extension CSVDecoder {
3636
/// - parameter data: The data blob representing a CSV file.
3737
open func decode<T:Decodable>(_ type: T.Type, from data: Data) throws -> T {
3838
let reader = try CSVReader(input: data, configuration: self._configuration.readerConfiguration)
39-
let source = ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)
40-
return try T(from: ShadowDecoder(source: source, codingPath: []))
39+
return try withExtendedLifetime(ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)) {
40+
try T(from: ShadowDecoder(source: .passUnretained($0), codingPath: []))
41+
}
4142
}
4243

4344
/// Returns a value of the type you specify, decoded from a CSV file (given as a `String`).
4445
/// - parameter type: The type of the value to decode from the supplied file.
4546
/// - parameter string: A Swift string representing a CSV file.
4647
open func decode<T:Decodable>(_ type: T.Type, from string: String) throws -> T {
4748
let reader = try CSVReader(input: string, configuration: self._configuration.readerConfiguration)
48-
let source = ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)
49-
return try T(from: ShadowDecoder(source: source, codingPath: []))
49+
return try withExtendedLifetime(ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)) {
50+
try T(from: ShadowDecoder(source: .passUnretained($0), codingPath: []))
51+
}
5052
}
5153

5254
/// Returns a value of the type you specify, decoded from a CSV file (being pointed by the url).
5355
/// - parameter type: The type of the value to decode from the supplied file.
5456
/// - parameter url: The URL pointing to the file to decode.
5557
open func decode<T:Decodable>(_ type: T.Type, from url: URL) throws -> T {
5658
let reader = try CSVReader(input: url, configuration: self._configuration.readerConfiguration)
57-
let source = ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)
58-
return try T(from: ShadowDecoder(source: source, codingPath: []))
59+
return try withExtendedLifetime(ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)) {
60+
try T(from: ShadowDecoder(source: .passUnretained($0), codingPath: []))
61+
}
5962
}
6063

6164
/// Returns a value of the type you specify, decoded from a CSV file (provided by the input stream).
6265
/// - parameter type: The type of the value to decode from the supplied file.
6366
/// - parameter stream: The input stream providing the raw bytes.
6467
open func decode<T:Decodable>(_ type: T.Type, from stream: InputStream) throws -> T {
6568
let reader = try CSVReader(input: stream, configuration: self._configuration.readerConfiguration)
66-
let source = ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)
67-
return try T(from: ShadowDecoder(source: source, codingPath: []))
69+
return try withExtendedLifetime(ShadowDecoder.Source(reader: reader, configuration: self._configuration, userInfo: self.userInfo)) {
70+
try T(from: ShadowDecoder(source: .passUnretained($0), codingPath: []))
71+
}
6872
}
6973
}
7074

sources/declarative/decodable/DecoderLazy.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension CSVDecoder.Lazy {
4848
guard !self._source.isRowAtEnd(index: self._currentIndex) else { return nil }
4949

5050
defer { self._currentIndex += 1 }
51-
let decoder = ShadowDecoder(source: self._source, codingPath: [IndexKey(self._currentIndex)])
51+
let decoder = ShadowDecoder(source: .passUnretained(self._source), codingPath: [IndexKey(self._currentIndex)])
5252
return Row(decoder: decoder)
5353
}
5454

sources/declarative/decodable/containers/KeyedDecodingContainer.swift

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,35 @@ extension ShadowDecoder {
4343
}
4444

4545
var allKeys: [Key] {
46-
switch self._focus {
47-
case .file:
48-
guard let numRows = self._decoder.source.numRows, numRows > 0 else { return [] }
49-
return (0..<numRows).compactMap { Key(intValue: $0) }
50-
case .row:
51-
let numFields = self._decoder.source.numExpectedFields
52-
guard numFields > 0 else { return [] }
53-
54-
let numberKeys = (0..<numFields).compactMap { Key(intValue: $0) }
55-
guard numberKeys.isEmpty else { return numberKeys }
56-
57-
return self._decoder.source.headers.compactMap { Key(stringValue: $0) }
46+
self._decoder.source._withUnsafeGuaranteedRef { [focus = self._focus] in
47+
switch focus {
48+
case .file:
49+
guard let numRows = $0.numRows, numRows > 0 else { return [] }
50+
return (0..<numRows).compactMap { Key(intValue: $0) }
51+
case .row:
52+
let numFields = $0.numExpectedFields
53+
guard numFields > 0 else { return [] }
54+
55+
let numberKeys = (0..<numFields).compactMap { Key(intValue: $0) }
56+
guard numberKeys.isEmpty else { return numberKeys }
57+
58+
return $0.headers.compactMap { Key(stringValue: $0) }
59+
}
5860
}
5961
}
6062

6163
func contains(_ key: Key) -> Bool {
62-
switch self._focus {
63-
case .file:
64-
guard let index = key.intValue else { return false }
65-
return self._decoder.source.contains(rowIndex: index)
66-
case .row:
67-
if let index = key.intValue {
68-
return index >= 0 && index < self._decoder.source.numExpectedFields
69-
} else {
70-
return self._decoder.source.headers.contains(key.stringValue)
64+
self._decoder.source._withUnsafeGuaranteedRef { [focus = self._focus] in
65+
switch focus {
66+
case .file:
67+
guard let index = key.intValue else { return false }
68+
return $0.contains(rowIndex: index)
69+
case .row:
70+
if let index = key.intValue {
71+
return index >= 0 && index < $0.numExpectedFields
72+
} else {
73+
return $0.headers.contains(key.stringValue)
74+
}
7175
}
7276
}
7377
}
@@ -277,11 +281,11 @@ private extension ShadowDecoder.KeyedContainer {
277281

278282
switch self._focus {
279283
case .row(let rowIndex):
280-
index = (rowIndex, try self._decoder.source.fieldIndex(forKey: key, codingPath: self.codingPath))
284+
index = (rowIndex, try self._decoder.source._withUnsafeGuaranteedRef({ try $0.fieldIndex(forKey: key, codingPath: self.codingPath) }))
281285
case .file:
282286
guard let rowIndex = key.intValue else { throw CSVDecoder.Error._invalidRowKey(forKey: key, codingPath: codingPath) }
283287
// Values are only allowed to be decoded directly from a nested container in "file level" if the CSV rows have a single column.
284-
guard self._decoder.source.numExpectedFields == 1 else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
288+
guard self._decoder.source._withUnsafeGuaranteedRef({ $0.numExpectedFields == 1 }) else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
285289
index = (rowIndex, 0)
286290
codingPath.append(IndexKey(index.field))
287291
}

sources/declarative/decodable/containers/SingleValueDecodingContainer.swift

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ extension ShadowDecoder {
3030
case 2:
3131
let key = (row: decoder.codingPath[0], field: decoder.codingPath[1])
3232
let r = try key.row.intValue ?> CSVDecoder.Error._invalidRowKey(forKey: key.row, codingPath: decoder.codingPath)
33-
let f = try decoder.source.fieldIndex(forKey: key.field, codingPath: decoder.codingPath)
33+
let f = try decoder.source._withUnsafeGuaranteedRef { try $0.fieldIndex(forKey: key.field, codingPath: decoder.codingPath) }
3434
self._focus = .field(r, f)
3535
case 1:
3636
let key = decoder.codingPath[0]
@@ -56,14 +56,14 @@ extension ShadowDecoder.SingleValueContainer {
5656
}
5757

5858
func decodeNil() -> Bool {
59-
switch self._decoder.source.configuration.nilStrategy {
59+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.nilStrategy }) {
6060
case .empty: return (try? self._lowlevelDecode { $0.isEmpty }) ?? false
6161
case .custom(let closure): return closure(self._decoder)
6262
}
6363
}
6464

6565
func decode(_ type: Bool.Type) throws -> Bool {
66-
switch self._decoder.source.configuration.boolStrategy {
66+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.boolStrategy }) {
6767
case .deferredToBool:
6868
return try self._lowlevelDecode { Bool($0) }
6969
case .insensitive:
@@ -122,7 +122,7 @@ extension ShadowDecoder.SingleValueContainer {
122122
func decode(_ type: Float.Type) throws -> Float {
123123
try self._lowlevelDecode {
124124
guard let result = Float($0), result.isFinite else {
125-
switch self._decoder.source.configuration.nonConformingFloatStrategy {
125+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.nonConformingFloatStrategy }) {
126126
case .throw: return nil
127127
case .convert(let positiveInfinity, let negativeInfinity, let nan):
128128
switch $0 {
@@ -141,7 +141,7 @@ extension ShadowDecoder.SingleValueContainer {
141141
func decode(_ type: Double.Type) throws -> Double {
142142
try self._lowlevelDecode {
143143
guard let result = Double($0), result.isFinite else {
144-
switch self._decoder.source.configuration.nonConformingFloatStrategy {
144+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.nonConformingFloatStrategy }) {
145145
case .throw: return nil
146146
case .convert(let positiveInfinity, let negativeInfinity, let nan):
147147
switch $0 {
@@ -173,7 +173,7 @@ extension ShadowDecoder.SingleValueContainer {
173173
/// - parameter type: The type to decode as.
174174
/// - returns: A value of the requested type.
175175
func decode(_ type: Date.Type) throws -> Date {
176-
switch self._decoder.source.configuration.dateStrategy {
176+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.dateStrategy }) {
177177
case .deferredToDate:
178178
return try Date(from: self._decoder)
179179
case .secondsSince1970:
@@ -197,7 +197,7 @@ extension ShadowDecoder.SingleValueContainer {
197197
/// - parameter type: The type to decode as.
198198
/// - returns: A value of the requested type.
199199
func decode(_ type: Data.Type) throws -> Data {
200-
switch self._decoder.source.configuration.dataStrategy {
200+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.dataStrategy }) {
201201
case .deferredToData:
202202
return try Data(from: self._decoder)
203203
case .base64:
@@ -212,7 +212,7 @@ extension ShadowDecoder.SingleValueContainer {
212212
/// - parameter type: The type to decode as.
213213
/// - returns: A value of the requested type.
214214
func decode(_ type: Decimal.Type) throws -> Decimal {
215-
switch self._decoder.source.configuration.decimalStrategy {
215+
switch self._decoder.source._withUnsafeGuaranteedRef({ $0.configuration.decimalStrategy }) {
216216
case .locale(let locale):
217217
let string = try self.decode(String.self)
218218
return try Decimal(string: string, locale: locale) ?> CSVDecoder.Error._invalidDecimal(string: string, locale: locale, codingPath: self.codingPath)
@@ -245,24 +245,24 @@ private extension ShadowDecoder.SingleValueContainer {
245245
/// Decodes the `String` value under the receiving single value container's `focus` and then tries to transform it in the requested type.
246246
/// - parameter transform: Closure transforming the decoded `String` value into the required type. If it fails, the closure returns `nil`.
247247
func _lowlevelDecode<T>(transform: (String) -> T?) throws -> T {
248-
let source = self._decoder.source
249-
250-
switch self._focus {
251-
case .field(let rowIndex, let fieldIndex):
252-
let string = try source.field(rowIndex, fieldIndex)
253-
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath)
254-
case .row(let rowIndex):
255-
// Values are only allowed to be decoded directly from a single value container in "row level" if the CSV has single column rows.
256-
guard source.numExpectedFields == 1 else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
257-
let string = try source.field(rowIndex, 0)
258-
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath + [IndexKey(0)])
259-
case .file:
260-
// Values are only allowed to be decoded directly from a single value container in "file level" if the CSV file has a single row with a single column.
261-
if source.isRowAtEnd(index: 1), source.numExpectedFields == 1 {
262-
let string = try self._decoder.source.field(0, 0)
263-
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath + [IndexKey(0), IndexKey(0)])
264-
} else {
265-
throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath)
248+
try self._decoder.source._withUnsafeGuaranteedRef {
249+
switch self._focus {
250+
case .field(let rowIndex, let fieldIndex):
251+
let string = try $0.field(rowIndex, fieldIndex)
252+
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath)
253+
case .row(let rowIndex):
254+
// Values are only allowed to be decoded directly from a single value container in "row level" if the CSV has single column rows.
255+
guard $0.numExpectedFields == 1 else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
256+
let string = try $0.field(rowIndex, 0)
257+
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath + [IndexKey(0)])
258+
case .file:
259+
// Values are only allowed to be decoded directly from a single value container in "file level" if the CSV file has a single row with a single column.
260+
if $0.isRowAtEnd(index: 1), $0.numExpectedFields == 1 {
261+
let string = try $0.field(0, 0)
262+
return try transform(string) ?> CSVDecoder.Error._invalid(type: T.self, string: string, codingPath: self.codingPath + [IndexKey(0), IndexKey(0)])
263+
} else {
264+
throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath)
265+
}
266266
}
267267
}
268268
}

sources/declarative/decodable/containers/UnkeyedDecodingContainer.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,20 @@ extension ShadowDecoder {
4646
}
4747

4848
var count: Int? {
49-
switch self._focus {
50-
case .file: return self._decoder.source.numRows
51-
case .row: return self._decoder.source.numExpectedFields
49+
self._decoder.source._withUnsafeGuaranteedRef { [focus = self._focus] in
50+
switch focus {
51+
case .file: return $0.numRows
52+
case .row: return $0.numExpectedFields
53+
}
5254
}
5355
}
5456

5557
var isAtEnd: Bool {
56-
switch self._focus {
57-
case .file: return self._decoder.source.isRowAtEnd(index: self.currentIndex)
58-
case .row: return self._decoder.source.isFieldAtEnd(index: self.currentIndex)
58+
self._decoder.source._withUnsafeGuaranteedRef {
59+
switch self._focus {
60+
case .file: return $0.isRowAtEnd(index: self.currentIndex)
61+
case .row: return $0.isFieldAtEnd(index: self.currentIndex)
62+
}
5963
}
6064
}
6165
}
@@ -234,7 +238,7 @@ private extension ShadowDecoder.UnkeyedContainer {
234238
index = (rowIndex, self.currentIndex)
235239
case .file:
236240
// Values are only allowed to be decoded directly from a nested container in "file level" if the CSV rows have a single column.
237-
guard self._decoder.source.numExpectedFields == 1 else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
241+
guard self._decoder.source._withUnsafeGuaranteedRef({ $0.numExpectedFields == 1 }) else { throw CSVDecoder.Error._invalidNestedRequired(codingPath: self.codingPath) }
238242
index = (self.currentIndex, 0)
239243
codingPath.append(IndexKey(index.field))
240244
}

sources/declarative/decodable/internal/ShadowDecoder.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
/// A shadow decoder represents a moment in time on the decoding process. Therefore it is a immutable structure.
44
internal struct ShadowDecoder: Decoder {
55
/// The source of the CSV data.
6-
let source: Source
6+
let source: Unmanaged<Source>
77
/// The path of coding keys taken to get to this point in decoding.
88
let codingPath: [CodingKey]
9-
/// Any contextual information set by the user for decoding.
10-
var userInfo: [CodingUserInfoKey:Any] { self.source.userInfo }
119

1210
/// Designated initializer passing all required components.
1311
/// - parameter source: The data source for the decoder.
1412
/// - parameter codingPath: The path taken to create the decoder instance.
15-
init(source: Source, codingPath: [CodingKey]) {
13+
init(source: Unmanaged<Source>, codingPath: [CodingKey]) {
1614
self.source = source
1715
self.codingPath = codingPath
1816
}
17+
18+
/// Any contextual information set by the user for decoding.
19+
var userInfo: [CodingUserInfoKey:Any] {
20+
self.source._withUnsafeGuaranteedRef {
21+
$0.userInfo
22+
}
23+
}
1924
}
2025

2126
extension ShadowDecoder {

sources/declarative/encodable/Encoder.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ extension CSVEncoder {
3737
/// - returns: `Data` blob with the CSV representation of `value`.
3838
open func encode<T:Encodable>(_ value: T, into type: Data.Type) throws -> Data {
3939
let writer = try CSVWriter(configuration: self._configuration.writerConfiguration)
40-
let sink = try ShadowEncoder.Sink(writer: writer, configuration: self._configuration, userInfo: self.userInfo)
41-
try value.encode(to: ShadowEncoder(sink: sink, codingPath: []))
42-
try sink.completeEncoding()
40+
try withExtendedLifetime(try ShadowEncoder.Sink(writer: writer, configuration: self._configuration, userInfo: self.userInfo)) {
41+
try value.encode(to: ShadowEncoder(sink: .passUnretained($0), codingPath: []))
42+
try $0.completeEncoding()
43+
}
4344
return try writer.data()
4445
}
4546

@@ -59,9 +60,10 @@ extension CSVEncoder {
5960
/// - parameter append: In case an existing file is under the given URL, this Boolean indicates that the information will be appended to the file (`true`), or the file will be overwritten (`false`).
6061
open func encode<T:Encodable>(_ value: T, into fileURL: URL, append: Bool = false) throws {
6162
let writer = try CSVWriter(fileURL: fileURL, append: append, configuration: self._configuration.writerConfiguration)
62-
let sink = try ShadowEncoder.Sink(writer: writer, configuration: self._configuration, userInfo: self.userInfo)
63-
try value.encode(to: ShadowEncoder(sink: sink, codingPath: []))
64-
try sink.completeEncoding()
63+
try withExtendedLifetime(try ShadowEncoder.Sink(writer: writer, configuration: self._configuration, userInfo: self.userInfo)) {
64+
try value.encode(to: ShadowEncoder(sink: .passUnretained($0), codingPath: []))
65+
try $0.completeEncoding()
66+
}
6567
}
6668
}
6769

0 commit comments

Comments
 (0)