From a8018e7077ad339ddad5baa1f1d5660a4f746bff Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Wed, 30 Aug 2017 21:53:56 +0100 Subject: [PATCH] URLSessionTask retain cycle and memory leak fix --- Foundation/URLSession/URLSessionTask.swift | 30 +++++++++++++++---- .../URLSession/http/HTTPURLProtocol.swift | 12 ++++---- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Foundation/URLSession/URLSessionTask.swift b/Foundation/URLSession/URLSessionTask.swift index 6149f17277..98ded2c113 100644 --- a/Foundation/URLSession/URLSessionTask.swift +++ b/Foundation/URLSession/URLSessionTask.swift @@ -33,7 +33,7 @@ open class URLSessionTask : NSObject, NSCopying { internal var suspendCount = 1 internal var session: URLSessionProtocol! //change to nil when task completes internal let body: _Body - fileprivate var _protocol: URLProtocol! = nil + fileprivate var _protocol: URLProtocol? = nil private let syncQ = DispatchQueue(label: "org.swift.URLSessionTask.SyncQ") /// All operations must run on this queue. @@ -178,8 +178,8 @@ open class URLSessionTask : NSObject, NSCopying { self.workQueue.async { let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil)) self.error = urlError - self._protocol.stopLoading() - self._protocol.client?.urlProtocol(self._protocol, didFailWithError: urlError) + self._protocol?.stopLoading() + self._protocol?.client?.urlProtocol(self._protocol!, didFailWithError: urlError) } } } @@ -235,7 +235,7 @@ open class URLSessionTask : NSObject, NSCopying { if self.suspendCount == 1 { self.workQueue.async { - self._protocol.stopLoading() + self._protocol?.stopLoading() } } } @@ -250,7 +250,21 @@ open class URLSessionTask : NSObject, NSCopying { self.updateTaskState() if self.suspendCount == 0 { self.workQueue.async { - self._protocol.startLoading() + if let _protocol = self._protocol { + _protocol.startLoading() + } + else if self.error == nil { + var userInfo: [String: Any] = [NSLocalizedDescriptionKey: "unsupported URL"] + if let url = self.originalRequest?.url { + userInfo[NSURLErrorFailingURLErrorKey] = url + userInfo[NSURLErrorFailingURLStringErrorKey] = url.absoluteString + } + let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, + code: NSURLErrorUnsupportedURL, + userInfo: userInfo)) + self.error = urlError + _ProtocolClient().urlProtocol(task: self, didFailWithError: urlError) + } } } } @@ -547,6 +561,7 @@ extension _ProtocolClient : URLProtocolClient { session.taskRegistry.remove(task) } } + task._protocol = nil } func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge) { @@ -574,6 +589,10 @@ extension _ProtocolClient : URLProtocolClient { func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) { guard let task = `protocol`.task else { fatalError() } + urlProtocol(task: task, didFailWithError: error) + } + + func urlProtocol(task: URLSessionTask, didFailWithError error: Error) { guard let session = task.session as? URLSession else { fatalError() } switch session.behaviour(for: task) { case .taskDelegate(let delegate): @@ -602,6 +621,7 @@ extension _ProtocolClient : URLProtocolClient { session.taskRegistry.remove(task) } } + task._protocol = nil } func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse) { diff --git a/Foundation/URLSession/http/HTTPURLProtocol.swift b/Foundation/URLSession/http/HTTPURLProtocol.swift index 307a64f7af..78aff84b68 100644 --- a/Foundation/URLSession/http/HTTPURLProtocol.swift +++ b/Foundation/URLSession/http/HTTPURLProtocol.swift @@ -13,13 +13,14 @@ import Dispatch internal class _HTTPURLProtocol: URLProtocol { fileprivate var easyHandle: _EasyHandle! - fileprivate var tempFileURL: URL + fileprivate lazy var tempFileURL: URL = { + let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp" + _ = FileManager.default.createFile(atPath: fileName, contents: nil) + return URL(fileURLWithPath: fileName) + }() public required init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { self.internalState = _InternalState.initial - let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp" - _ = FileManager.default.createFile(atPath: fileName, contents: nil) - self.tempFileURL = URL(fileURLWithPath: fileName) super.init(request: task.originalRequest!, cachedResponse: cachedResponse, client: client) self.task = task self.easyHandle = _EasyHandle(delegate: self) @@ -27,9 +28,6 @@ internal class _HTTPURLProtocol: URLProtocol { public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { self.internalState = _InternalState.initial - let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp" - _ = FileManager.default.createFile(atPath: fileName, contents: nil) - self.tempFileURL = URL(fileURLWithPath: fileName) super.init(request: request, cachedResponse: cachedResponse, client: client) self.easyHandle = _EasyHandle(delegate: self) }