Skip to content

Commit 5093191

Browse files
committed
[SR-10428] URLSession.getTasksWithCompletion method implemented
* New private property hasTriggredResume introduces to track whether the task actually move passed the suspendCount in execution * New internal property isSuspendedAfterResume introduced to find the accurate state reason for suspend state
1 parent 0034526 commit 5093191

File tree

3 files changed

+80
-10
lines changed

3 files changed

+80
-10
lines changed

Foundation/URLSession/URLSession.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,38 @@ open class URLSession : NSObject {
345345
open func reset(completionHandler: @escaping () -> Void) { NSUnimplemented() } /* empty all cookies, cache and credential stores, removes disk files, issues -flushWithCompletionHandler:. Invokes completionHandler() on the delegate queue if not nil. */
346346

347347
open func flush(completionHandler: @escaping () -> Void) { NSUnimplemented() }/* flush storage to disk and clear transient network caches. Invokes completionHandler() on the delegate queue if not nil. */
348-
349-
open func getTasksWithCompletionHandler(completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with outstanding data, upload and download tasks. */
348+
349+
/* invokes completionHandler with outstanding data, upload and download tasks. */
350+
open func getTasksWithCompletionHandler(completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) {
351+
workQueue.async {
352+
self.delegateQueue.addOperation {
353+
var dataTasks = [URLSessionDataTask]()
354+
var uploadTasks = [URLSessionUploadTask]()
355+
var downloadTasks = [URLSessionDownloadTask]()
356+
357+
for task in self.taskRegistry.allTasks {
358+
guard task.state == .running || task.isSuspendedAfterResume else { continue }
359+
360+
if let uploadTask = task as? URLSessionUploadTask {
361+
uploadTasks.append(uploadTask)
362+
} else if let dataTask = task as? URLSessionDataTask {
363+
dataTasks.append(dataTask)
364+
} else if let downloadTask = task as? URLSessionDownloadTask {
365+
downloadTasks.append(downloadTask)
366+
} else {
367+
// Above three are the only required tasks to be returned from this API, so we can ignore any other types of tasks.
368+
}
369+
}
370+
completionHandler(dataTasks, uploadTasks, downloadTasks)
371+
}
372+
}
373+
}
350374

351375
/* invokes completionHandler with all outstanding tasks. */
352376
open func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) {
353377
workQueue.async {
354378
self.delegateQueue.addOperation {
355-
completionHandler(self.taskRegistry.allTasks.filter { $0.state == .running || $0.state == .suspended })
379+
completionHandler(self.taskRegistry.allTasks.filter { $0.state == .running || $0.isSuspendedAfterResume })
356380
}
357381
}
358382
}

Foundation/URLSession/URLSessionTask.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ open class URLSessionTask : NSObject, NSCopying {
3535
internal let body: _Body
3636
fileprivate var _protocol: URLProtocol? = nil
3737
private let syncQ = DispatchQueue(label: "org.swift.URLSessionTask.SyncQ")
38+
private var hasTriggeredResume: Bool = false
39+
internal var isSuspendedAfterResume: Bool {
40+
return self.syncQ.sync { return self.hasTriggeredResume } && self.state == .suspended
41+
}
3842

3943
/// All operations must run on this queue.
4044
internal let workQueue: DispatchQueue
@@ -258,6 +262,7 @@ open class URLSessionTask : NSObject, NSCopying {
258262
guard 0 <= self.suspendCount else { fatalError("Resuming a task that's not suspended. Calls to resume() / suspend() need to be matched.") }
259263
self.updateTaskState()
260264
if self.suspendCount == 0 {
265+
self.hasTriggeredResume = true
261266
self.workQueue.async {
262267
if let _protocol = self._protocol {
263268
_protocol.startLoading()

TestFoundation/TestURLSession.swift

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class TestURLSession : LoopbackServerTest {
5151
("test_checkErrorTypeAfterInvalidateAndCancel", test_checkErrorTypeAfterInvalidateAndCancel),
5252
("test_taskCountAfterInvalidateAndCancel", test_taskCountAfterInvalidateAndCancel),
5353
("test_sessionDelegateAfterInvalidateAndCancel", test_sessionDelegateAfterInvalidateAndCancel),
54+
("test_getTasksWithCompletion", test_getTasksWithCompletion),
5455
]
5556
}
5657

@@ -795,32 +796,31 @@ class TestURLSession : LoopbackServerTest {
795796
session.invalidateAndCancel()
796797
waitForExpectations(timeout: 5)
797798
}
798-
799+
799800
func test_taskCountAfterInvalidateAndCancel() {
800801
let expect = expectation(description: "Check task count after invalidateAndCancel")
801-
802+
802803
let session = URLSession(configuration: .default)
803804
let task1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
804805
let task2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
805806
let task3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
806-
807+
807808
task1.resume()
808809
task2.resume()
809810
session.invalidateAndCancel()
810811
Thread.sleep(forTimeInterval: 1)
811-
812+
812813
session.getAllTasks { tasksBeforeResume in
813814
XCTAssertEqual(tasksBeforeResume.count, 0)
814-
815+
815816
// Resume a task after invalidating a session shouldn't change the task's status
816817
task3.resume()
817-
818+
818819
session.getAllTasks { tasksAfterResume in
819820
XCTAssertEqual(tasksAfterResume.count, 0)
820821
expect.fulfill()
821822
}
822823
}
823-
824824
waitForExpectations(timeout: 5)
825825
}
826826

@@ -832,6 +832,47 @@ class TestURLSession : LoopbackServerTest {
832832
XCTAssertNil(session.delegate)
833833
}
834834

835+
func test_getTasksWithCompletion() {
836+
let expect = expectation(description: "Check task count after invalidateAndCancel")
837+
838+
let session = URLSession(configuration: .default)
839+
let dataTask1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
840+
let dataTask2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
841+
let dataTask3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
842+
843+
let uploadTask1 = session.uploadTask(with: URLRequest(url: URL(string: "https://developer.apple.com")!), from: Data())
844+
let uploadTask2 = session.uploadTask(with: URLRequest(url: URL(string: "https://developer.apple.com/swift")!), from: Data())
845+
846+
let downloadTask1 = session.downloadTask(with: URL(string: "https://developer.apple.com/assets/elements/icons/brandmark/apple-developer-brandmark.svg")!)
847+
848+
session.getTasksWithCompletionHandler { (dataTasksBeforeCancel, uploadTasksBeforeCancel, downloadTasksBeforeCancel) in
849+
XCTAssertEqual(dataTasksBeforeCancel.count, 0)
850+
XCTAssertEqual(uploadTasksBeforeCancel.count, 0)
851+
XCTAssertEqual(downloadTasksBeforeCancel.count, 0)
852+
853+
dataTask1.cancel()
854+
dataTask2.resume()
855+
// dataTask3 is resumed and suspended, so this task should be a part of `getTasksWithCompletionHandler` response
856+
dataTask3.resume()
857+
dataTask3.suspend()
858+
859+
// uploadTask1 suspended even before it was resumed, so this task shouldn't be a part of `getTasksWithCompletionHandler` response
860+
uploadTask1.suspend()
861+
uploadTask2.resume()
862+
863+
downloadTask1.cancel()
864+
865+
session.getTasksWithCompletionHandler{ (dataTasksAfterCancel, uploadTasksAfterCancel, downloadTasksAfterCancel) in
866+
XCTAssertEqual(dataTasksAfterCancel.count, 2)
867+
XCTAssertEqual(uploadTasksAfterCancel.count, 1)
868+
XCTAssertEqual(downloadTasksAfterCancel.count, 0)
869+
expect.fulfill()
870+
}
871+
}
872+
873+
waitForExpectations(timeout: 20)
874+
}
875+
835876
}
836877

837878
class SharedDelegate: NSObject {

0 commit comments

Comments
 (0)