@@ -87,7 +87,50 @@ 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
+
105
+ public func urlSession(
106
+ _ session: URLSession ,
107
+ dataTask: URLSessionDataTask ,
108
+ didReceive response: URLResponse ,
109
+ completionHandler: @escaping ( URLSession . ResponseDisposition ) -> Void
110
+ ) {
111
+ dataTaskManager? . urlSession ( session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler)
112
+ }
113
+
114
+ public func urlSession( _ session: URLSession , dataTask: URLSessionDataTask , didReceive data: Data ) {
115
+ dataTaskManager? . urlSession ( session, dataTask: dataTask, didReceive: data)
116
+ }
117
+
118
+ public func urlSession( _ session: URLSession , task: URLSessionTask , didCompleteWithError error: Error ? ) {
119
+ dataTaskManager? . urlSession ( session, task: task, didCompleteWithError: error)
120
+ }
121
+
122
+ public func urlSession(
123
+ _ session: URLSession ,
124
+ task: URLSessionTask ,
125
+ willPerformHTTPRedirection response: HTTPURLResponse ,
126
+ newRequest request: URLRequest ,
127
+ completionHandler: @escaping ( URLRequest ? ) -> Void
128
+ ) {
129
+ dataTaskManager? . urlSession ( session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler)
130
+ }
131
+ }
132
+
133
+ private class DataTaskManager {
91
134
private var tasks = ThreadSafeKeyValueStore < Int , DataTask > ( )
92
135
private let delegateQueue : OperationQueue
93
136
private var session : URLSession !
@@ -96,8 +139,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
96
139
self . delegateQueue = OperationQueue ( )
97
140
self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-data-delegate "
98
141
self . delegateQueue. maxConcurrentOperationCount = 1
99
- super. init ( )
100
- self . session = URLSession ( configuration: configuration, delegate: self , delegateQueue: self . delegateQueue)
142
+ self . session = URLSession ( configuration: configuration, delegate: WeakDataTaskManager ( self ) , delegateQueue: self . delegateQueue)
143
+ }
144
+
145
+ deinit {
146
+ session. finishTasksAndInvalidate ( )
101
147
}
102
148
103
149
func makeTask(
@@ -110,6 +156,7 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
110
156
self . tasks [ task. taskIdentifier] = DataTask (
111
157
task: task,
112
158
progressHandler: progress,
159
+ dataTaskManager: self ,
113
160
completionHandler: completion,
114
161
authorizationProvider: authorizationProvider
115
162
)
@@ -192,6 +239,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
192
239
class DataTask {
193
240
let task : URLSessionDataTask
194
241
let completionHandler : LegacyHTTPClient . CompletionHandler
242
+ /// A strong reference to keep the `DataTaskManager` alive so it can handle the callbacks from the
243
+ /// `URLSession`.
244
+ ///
245
+ /// See comment on `WeakDataTaskManager`.
246
+ let dataTaskManager : DataTaskManager
195
247
let progressHandler : LegacyHTTPClient . ProgressHandler ?
196
248
let authorizationProvider : LegacyHTTPClientConfiguration . AuthorizationProvider ?
197
249
@@ -202,18 +254,61 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
202
254
init (
203
255
task: URLSessionDataTask ,
204
256
progressHandler: LegacyHTTPClient . ProgressHandler ? ,
257
+ dataTaskManager: DataTaskManager ,
205
258
completionHandler: @escaping LegacyHTTPClient . CompletionHandler ,
206
259
authorizationProvider: LegacyHTTPClientConfiguration . AuthorizationProvider ?
207
260
) {
208
261
self . task = task
209
262
self . progressHandler = progressHandler
263
+ self . dataTaskManager = dataTaskManager
210
264
self . completionHandler = completionHandler
211
265
self . authorizationProvider = authorizationProvider
212
266
}
213
267
}
214
268
}
215
269
216
- private class DownloadTaskManager : NSObject , URLSessionDownloadDelegate {
270
+ /// This uses the same pattern as `WeakDataTaskManager`. See comment on that type.
271
+ private class WeakDownloadTaskManager : NSObject , URLSessionDownloadDelegate {
272
+ private weak var downloadTaskManager : DownloadTaskManager ?
273
+
274
+ init ( _ downloadTaskManager: DownloadTaskManager ? = nil ) {
275
+ self . downloadTaskManager = downloadTaskManager
276
+ }
277
+
278
+ func urlSession(
279
+ _ session: URLSession ,
280
+ downloadTask: URLSessionDownloadTask ,
281
+ didWriteData bytesWritten: Int64 ,
282
+ totalBytesWritten: Int64 ,
283
+ totalBytesExpectedToWrite: Int64
284
+ ) {
285
+ downloadTaskManager? . urlSession (
286
+ session,
287
+ downloadTask: downloadTask,
288
+ didWriteData: bytesWritten,
289
+ totalBytesWritten: totalBytesWritten,
290
+ totalBytesExpectedToWrite: totalBytesExpectedToWrite
291
+ )
292
+ }
293
+
294
+ func urlSession(
295
+ _ session: URLSession ,
296
+ downloadTask: URLSessionDownloadTask ,
297
+ didFinishDownloadingTo location: URL
298
+ ) {
299
+ downloadTaskManager? . urlSession ( session, downloadTask: downloadTask, didFinishDownloadingTo: location)
300
+ }
301
+
302
+ public func urlSession(
303
+ _ session: URLSession ,
304
+ task downloadTask: URLSessionTask ,
305
+ didCompleteWithError error: Error ?
306
+ ) {
307
+ downloadTaskManager? . urlSession ( session, task: downloadTask, didCompleteWithError: error)
308
+ }
309
+ }
310
+
311
+ private class DownloadTaskManager {
217
312
private var tasks = ThreadSafeKeyValueStore < Int , DownloadTask > ( )
218
313
private let delegateQueue : OperationQueue
219
314
private var session : URLSession !
@@ -222,8 +317,11 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
222
317
self . delegateQueue = OperationQueue ( )
223
318
self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-download-delegate "
224
319
self . delegateQueue. maxConcurrentOperationCount = 1
225
- super. init ( )
226
- self . session = URLSession ( configuration: configuration, delegate: self , delegateQueue: self . delegateQueue)
320
+ self . session = URLSession ( configuration: configuration, delegate: WeakDownloadTaskManager ( self ) , delegateQueue: self . delegateQueue)
321
+ }
322
+
323
+ deinit {
324
+ session. finishTasksAndInvalidate ( )
227
325
}
228
326
229
327
func makeTask(
@@ -238,6 +336,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
238
336
task: task,
239
337
fileSystem: fileSystem,
240
338
destination: destination,
339
+ downloadTaskManager: self ,
241
340
progressHandler: progress,
242
341
completionHandler: completion
243
342
)
@@ -314,21 +413,28 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
314
413
let task : URLSessionDownloadTask
315
414
let fileSystem : FileSystem
316
415
let destination : AbsolutePath
317
- let completionHandler : LegacyHTTPClient . CompletionHandler
416
+ /// A strong reference to keep the `DownloadTaskManager` alive so it can handle the callbacks from the
417
+ /// `URLSession`.
418
+ ///
419
+ /// See comment on `WeakDownloadTaskManager`.
420
+ private let downloadTaskManager : DownloadTaskManager
318
421
let progressHandler : LegacyHTTPClient . ProgressHandler ?
422
+ let completionHandler : LegacyHTTPClient . CompletionHandler
319
423
320
424
var moveFileError : Error ?
321
425
322
426
init (
323
427
task: URLSessionDownloadTask ,
324
428
fileSystem: FileSystem ,
325
429
destination: AbsolutePath ,
430
+ downloadTaskManager: DownloadTaskManager ,
326
431
progressHandler: LegacyHTTPClient . ProgressHandler ? ,
327
432
completionHandler: @escaping LegacyHTTPClient . CompletionHandler
328
433
) {
329
434
self . task = task
330
435
self . fileSystem = fileSystem
331
436
self . destination = destination
437
+ self . downloadTaskManager = downloadTaskManager
332
438
self . progressHandler = progressHandler
333
439
self . completionHandler = completionHandler
334
440
}
0 commit comments