1 |
efrain |
1 |
//
|
|
|
2 |
// RequestTaskMap.swift
|
|
|
3 |
//
|
|
|
4 |
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
|
|
|
5 |
//
|
|
|
6 |
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
7 |
// of this software and associated documentation files (the "Software"), to deal
|
|
|
8 |
// in the Software without restriction, including without limitation the rights
|
|
|
9 |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
10 |
// copies of the Software, and to permit persons to whom the Software is
|
|
|
11 |
// furnished to do so, subject to the following conditions:
|
|
|
12 |
//
|
|
|
13 |
// The above copyright notice and this permission notice shall be included in
|
|
|
14 |
// all copies or substantial portions of the Software.
|
|
|
15 |
//
|
|
|
16 |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
17 |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
18 |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
19 |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
20 |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
21 |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
22 |
// THE SOFTWARE.
|
|
|
23 |
//
|
|
|
24 |
|
|
|
25 |
import Foundation
|
|
|
26 |
|
|
|
27 |
/// A type that maintains a two way, one to one map of `URLSessionTask`s to `Request`s.
|
|
|
28 |
struct RequestTaskMap {
|
|
|
29 |
private typealias Events = (completed: Bool, metricsGathered: Bool)
|
|
|
30 |
|
|
|
31 |
private var tasksToRequests: [URLSessionTask: Request]
|
|
|
32 |
private var requestsToTasks: [Request: URLSessionTask]
|
|
|
33 |
private var taskEvents: [URLSessionTask: Events]
|
|
|
34 |
|
|
|
35 |
var requests: [Request] {
|
|
|
36 |
Array(tasksToRequests.values)
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
init(tasksToRequests: [URLSessionTask: Request] = [:],
|
|
|
40 |
requestsToTasks: [Request: URLSessionTask] = [:],
|
|
|
41 |
taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)] = [:]) {
|
|
|
42 |
self.tasksToRequests = tasksToRequests
|
|
|
43 |
self.requestsToTasks = requestsToTasks
|
|
|
44 |
self.taskEvents = taskEvents
|
|
|
45 |
}
|
|
|
46 |
|
|
|
47 |
subscript(_ request: Request) -> URLSessionTask? {
|
|
|
48 |
get { requestsToTasks[request] }
|
|
|
49 |
set {
|
|
|
50 |
guard let newValue = newValue else {
|
|
|
51 |
guard let task = requestsToTasks[request] else {
|
|
|
52 |
fatalError("RequestTaskMap consistency error: no task corresponding to request found.")
|
|
|
53 |
}
|
|
|
54 |
|
|
|
55 |
requestsToTasks.removeValue(forKey: request)
|
|
|
56 |
tasksToRequests.removeValue(forKey: task)
|
|
|
57 |
taskEvents.removeValue(forKey: task)
|
|
|
58 |
|
|
|
59 |
return
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
requestsToTasks[request] = newValue
|
|
|
63 |
tasksToRequests[newValue] = request
|
|
|
64 |
taskEvents[newValue] = (completed: false, metricsGathered: false)
|
|
|
65 |
}
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
subscript(_ task: URLSessionTask) -> Request? {
|
|
|
69 |
get { tasksToRequests[task] }
|
|
|
70 |
set {
|
|
|
71 |
guard let newValue = newValue else {
|
|
|
72 |
guard let request = tasksToRequests[task] else {
|
|
|
73 |
fatalError("RequestTaskMap consistency error: no request corresponding to task found.")
|
|
|
74 |
}
|
|
|
75 |
|
|
|
76 |
tasksToRequests.removeValue(forKey: task)
|
|
|
77 |
requestsToTasks.removeValue(forKey: request)
|
|
|
78 |
taskEvents.removeValue(forKey: task)
|
|
|
79 |
|
|
|
80 |
return
|
|
|
81 |
}
|
|
|
82 |
|
|
|
83 |
tasksToRequests[task] = newValue
|
|
|
84 |
requestsToTasks[newValue] = task
|
|
|
85 |
taskEvents[task] = (completed: false, metricsGathered: false)
|
|
|
86 |
}
|
|
|
87 |
}
|
|
|
88 |
|
|
|
89 |
var count: Int {
|
|
|
90 |
precondition(tasksToRequests.count == requestsToTasks.count,
|
|
|
91 |
"RequestTaskMap.count invalid, requests.count: \(tasksToRequests.count) != tasks.count: \(requestsToTasks.count)")
|
|
|
92 |
|
|
|
93 |
return tasksToRequests.count
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
var eventCount: Int {
|
|
|
97 |
precondition(taskEvents.count == count, "RequestTaskMap.eventCount invalid, count: \(count) != taskEvents.count: \(taskEvents.count)")
|
|
|
98 |
|
|
|
99 |
return taskEvents.count
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
var isEmpty: Bool {
|
|
|
103 |
precondition(tasksToRequests.isEmpty == requestsToTasks.isEmpty,
|
|
|
104 |
"RequestTaskMap.isEmpty invalid, requests.isEmpty: \(tasksToRequests.isEmpty) != tasks.isEmpty: \(requestsToTasks.isEmpty)")
|
|
|
105 |
|
|
|
106 |
return tasksToRequests.isEmpty
|
|
|
107 |
}
|
|
|
108 |
|
|
|
109 |
var isEventsEmpty: Bool {
|
|
|
110 |
precondition(taskEvents.isEmpty == isEmpty, "RequestTaskMap.isEventsEmpty invalid, isEmpty: \(isEmpty) != taskEvents.isEmpty: \(taskEvents.isEmpty)")
|
|
|
111 |
|
|
|
112 |
return taskEvents.isEmpty
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
mutating func disassociateIfNecessaryAfterGatheringMetricsForTask(_ task: URLSessionTask) -> Bool {
|
|
|
116 |
guard let events = taskEvents[task] else {
|
|
|
117 |
fatalError("RequestTaskMap consistency error: no events corresponding to task found.")
|
|
|
118 |
}
|
|
|
119 |
|
|
|
120 |
switch (events.completed, events.metricsGathered) {
|
|
|
121 |
case (_, true): fatalError("RequestTaskMap consistency error: duplicate metricsGatheredForTask call.")
|
|
|
122 |
case (false, false): taskEvents[task] = (completed: false, metricsGathered: true); return false
|
|
|
123 |
case (true, false): self[task] = nil; return true
|
|
|
124 |
}
|
|
|
125 |
}
|
|
|
126 |
|
|
|
127 |
mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) -> Bool {
|
|
|
128 |
guard let events = taskEvents[task] else {
|
|
|
129 |
fatalError("RequestTaskMap consistency error: no events corresponding to task found.")
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
switch (events.completed, events.metricsGathered) {
|
|
|
133 |
case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.")
|
|
|
134 |
#if os(Linux) // Linux doesn't gather metrics, so unconditionally remove the reference and return true.
|
|
|
135 |
default: self[task] = nil; return true
|
|
|
136 |
#else
|
|
|
137 |
case (false, false):
|
|
|
138 |
if #available(macOS 10.12, iOS 10, watchOS 7, tvOS 10, *) {
|
|
|
139 |
taskEvents[task] = (completed: true, metricsGathered: false); return false
|
|
|
140 |
} else {
|
|
|
141 |
// watchOS < 7 doesn't gather metrics, so unconditionally remove the reference and return true.
|
|
|
142 |
self[task] = nil; return true
|
|
|
143 |
}
|
|
|
144 |
case (false, true):
|
|
|
145 |
self[task] = nil; return true
|
|
|
146 |
#endif
|
|
|
147 |
}
|
|
|
148 |
}
|
|
|
149 |
}
|