Skip to content

Commit dffdfe6

Browse files
authored
Merge pull request #2105 from karthikkeyan/karthik/SR-10428-gettaskswithcompletion
2 parents 25c881b + 1833606 commit dffdfe6

File tree

3 files changed

+125
-10
lines changed

3 files changed

+125
-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: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class TestURLSession : LoopbackServerTest {
5353
("test_checkErrorTypeAfterInvalidateAndCancel", test_checkErrorTypeAfterInvalidateAndCancel),
5454
("test_taskCountAfterInvalidateAndCancel", test_taskCountAfterInvalidateAndCancel),
5555
("test_sessionDelegateAfterInvalidateAndCancel", test_sessionDelegateAfterInvalidateAndCancel),
56+
("test_getAllTasks", test_getAllTasks),
57+
("test_getTasksWithCompletion", test_getTasksWithCompletion),
5658
]
5759
}
5860

@@ -819,32 +821,31 @@ class TestURLSession : LoopbackServerTest {
819821
session.invalidateAndCancel()
820822
waitForExpectations(timeout: 5)
821823
}
822-
824+
823825
func test_taskCountAfterInvalidateAndCancel() {
824826
let expect = expectation(description: "Check task count after invalidateAndCancel")
825-
827+
826828
let session = URLSession(configuration: .default)
827829
let task1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
828830
let task2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
829831
let task3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
830-
832+
831833
task1.resume()
832834
task2.resume()
833835
session.invalidateAndCancel()
834836
Thread.sleep(forTimeInterval: 1)
835-
837+
836838
session.getAllTasks { tasksBeforeResume in
837839
XCTAssertEqual(tasksBeforeResume.count, 0)
838-
840+
839841
// Resume a task after invalidating a session shouldn't change the task's status
840842
task3.resume()
841-
843+
842844
session.getAllTasks { tasksAfterResume in
843845
XCTAssertEqual(tasksAfterResume.count, 0)
844846
expect.fulfill()
845847
}
846848
}
847-
848849
waitForExpectations(timeout: 5)
849850
}
850851

@@ -856,6 +857,91 @@ class TestURLSession : LoopbackServerTest {
856857
XCTAssertNil(session.delegate)
857858
}
858859

860+
func test_getAllTasks() {
861+
let expect = expectation(description: "Tasks URLSession.getAllTasks")
862+
863+
let session = URLSession(configuration: .default)
864+
let dataTask1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
865+
let dataTask2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
866+
let dataTask3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
867+
868+
session.getAllTasks { (tasksBeforeResume) in
869+
XCTAssertEqual(tasksBeforeResume.count, 0)
870+
871+
dataTask1.cancel()
872+
873+
dataTask2.resume()
874+
dataTask2.suspend()
875+
// dataTask3 is suspended even before it was resumed, so the next call to `getAllTasks` should not include this tasks
876+
dataTask3.suspend()
877+
session.getAllTasks { (tasksAfterCancel) in
878+
// tasksAfterCancel should only contain dataTask2
879+
XCTAssertEqual(tasksAfterCancel.count, 1)
880+
881+
// A task will in be in suspended state when it was created.
882+
// Given that, dataTask3 was suspended once again earlier above, so it should receive `resume()` twice in order to be executed
883+
// Calling `getAllTasks` next time should not include dataTask3
884+
dataTask3.resume()
885+
886+
session.getAllTasks { (tasksAfterFirstResume) in
887+
// tasksAfterFirstResume should only contain dataTask2
888+
XCTAssertEqual(tasksAfterFirstResume.count, 1)
889+
890+
// Now dataTask3 received `resume()` twice, this time `getAllTasks` should include
891+
dataTask3.resume()
892+
session.getAllTasks { (tasksAfterSecondResume) in
893+
// tasksAfterSecondResume should contain dataTask2 and dataTask2 this time
894+
XCTAssertEqual(tasksAfterSecondResume.count, 2)
895+
expect.fulfill()
896+
}
897+
}
898+
}
899+
}
900+
901+
waitForExpectations(timeout: 20)
902+
}
903+
904+
func test_getTasksWithCompletion() {
905+
let expect = expectation(description: "Test URLSession.getTasksWithCompletion")
906+
907+
let session = URLSession(configuration: .default)
908+
let dataTask1 = session.dataTask(with: URL(string: "https://www.apple.com")!)
909+
let dataTask2 = session.dataTask(with: URL(string: "https://developer.apple.com")!)
910+
let dataTask3 = session.dataTask(with: URL(string: "https://developer.apple.com/swift")!)
911+
912+
let uploadTask1 = session.uploadTask(with: URLRequest(url: URL(string: "https://developer.apple.com")!), from: Data())
913+
let uploadTask2 = session.uploadTask(with: URLRequest(url: URL(string: "https://developer.apple.com/swift")!), from: Data())
914+
915+
let downloadTask1 = session.downloadTask(with: URL(string: "https://developer.apple.com/assets/elements/icons/brandmark/apple-developer-brandmark.svg")!)
916+
917+
session.getTasksWithCompletionHandler { (dataTasksBeforeCancel, uploadTasksBeforeCancel, downloadTasksBeforeCancel) in
918+
XCTAssertEqual(dataTasksBeforeCancel.count, 0)
919+
XCTAssertEqual(uploadTasksBeforeCancel.count, 0)
920+
XCTAssertEqual(downloadTasksBeforeCancel.count, 0)
921+
922+
dataTask1.cancel()
923+
dataTask2.resume()
924+
// dataTask3 is resumed and suspended, so this task should be a part of `getTasksWithCompletionHandler` response
925+
dataTask3.resume()
926+
dataTask3.suspend()
927+
928+
// uploadTask1 suspended even before it was resumed, so this task shouldn't be a part of `getTasksWithCompletionHandler` response
929+
uploadTask1.suspend()
930+
uploadTask2.resume()
931+
932+
downloadTask1.cancel()
933+
934+
session.getTasksWithCompletionHandler{ (dataTasksAfterCancel, uploadTasksAfterCancel, downloadTasksAfterCancel) in
935+
XCTAssertEqual(dataTasksAfterCancel.count, 2)
936+
XCTAssertEqual(uploadTasksAfterCancel.count, 1)
937+
XCTAssertEqual(downloadTasksAfterCancel.count, 0)
938+
expect.fulfill()
939+
}
940+
}
941+
942+
waitForExpectations(timeout: 20)
943+
}
944+
859945
}
860946

861947
class SharedDelegate: NSObject {

0 commit comments

Comments
 (0)