@@ -87,7 +87,60 @@ final class URLSessionHTTPClient {
8787 }
8888}
8989
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 {
91144 private var tasks = ThreadSafeKeyValueStore < Int , DataTask > ( )
92145 private let delegateQueue : OperationQueue
93146 private var session : URLSession !
@@ -96,8 +149,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
96149 self . delegateQueue = OperationQueue ( )
97150 self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-data-delegate "
98151 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 ( )
101157 }
102158
103159 func makeTask(
@@ -110,6 +166,7 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
110166 self . tasks [ task. taskIdentifier] = DataTask (
111167 task: task,
112168 progressHandler: progress,
169+ dataTaskManager: self ,
113170 completionHandler: completion,
114171 authorizationProvider: authorizationProvider
115172 )
@@ -192,6 +249,11 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
192249 class DataTask {
193250 let task : URLSessionDataTask
194251 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
195257 let progressHandler : LegacyHTTPClient . ProgressHandler ?
196258 let authorizationProvider : LegacyHTTPClientConfiguration . AuthorizationProvider ?
197259
@@ -202,18 +264,61 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate {
202264 init (
203265 task: URLSessionDataTask ,
204266 progressHandler: LegacyHTTPClient . ProgressHandler ? ,
267+ dataTaskManager: DataTaskManager ,
205268 completionHandler: @escaping LegacyHTTPClient . CompletionHandler ,
206269 authorizationProvider: LegacyHTTPClientConfiguration . AuthorizationProvider ?
207270 ) {
208271 self . task = task
209272 self . progressHandler = progressHandler
273+ self . dataTaskManager = dataTaskManager
210274 self . completionHandler = completionHandler
211275 self . authorizationProvider = authorizationProvider
212276 }
213277 }
214278}
215279
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 {
217322 private var tasks = ThreadSafeKeyValueStore < Int , DownloadTask > ( )
218323 private let delegateQueue : OperationQueue
219324 private var session : URLSession !
@@ -222,8 +327,11 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
222327 self . delegateQueue = OperationQueue ( )
223328 self . delegateQueue. name = " org.swift.swiftpm.urlsession-http-client-download-delegate "
224329 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 ( )
227335 }
228336
229337 func makeTask(
@@ -238,6 +346,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
238346 task: task,
239347 fileSystem: fileSystem,
240348 destination: destination,
349+ downloadTaskManager: self ,
241350 progressHandler: progress,
242351 completionHandler: completion
243352 )
@@ -314,21 +423,28 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate {
314423 let task : URLSessionDownloadTask
315424 let fileSystem : FileSystem
316425 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
318431 let progressHandler : LegacyHTTPClient . ProgressHandler ?
432+ let completionHandler : LegacyHTTPClient . CompletionHandler
319433
320434 var moveFileError : Error ?
321435
322436 init (
323437 task: URLSessionDownloadTask ,
324438 fileSystem: FileSystem ,
325439 destination: AbsolutePath ,
440+ downloadTaskManager: DownloadTaskManager ,
326441 progressHandler: LegacyHTTPClient . ProgressHandler ? ,
327442 completionHandler: @escaping LegacyHTTPClient . CompletionHandler
328443 ) {
329444 self . task = task
330445 self . fileSystem = fileSystem
331446 self . destination = destination
447+ self . downloadTaskManager = downloadTaskManager
332448 self . progressHandler = progressHandler
333449 self . completionHandler = completionHandler
334450 }
0 commit comments