@@ -87,7 +87,60 @@ final class URLSessionHTTPClient {
87
87
}
88
88
}
89
89
90
- private class DataTaskManager : NSObject , URLSessionDataDelegate {
90
+ /// A weak wrapper around `DataTaskManager` that conforms to `URLSessionDataDelegate`.
91
+ ///
92
+ /// This ensures that we don't get a retain cycle between `DataTaskManager.session` -> `URLSession.delegate` -> `DataTaskManager`.
93
+ ///
94
+ /// The `DataTaskManager` is being kept alive by a reference from all `DataTask`s that it manages. Once all the
95
+ /// `DataTasks` have finished and are deallocated, `DataTaskManager` will get deinitialized, which invalidates the
96
+ /// session, which then lets go of `WeakDataTaskManager`.
97
+ private class WeakDataTaskManager : NSObject , URLSessionDataDelegate {
98
+ private weak var dataTaskManager : DataTaskManager ?
99
+
100
+ init ( _ dataTaskManager: DataTaskManager ? = nil ) {
101
+ self . dataTaskManager = dataTaskManager
102
+ }
103
+
104
+ func urlSession(
105
+ _ session: URLSession ,
106
+ dataTask: URLSessionDataTask ,
107
+ didReceive response: URLResponse ,
108
+ completionHandler: @escaping ( URLSession . ResponseDisposition ) -> Void
109
+ ) {
110
+ dataTaskManager? . urlSession (
111
+ session,
112
+ dataTask: dataTask,
113
+ didReceive: response,
114
+ completionHandler: completionHandler
115
+ )
116
+ }
117
+
118
+ func urlSession( _ session: URLSession , dataTask: URLSessionDataTask , didReceive data: Data ) {
119
+ dataTaskManager? . urlSession ( session, dataTask: dataTask, didReceive: data)
120
+ }
121
+
122
+ func urlSession( _ session: URLSession , task: URLSessionTask , didCompleteWithError error: Error ? ) {
123
+ dataTaskManager? . urlSession ( session, task: task, didCompleteWithError: error)
124
+ }
125
+
126
+ func urlSession(
127
+ _ session: URLSession ,
128
+ task: URLSessionTask ,
129
+ willPerformHTTPRedirection response: HTTPURLResponse ,
130
+ newRequest request: URLRequest ,
131
+ completionHandler: @escaping ( URLRequest ? ) -> Void
132
+ ) {
133
+ dataTaskManager? . urlSession (
134
+ session,
135
+ task: task,
136
+ willPerformHTTPRedirection: response,
137
+ newRequest: request,
138
+ completionHandler: completionHandler
139
+ )
140
+ }
141
+ }
142
+
143
+ private class DataTaskManager {
91
144
private var tasks = ThreadSafeKeyValueStore < Int , DataTask > ( )
92
145
private let delegateQueue : OperationQueue
93
146
private var session : URLSession !
@@ -96,8 +149,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
96
149
self . delegateQueue = OperationQueue ( )
97
150
self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-data-delegate "
98
151
self . delegateQueue. maxConcurrentOperationCount = 1
99
- super. init ( )
100
- self . session = URLSession ( configuration: configuration, delegate: self , delegateQueue: self . delegateQueue)
152
+ self . session = URLSession ( configuration: configuration, delegate: WeakDataTaskManager ( self ) , delegateQueue: self . delegateQueue)
153
+ }
154
+
155
+ deinit {
156
+ session. finishTasksAndInvalidate ( )
101
157
}
102
158
103
159
func makeTask(
@@ -110,6 +166,7 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
110
166
self . tasks [ task. taskIdentifier] = DataTask (
111
167
task: task,
112
168
progressHandler: progress,
169
+ dataTaskManager: self ,
113
170
completionHandler: completion,
114
171
authorizationProvider: authorizationProvider
115
172
)
@@ -192,6 +249,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
192
249
class DataTask {
193
250
let task : URLSessionDataTask
194
251
let completionHandler : LegacyHTTPClient . CompletionHandler
252
+ /// A strong reference to keep the `DataTaskManager` alive so it can handle the callbacks from the
253
+ /// `URLSession`.
254
+ ///
255
+ /// See comment on `WeakDataTaskManager`.
256
+ let dataTaskManager : DataTaskManager
195
257
let progressHandler : LegacyHTTPClient . ProgressHandler ?
196
258
let authorizationProvider : LegacyHTTPClientConfiguration . AuthorizationProvider ?
197
259
@@ -202,18 +264,61 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
202
264
init (
203
265
task: URLSessionDataTask ,
204
266
progressHandler: LegacyHTTPClient . ProgressHandler ? ,
267
+ dataTaskManager: DataTaskManager ,
205
268
completionHandler: @escaping LegacyHTTPClient . CompletionHandler ,
206
269
authorizationProvider: LegacyHTTPClientConfiguration . AuthorizationProvider ?
207
270
) {
208
271
self . task = task
209
272
self . progressHandler = progressHandler
273
+ self . dataTaskManager = dataTaskManager
210
274
self . completionHandler = completionHandler
211
275
self . authorizationProvider = authorizationProvider
212
276
}
213
277
}
214
278
}
215
279
216
- private class DownloadTaskManager : NSObject , URLSessionDownloadDelegate {
280
+ /// This uses the same pattern as `WeakDataTaskManager`. See comment on that type.
281
+ private class WeakDownloadTaskManager : NSObject , URLSessionDownloadDelegate {
282
+ private weak var downloadTaskManager : DownloadTaskManager ?
283
+
284
+ init ( _ downloadTaskManager: DownloadTaskManager ? = nil ) {
285
+ self . downloadTaskManager = downloadTaskManager
286
+ }
287
+
288
+ func urlSession(
289
+ _ session: URLSession ,
290
+ downloadTask: URLSessionDownloadTask ,
291
+ didWriteData bytesWritten: Int64 ,
292
+ totalBytesWritten: Int64 ,
293
+ totalBytesExpectedToWrite: Int64
294
+ ) {
295
+ downloadTaskManager? . urlSession (
296
+ session,
297
+ downloadTask: downloadTask,
298
+ didWriteData: bytesWritten,
299
+ totalBytesWritten: totalBytesWritten,
300
+ totalBytesExpectedToWrite: totalBytesExpectedToWrite
301
+ )
302
+ }
303
+
304
+ func urlSession(
305
+ _ session: URLSession ,
306
+ downloadTask: URLSessionDownloadTask ,
307
+ didFinishDownloadingTo location: URL
308
+ ) {
309
+ downloadTaskManager? . urlSession ( session, downloadTask: downloadTask, didFinishDownloadingTo: location)
310
+ }
311
+
312
+ func urlSession(
313
+ _ session: URLSession ,
314
+ task downloadTask: URLSessionTask ,
315
+ didCompleteWithError error: Error ?
316
+ ) {
317
+ downloadTaskManager? . urlSession ( session, task: downloadTask, didCompleteWithError: error)
318
+ }
319
+ }
320
+
321
+ private class DownloadTaskManager {
217
322
private var tasks = ThreadSafeKeyValueStore < Int , DownloadTask > ( )
218
323
private let delegateQueue : OperationQueue
219
324
private var session : URLSession !
@@ -222,8 +327,11 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
222
327
self . delegateQueue = OperationQueue ( )
223
328
self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-download-delegate "
224
329
self . delegateQueue. maxConcurrentOperationCount = 1
225
- super. init ( )
226
- self . session = URLSession ( configuration: configuration, delegate: self , delegateQueue: self . delegateQueue)
330
+ self . session = URLSession ( configuration: configuration, delegate: WeakDownloadTaskManager ( self ) , delegateQueue: self . delegateQueue)
331
+ }
332
+
333
+ deinit {
334
+ session. finishTasksAndInvalidate ( )
227
335
}
228
336
229
337
func makeTask(
@@ -238,6 +346,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
238
346
task: task,
239
347
fileSystem: fileSystem,
240
348
destination: destination,
349
+ downloadTaskManager: self ,
241
350
progressHandler: progress,
242
351
completionHandler: completion
243
352
)
@@ -314,21 +423,28 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
314
423
let task : URLSessionDownloadTask
315
424
let fileSystem : FileSystem
316
425
let destination : AbsolutePath
317
- let completionHandler : LegacyHTTPClient . CompletionHandler
426
+ /// A strong reference to keep the `DownloadTaskManager` alive so it can handle the callbacks from the
427
+ /// `URLSession`.
428
+ ///
429
+ /// See comment on `WeakDownloadTaskManager`.
430
+ private let downloadTaskManager : DownloadTaskManager
318
431
let progressHandler : LegacyHTTPClient . ProgressHandler ?
432
+ let completionHandler : LegacyHTTPClient . CompletionHandler
319
433
320
434
var moveFileError : Error ?
321
435
322
436
init (
323
437
task: URLSessionDownloadTask ,
324
438
fileSystem: FileSystem ,
325
439
destination: AbsolutePath ,
440
+ downloadTaskManager: DownloadTaskManager ,
326
441
progressHandler: LegacyHTTPClient . ProgressHandler ? ,
327
442
completionHandler: @escaping LegacyHTTPClient . CompletionHandler
328
443
) {
329
444
self . task = task
330
445
self . fileSystem = fileSystem
331
446
self . destination = destination
447
+ self . downloadTaskManager = downloadTaskManager
332
448
self . progressHandler = progressHandler
333
449
self . completionHandler = completionHandler
334
450
}
0 commit comments