I have a function that can be run up to 2 times simultaneously to fetch information from an API. In Google Crashlytics, I see quite a few users affected by this crash but I've never seen anything like it and am not sure where to go from here.
The meat of the method:
URLSession.shared.dataTask(with: urlRequest) { (data, res, err) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
let json = try decoder.decode([String: TokenResponse].self, from: data)
}
catch {
print(error.localizedDescription)
}
}.resume()
TokenResponse:
struct TokenResponse: Decodable {
var ticket : String
var expiration : Date?
var sessionId: String
}
Stack Trace:
Crashed: com.apple.NSURLSession-delegate
0 libicucore.A.dylib 0xe4874 icu::Calendar::clear() + 168
1 CoreFoundation 0x57ab4 __cficu_ucal_clear + 28
2 CoreFoundation 0x57ab4 __cficu_ucal_clear + 28
3 CoreFoundation 0xca884 CFDateFormatterGetAbsoluteTimeFromString + 396
4 CoreFoundation 0xe0490 CFDateFormatterCreateDateFromString + 108
5 Foundation 0x1c2dc getObjectValue + 272
6 Foundation 0x9f300 -[NSDateFormatter getObjectValue:forString:errorDescription:] + 200
7 Foundation 0xbb5d0 -[NSDateFormatter dateFromString:] + 64
8 libswiftFoundation.dylib 0x37a58 __JSONDecoder.unbox(_:as:) + 592
9 libswiftFoundation.dylib 0x21064 __JSONDecoder.unbox_(_:as:) + 452
10 libswiftFoundation.dylib 0x1e040 _JSONKeyedDecodingContainer.decode<A>(_:forKey:) + 848
11 libswiftFoundation.dylib 0x27a48 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 56
12 libswiftFoundation.dylib 0x258e0 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 36
13 libswiftCore.dylib 0x549a8 KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) + 648
14 libswiftFoundation.dylib 0x231dc protocol witness for KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 36
15 libswiftCore.dylib 0x67dbc _KeyedDecodingContainerBox.decodeIfPresent<A, B>(_:forKey:) + 520
16 libswiftCore.dylib 0x55880 KeyedDecodingContainer.decodeIfPresent<A>(_:forKey:) + 76
17 MyApp 0x1648dc MyClass.TokenResponse.init(from:) + 4344695004 (<compiler-generated>:4344695004)
18 MyApp 0x164a78 protocol witness for Decodable.init(from:) in conformance MyClass.TokenResponse + 4344695416 (<compiler-generated>:4344695416)
19 libswiftCore.dylib 0x34c124 dispatch thunk of Decodable.init(from:) + 32
20 libswiftFoundation.dylib 0x51380 specialized __JSONDecoder.unbox<A>(_:as:) + 2540
21 libswiftFoundation.dylib 0x21048 __JSONDecoder.unbox_(_:as:) + 424
22 libswiftFoundation.dylib 0xcfc4 JSONDecoder.decode<A>(_:from:) + 1548
23 libswiftFoundation.dylib 0x44fc4 dispatch thunk of JSONDecoder.decode<A>(_:from:) + 56
24 MyApp 0x12b84c closure #1 in MyClass.GetAllServerTokens() + 188 (MyClass.swift:188)
25 MyApp 0x12add0 thunk for @escaping @callee_guaranteed (@guaranteed Data?, @guaranteed NSURLResponse?, @guaranteed Error?) -> () + 4344458704 (<compiler-generated>:4344458704)
26 CFNetwork 0x22b34 CFURLRequestSetMainDocumentURL + 3028
27 CFNetwork 0x33af8 _CFNetworkErrorCopyLocalizedDescriptionWithHostname + 11412
28 libdispatch.dylib 0x2914 _dispatch_call_block_and_release + 32
29 libdispatch.dylib 0x4660 _dispatch_client_callout + 20
30 libdispatch.dylib 0xbde4 _dispatch_lane_serial_drain + 672
31 libdispatch.dylib 0xc98c _dispatch_lane_invoke + 444
32 libdispatch.dylib 0x171a8 _dispatch_workloop_worker_thread + 656
33 libsystem_pthread.dylib 0x10f4 _pthread_wqthread + 288
34 libsystem_pthread.dylib 0xe94 start_wqthread + 8
EDIT: Example of the JSON that is fetched. I cannot post due to security concerns. It changes every 2 hours so I don't have the exact copy that caused the crashes but it should always follow this pattern. ticket
and sessionId
are never null:
{
"1": {
"ticket":"aRandomJSONWebTokenHere",
"expiration":"2022-01-16T10:00:38.2775891Z",
"sessionId":"aUuidHere"
},
"2": {
"ticket":"aRandomJSONWebTokenHere",
"expiration":"2022-01-16T10:00:38.2775891Z",
"sessionId":"aUuidHere"
}...all the way until 60
}
EDIT 2: self.dateFormatter
:
let dateFormatter : DateFormatter = {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return df
}()
self.dateFormatter
is not modified. It is used in other places in the same manner, as a dateDecodingStrategy. Maybe this crash happens when more than one URLSession read self.dateFormatter
at the same time?
Maybe this crash happens when more than one URLSession read self.dateFormatter at the same time?
Sure, it's possible. Your code is certainly thread-unsafe! But it's obvious what to do; in your URLSession.shared.dataTask
completion handler, get on the main thread and stay there.
URLSession.shared.dataTask(with: urlRequest) { (data, res, err) in
guard let data = data else { return }
DispatchQueue.main.async {
do { // ...
}
catch { // ...
}
}
}.resume()