From 588b59e5b919787626bcb9728e0b682652244795 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 27 Aug 2022 19:39:57 -0400 Subject: [PATCH 01/20] fix: allow url async calls to run in parallel --- Sources/ParseSwift/API/API+Command.swift | 9 ++ .../API/ParseURLSessionDelegate.swift | 121 ++++++++++++++++-- .../ParseSwift/Extensions/URLSession.swift | 16 +++ .../ParseSwiftTests/ParseFileAsyncTests.swift | 78 +++++++++++ 4 files changed, 211 insertions(+), 13 deletions(-) diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 8aae02450..28ea391e6 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -60,10 +60,19 @@ internal extension API { case .success(let urlRequest): if method == .POST || method == .PUT || method == .PATCH { let task = URLSession.parse.uploadTask(withStreamedRequest: urlRequest) + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await ParseSwift.sessionDelegate.delegates.insertUpload(task, callback: uploadProgress) + await ParseSwift.sessionDelegate.delegates.insertStream(task, stream: stream) + await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: callbackQueue) + task.resume() + } + #else ParseSwift.sessionDelegate.uploadDelegates[task] = uploadProgress ParseSwift.sessionDelegate.streamDelegates[task] = stream ParseSwift.sessionDelegate.taskCallbackQueues[task] = callbackQueue task.resume() + #endif return } case .failure(let error): diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index 554b46dfa..bbaea3e24 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -22,6 +22,53 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg var streamDelegates = [URLSessionTask: InputStream]() var taskCallbackQueues = [URLSessionTask: DispatchQueue]() +#if compiler(>=5.5.2) && canImport(_Concurrency) + actor SessionDelegate { + var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() + var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() + var streamDelegates = [URLSessionTask: InputStream]() + var taskCallbackQueues = [URLSessionTask: DispatchQueue]() + + func insertDownload(_ task: URLSessionDownloadTask, + callback: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?) { + downloadDelegates[task] = callback + } + + func removeDownload(_ task: URLSessionDownloadTask) { + downloadDelegates.removeValue(forKey: task) + } + + func insertUpload(_ task: URLSessionTask, + callback: ((URLSessionTask, Int64, Int64, Int64) -> Void)?) { + uploadDelegates[task] = callback + } + + func removeUpload(_ task: URLSessionTask) { + uploadDelegates.removeValue(forKey: task) + } + + func insertStream(_ task: URLSessionTask, + stream: InputStream) { + streamDelegates[task] = stream + } + + func removeStream(_ task: URLSessionTask) { + streamDelegates.removeValue(forKey: task) + } + + func insertTask(_ task: URLSessionTask, + queue: DispatchQueue) { + taskCallbackQueues[task] = queue + } + + func removeTask(_ task: URLSessionTask) { + taskCallbackQueues.removeValue(forKey: task) + } + } + + var delegates = SessionDelegate() +#endif + init (callbackQueue: DispatchQueue, authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, @@ -49,17 +96,29 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { - if let callBack = uploadDelegates[task], - let queue = taskCallbackQueues[task] { - - queue.async { - callBack(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) - + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + if let callback = await delegates.uploadDelegates[task], + let queue = await delegates.taskCallbackQueues[task] { if totalBytesSent == totalBytesExpectedToSend { - self.uploadDelegates.removeValue(forKey: task) + await delegates.removeUpload(task) + } + queue.async { + callback(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } } } + #else + if let callback = uploadDelegates[task], + let queue = taskCallbackQueues[task] { + if totalBytesSent == totalBytesExpectedToSend { + uploadDelegates.removeValue(forKey: task) + } + queue.async { + callback(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } + } + #endif } func urlSession(_ session: URLSession, @@ -67,36 +126,72 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - - if let callBack = downloadDelegates[downloadTask], - let queue = taskCallbackQueues[downloadTask] { - queue.async { - callBack(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + if let callback = await delegates.downloadDelegates[downloadTask], + let queue = await delegates.taskCallbackQueues[downloadTask] { if totalBytesWritten == totalBytesExpectedToWrite { - self.downloadDelegates.removeValue(forKey: downloadTask) + await delegates.removeDownload(downloadTask) } + queue.async { + callback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } + } + } + #else + if let callback = downloadDelegates[downloadTask], + let queue = taskCallbackQueues[downloadTask] { + if totalBytesWritten == totalBytesExpectedToWrite { + downloadDelegates.removeValue(forKey: downloadTask) + } + queue.async { + callback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) } } + #endif } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await delegates.removeDownload(downloadTask) + await delegates.removeTask(downloadTask) + } + #else downloadDelegates.removeValue(forKey: downloadTask) taskCallbackQueues.removeValue(forKey: downloadTask) + #endif } func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + if let stream = await delegates.streamDelegates[task] { + completionHandler(stream) + } + } + #else if let stream = streamDelegates[task] { completionHandler(stream) } + #endif } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await delegates.removeUpload(task) + await delegates.removeStream(task) + await delegates.removeTask(task) + } + #else uploadDelegates.removeValue(forKey: task) streamDelegates.removeValue(forKey: task) taskCallbackQueues.removeValue(forKey: task) + #endif } } diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 0e4e882f7..319f83742 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -220,9 +220,17 @@ internal extension URLSession { completion(.failure(ParseError(code: .unknownError, message: "data and file both cannot be nil"))) } if let task = task { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await ParseSwift.sessionDelegate.delegates.insertUpload(task, callback: progress) + await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: notificationQueue) + task.resume() + } + #else ParseSwift.sessionDelegate.uploadDelegates[task] = progress ParseSwift.sessionDelegate.taskCallbackQueues[task] = notificationQueue task.resume() + #endif } } @@ -255,9 +263,17 @@ internal extension URLSession { } completion(result) } + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await ParseSwift.sessionDelegate.delegates.insertDownload(task, callback: progress) + await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: notificationQueue) + task.resume() + } + #else ParseSwift.sessionDelegate.downloadDelegates[task] = progress ParseSwift.sessionDelegate.taskCallbackQueues[task] = notificationQueue task.resume() + #endif } func downloadTask( diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index bb420d672..d070c0aa3 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -22,6 +22,36 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng let url: URL } + struct User: ParseUser { + + //: These are required by ParseObject + var objectId: String? + var createdAt: Date? + var updatedAt: Date? + var ACL: ParseACL? + var originalData: Data? + + // These are required by ParseUser + var username: String? + var email: String? + var emailVerified: Bool? + var password: String? + var authData: [String: [String: String]?]? + + // Your custom keys + var customKey: String? + + //: Implement your own version of merge + func merge(with object: Self) throws -> Self { + var updated = try mergeParse(with: object) + if updated.shouldRestoreKey(\.customKey, + original: object) { + updated.customKey = object.customKey + } + return updated + } + } + override func setUpWithError() throws { try super.setUpWithError() guard let url = URL(string: "http://localhost:1337/1") else { @@ -261,5 +291,53 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(error.message, serverResponse.message) } } +/* + func testDownloadFileWithThrowingTaskGroup() async throws { + try await User.login(username: "hello", password: "world") + guard let parseFileURL = URL(string: "https://www.jmir.org/2022/4/e29492/PDF") else { + XCTFail("Should create URL") + return + } + /* + // swiftlint:disable:next line_length + guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg") else { + XCTFail("Should create URL") + return + }*/ + var parseFile = ParseFile(name: "d3a37aed0672a024595b766f97133615_logo.svg", cloudURL: parseFileURL) + parseFile.url = parseFileURL +/* + let response = FileUploadResponse(name: "d3a37aed0672a024595b766f97133615_logo.svg", + url: parseFileURL) + let encoded: Data! + do { + encoded = try ParseCoding.jsonEncoder().encode(response) + } catch { + XCTFail("Should encode/decode. Error \(error)") + return + } + MockURLProtocol.mockRequests { _ in + return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) + } */ + // let immutableParseFile = parseFile + let files = try await withThrowingTaskGroup(of: ParseFile.self) { group -> [ParseFile] in + var files = [ParseFile]() + + for index in 0..<20 { + let parseFile = ParseFile(name: "\(index)_d3a37aed0672a024595b766f97133615_logo.svg", + cloudURL: parseFileURL) + group.addTask { + return try await parseFile.save() + } + } + + for try await file in group { + files.append(file) + } + + return files + } + } + */ } #endif From 7db15663904060b464b9d624dfe733ec0a5c2cdd Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 27 Aug 2022 19:40:56 -0400 Subject: [PATCH 02/20] revert --- .../ParseSwiftTests/ParseFileAsyncTests.swift | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index d070c0aa3..bb420d672 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -22,36 +22,6 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng let url: URL } - struct User: ParseUser { - - //: These are required by ParseObject - var objectId: String? - var createdAt: Date? - var updatedAt: Date? - var ACL: ParseACL? - var originalData: Data? - - // These are required by ParseUser - var username: String? - var email: String? - var emailVerified: Bool? - var password: String? - var authData: [String: [String: String]?]? - - // Your custom keys - var customKey: String? - - //: Implement your own version of merge - func merge(with object: Self) throws -> Self { - var updated = try mergeParse(with: object) - if updated.shouldRestoreKey(\.customKey, - original: object) { - updated.customKey = object.customKey - } - return updated - } - } - override func setUpWithError() throws { try super.setUpWithError() guard let url = URL(string: "http://localhost:1337/1") else { @@ -291,53 +261,5 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(error.message, serverResponse.message) } } -/* - func testDownloadFileWithThrowingTaskGroup() async throws { - try await User.login(username: "hello", password: "world") - guard let parseFileURL = URL(string: "https://www.jmir.org/2022/4/e29492/PDF") else { - XCTFail("Should create URL") - return - } - /* - // swiftlint:disable:next line_length - guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg") else { - XCTFail("Should create URL") - return - }*/ - var parseFile = ParseFile(name: "d3a37aed0672a024595b766f97133615_logo.svg", cloudURL: parseFileURL) - parseFile.url = parseFileURL -/* - let response = FileUploadResponse(name: "d3a37aed0672a024595b766f97133615_logo.svg", - url: parseFileURL) - let encoded: Data! - do { - encoded = try ParseCoding.jsonEncoder().encode(response) - } catch { - XCTFail("Should encode/decode. Error \(error)") - return - } - MockURLProtocol.mockRequests { _ in - return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0) - } */ - // let immutableParseFile = parseFile - let files = try await withThrowingTaskGroup(of: ParseFile.self) { group -> [ParseFile] in - var files = [ParseFile]() - - for index in 0..<20 { - let parseFile = ParseFile(name: "\(index)_d3a37aed0672a024595b766f97133615_logo.svg", - cloudURL: parseFileURL) - group.addTask { - return try await parseFile.save() - } - } - - for try await file in group { - files.append(file) - } - - return files - } - } - */ } #endif From f7762bcd16f7806a50bd28c294b0d88772d47f92 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 27 Aug 2022 21:41:53 -0400 Subject: [PATCH 03/20] add tests --- .swiftlint.yml | 1 + Sources/ParseSwift/API/API+Command.swift | 6 +-- .../API/ParseURLSessionDelegate.swift | 22 ++++++----- .../ParseSwift/Extensions/URLSession.swift | 8 ++-- .../ParseSwiftTests/ParseFileAsyncTests.swift | 39 +++++++++++++++++++ 5 files changed, 59 insertions(+), 17 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 67ac4f0e5..a19854686 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -5,6 +5,7 @@ disabled_rules: - type_body_length - inclusive_language - comment_spacing + - identifier_name excluded: # paths to ignore during linting. Takes precedence over `included`. - Tests/ParseSwiftTests/ParseEncoderTests - DerivedData diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index 28ea391e6..afea63503 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -62,9 +62,9 @@ internal extension API { let task = URLSession.parse.uploadTask(withStreamedRequest: urlRequest) #if compiler(>=5.5.2) && canImport(_Concurrency) Task { - await ParseSwift.sessionDelegate.delegates.insertUpload(task, callback: uploadProgress) - await ParseSwift.sessionDelegate.delegates.insertStream(task, stream: stream) - await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: callbackQueue) + await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadProgress) + await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: stream) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: callbackQueue) task.resume() } #else diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index bbaea3e24..3531b2969 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -17,19 +17,15 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg var authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? - var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() - var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() - var streamDelegates = [URLSessionTask: InputStream]() - var taskCallbackQueues = [URLSessionTask: DispatchQueue]() -#if compiler(>=5.5.2) && canImport(_Concurrency) + #if compiler(>=5.5.2) && canImport(_Concurrency) actor SessionDelegate { var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() var streamDelegates = [URLSessionTask: InputStream]() var taskCallbackQueues = [URLSessionTask: DispatchQueue]() - func insertDownload(_ task: URLSessionDownloadTask, + func updateDownload(_ task: URLSessionDownloadTask, callback: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?) { downloadDelegates[task] = callback } @@ -38,7 +34,7 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg downloadDelegates.removeValue(forKey: task) } - func insertUpload(_ task: URLSessionTask, + func updateUpload(_ task: URLSessionTask, callback: ((URLSessionTask, Int64, Int64, Int64) -> Void)?) { uploadDelegates[task] = callback } @@ -47,7 +43,7 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg uploadDelegates.removeValue(forKey: task) } - func insertStream(_ task: URLSessionTask, + func updateStream(_ task: URLSessionTask, stream: InputStream) { streamDelegates[task] = stream } @@ -56,7 +52,7 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg streamDelegates.removeValue(forKey: task) } - func insertTask(_ task: URLSessionTask, + func updateTask(_ task: URLSessionTask, queue: DispatchQueue) { taskCallbackQueues[task] = queue } @@ -67,7 +63,13 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg } var delegates = SessionDelegate() -#endif + + #else + var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() + var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() + var streamDelegates = [URLSessionTask: InputStream]() + var taskCallbackQueues = [URLSessionTask: DispatchQueue]() + #endif init (callbackQueue: DispatchQueue, authentication: ((URLAuthenticationChallenge, diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 319f83742..ff2a77771 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -222,8 +222,8 @@ internal extension URLSession { if let task = task { #if compiler(>=5.5.2) && canImport(_Concurrency) Task { - await ParseSwift.sessionDelegate.delegates.insertUpload(task, callback: progress) - await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: notificationQueue) + await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: progress) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: notificationQueue) task.resume() } #else @@ -265,8 +265,8 @@ internal extension URLSession { } #if compiler(>=5.5.2) && canImport(_Concurrency) Task { - await ParseSwift.sessionDelegate.delegates.insertDownload(task, callback: progress) - await ParseSwift.sessionDelegate.delegates.insertTask(task, queue: notificationQueue) + await ParseSwift.sessionDelegate.delegates.updateDownload(task, callback: progress) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: notificationQueue) task.resume() } #else diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index bb420d672..3272479da 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -261,5 +261,44 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(error.message, serverResponse.message) } } + + @MainActor + func testURLSessionDelegates() async throws { + // swiftlint:disable:next line_length + let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = dowloadTask as URLSessionTask + // swiftlint:disable:next line_length + let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, _: Int64, _: Int64) -> Void in } + // swiftlint:disable:next line_length + let dowbloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } + + // Add tasks + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(taskCount, 1) + await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init()) + let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + XCTAssertEqual(streamCount, 1) + await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) + let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + XCTAssertEqual(uploadCount, 1) + await ParseSwift.sessionDelegate.delegates.updateDownload(dowloadTask, callback: dowbloadCompletion) + let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + XCTAssertEqual(downloadCount, 1) + + // Remove tasks + await ParseSwift.sessionDelegate.delegates.removeTask(task) + let taskCount2 = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(taskCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeStream(task) + let streamCount2 = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + XCTAssertEqual(streamCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeUpload(task) + let uploadCount2 = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + XCTAssertEqual(uploadCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeDownload(dowloadTask) + let downloadCount2 = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + XCTAssertEqual(downloadCount2, 0) + } } #endif From 566a3ddf0ae020efc0cefbf56ebccb048eb5a6dc Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 27 Aug 2022 21:45:08 -0400 Subject: [PATCH 04/20] add changelog --- CHANGELOG.md | 7 ++++++- Sources/ParseSwift/ParseConstants.swift | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3065c69e1..d06e49ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.9.0...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.9.1...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 4.9.1 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.9.0...4.9.1) +__Fixes__ +- Use an actor for the url session delegates to ensure thread safety when making async calls in parallel ([#394](https://github.com/parse-community/Parse-Swift/pull/394)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 4.9.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.8.0...4.9.0) diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index f6462b348..5e54c38ab 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "4.9.0" + static let version = "4.9.1" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" From f31ce78cff1e89293e58052e27d31ed2f01764cb Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:25:00 -0400 Subject: [PATCH 05/20] improve parse url session --- .../API/ParseURLSessionDelegate.swift | 68 ++++++++++--------- .../ParseSwift/Extensions/URLSession.swift | 16 +++-- Sources/ParseSwift/Parse.swift | 2 + .../ParseSwiftTests/ParseFileAsyncTests.swift | 43 +++++++++++- Tests/ParseSwiftTests/ParseFileTests.swift | 34 +++++++++- Tests/ParseSwiftTests/ParseSessionTests.swift | 62 +++++++++++++++++ 6 files changed, 185 insertions(+), 40 deletions(-) diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index 3531b2969..44bc4facd 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -11,7 +11,7 @@ import Foundation import FoundationNetworking #endif -class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate +class ParseURLSessionDelegate: NSObject { var callbackQueue: DispatchQueue var authentication: ((URLAuthenticationChallenge, @@ -79,7 +79,9 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg self.authentication = authentication super.init() } +} +extension ParseURLSessionDelegate: URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, @@ -92,7 +94,9 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg completionHandler(.performDefaultHandling, nil) } } +} +extension ParseURLSessionDelegate: URLSessionDataDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, @@ -123,6 +127,38 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg #endif } + func urlSession(_ session: URLSession, + task: URLSessionTask, + needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + if let stream = await delegates.streamDelegates[task] { + completionHandler(stream) + } + } + #else + if let stream = streamDelegates[task] { + completionHandler(stream) + } + #endif + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await delegates.removeUpload(task) + await delegates.removeStream(task) + await delegates.removeTask(task) + } + #else + uploadDelegates.removeValue(forKey: task) + streamDelegates.removeValue(forKey: task) + taskCallbackQueues.removeValue(forKey: task) + #endif + } +} + +extension ParseURLSessionDelegate: URLSessionDownloadDelegate { func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, @@ -166,34 +202,4 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg taskCallbackQueues.removeValue(forKey: downloadTask) #endif } - - func urlSession(_ session: URLSession, - task: URLSessionTask, - needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { - #if compiler(>=5.5.2) && canImport(_Concurrency) - Task { - if let stream = await delegates.streamDelegates[task] { - completionHandler(stream) - } - } - #else - if let stream = streamDelegates[task] { - completionHandler(stream) - } - #endif - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - #if compiler(>=5.5.2) && canImport(_Concurrency) - Task { - await delegates.removeUpload(task) - await delegates.removeStream(task) - await delegates.removeTask(task) - } - #else - uploadDelegates.removeValue(forKey: task) - streamDelegates.removeValue(forKey: task) - taskCallbackQueues.removeValue(forKey: task) - #endif - } } diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index ff2a77771..ca3e7fb38 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -13,21 +13,25 @@ import FoundationNetworking #endif internal extension URLSession { - static let parse: URLSession = { + static var parse = URLSession.shared + + static func updateParseURLSession() { if !ParseSwift.configuration.isTestingSDK { let configuration = URLSessionConfiguration.default configuration.urlCache = URLCache.parse configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders - return URLSession(configuration: configuration, - delegate: ParseSwift.sessionDelegate, - delegateQueue: nil) + Self.parse = URLSession(configuration: configuration, + delegate: ParseSwift.sessionDelegate, + delegateQueue: nil) } else { let session = URLSession.shared session.configuration.urlCache = URLCache.parse - return URLSession.shared + session.configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy + session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders + Self.parse = URLSession.shared } - }() + } static func reconnectInterval(_ maxExponent: Int) -> Int { let min = NSDecimalNumber(decimal: Swift.min(30, pow(2, maxExponent) - 1)) diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 2ad7c62c8..d61f012eb 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -267,6 +267,7 @@ public struct ParseSwift { Self.configuration = configuration Self.sessionDelegate = ParseURLSessionDelegate(callbackQueue: .main, authentication: configuration.authentication) + URLSession.updateParseURLSession() deleteKeychainIfNeeded() #if !os(Linux) && !os(Android) && !os(Windows) @@ -626,6 +627,7 @@ public struct ParseSwift { URLCredential?) -> Void) -> Void)?) { Self.sessionDelegate = ParseURLSessionDelegate(callbackQueue: .main, authentication: authentication) + URLSession.updateParseURLSession() } #if !os(Linux) && !os(Android) && !os(Windows) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index 3272479da..a234af5b0 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -263,7 +263,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } @MainActor - func testURLSessionDelegates() async throws { + func testParseURLSessionDelegates() async throws { // swiftlint:disable:next line_length let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = dowloadTask as URLSessionTask @@ -276,7 +276,46 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count XCTAssertEqual(taskCount, 1) - await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init()) + await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init(data: .init())) + let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + XCTAssertEqual(streamCount, 1) + await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) + let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + XCTAssertEqual(uploadCount, 1) + await ParseSwift.sessionDelegate.delegates.updateDownload(dowloadTask, callback: dowbloadCompletion) + let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + XCTAssertEqual(downloadCount, 1) + + // Remove tasks + await ParseSwift.sessionDelegate.delegates.removeTask(task) + let taskCount2 = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(taskCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeStream(task) + let streamCount2 = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + XCTAssertEqual(streamCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeUpload(task) + let uploadCount2 = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + XCTAssertEqual(uploadCount2, 0) + await ParseSwift.sessionDelegate.delegates.removeDownload(dowloadTask) + let downloadCount2 = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + XCTAssertEqual(downloadCount2, 0) + } + + @MainActor + func testParseURLSessionDelegateCalls() async throws { + // swiftlint:disable:next line_length + let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = dowloadTask as URLSessionTask + // swiftlint:disable:next line_length + let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, _: Int64, _: Int64) -> Void in } + // swiftlint:disable:next line_length + let dowbloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } + + // Add tasks + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(taskCount, 1) + await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init(data: .init())) let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count XCTAssertEqual(streamCount, 1) await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 10701ec4c..d64525619 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -680,9 +680,41 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length wait(for: [expectation1], timeout: 20.0) } + #if compiler(<5.5.2) + func testParseURLSessionDelegates() throws { + // swiftlint:disable:next line_length + let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = dowloadTask as URLSessionTask + // swiftlint:disable:next line_length + let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, _: Int64, _: Int64) -> Void in } + // swiftlint:disable:next line_length + let dowbloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } + + // Add tasks + ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues[task]?.count, 1) + ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates[task]?.count, 1) + ParseSwift.sessionDelegate.uploadDelegates[task] = uploadCompletion + XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates[task]?.count, 1) + ParseSwift.sessionDelegate.downloadDelegates[task] = dowbloadCompletion + XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates[task]?.count, 1) + + // Remove tasks + ParseSwift.sessionDelegate.taskCallbackQueues.removeValue(forKey: task) + XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues[task]?.count, 0) + ParseSwift.sessionDelegate.streamDelegates.removeValue(forKey: task) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates[task]?.count, 0) + ParseSwift.sessionDelegate.uploadDelegates.removeValue(forKey: task) + XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates[task]?.count, 0) + ParseSwift.sessionDelegate.downloadDelegates.removeValue(forKey: dowloadTask) + XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates[dowloadTask]?.count, 0) + } + #endif + #if !os(Linux) && !os(Android) && !os(Windows) - //URL Mocker is not able to mock this in linux and tests fail, so do not run. + // URL Mocker is not able to mock this in linux and tests fail, so do not run. func testFetchFileCancelAsync() throws { // swiftlint:disable:next line_length guard let parseFileURL = URL(string: "http://localhost:1337/1/files/applicationId/7793939a2e59b98138c1bbf2412a060c_logo.svg") else { diff --git a/Tests/ParseSwiftTests/ParseSessionTests.swift b/Tests/ParseSwiftTests/ParseSessionTests.swift index 6644dbb20..cdb85f33b 100644 --- a/Tests/ParseSwiftTests/ParseSessionTests.swift +++ b/Tests/ParseSwiftTests/ParseSessionTests.swift @@ -76,6 +76,7 @@ class ParseSessionTests: XCTestCase { try KeychainStore.shared.deleteAll() #endif try ParseStorage.shared.deleteAll() + ParseSwift.configuration = nil } func testFetchCommand() throws { @@ -101,4 +102,65 @@ class ParseSessionTests: XCTestCase { session.objectId = "me" XCTAssertEqual(session.endpoint.urlComponent, "/sessions/me") } + + func testParseURLSession() throws { + XCTAssertEqual(URLSession.parse.configuration.requestCachePolicy, + ParseSwift.configuration.requestCachePolicy) + XCTAssertEqual(URLSession.parse.configuration.httpAdditionalHeaders?.count, + ParseSwift.configuration.httpAdditionalHeaders?.count) + guard let delegate = URLSession.parse.delegate as? ParseURLSessionDelegate else { + XCTFail("Should have casted") + return + } + XCTAssertEqual(delegate, ParseSwift.sessionDelegate) + } + + func testParseURLSessionDefaultCertificatePinning() throws { + let expectation1 = XCTestExpectation(description: "Authentication") + URLSession.parse.delegate?.urlSession?(URLSession.parse, + didReceive: .init()) { (challenge, credential) -> Void in + XCTAssertEqual(challenge, .performDefaultHandling) + XCTAssertNil(credential) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testParseURLSessionCustomCertificatePinning() throws { + guard let url = URL(string: "http://localhost:1337/1") else { + XCTFail("Should create valid URL") + return + } + ParseSwift.initialize(applicationId: "applicationId", + clientKey: "clientKey", + masterKey: "masterKey", + serverURL: url, + // swiftlint:disable:next line_length + testing: false) {(_: URLAuthenticationChallenge, completion: (_: URLSession.AuthChallengeDisposition, _: URLCredential?) -> Void) in + completion(.cancelAuthenticationChallenge, .none) + } + let expectation1 = XCTestExpectation(description: "Authentication") + URLSession.parse.delegate?.urlSession?(URLSession.parse, + didReceive: .init()) { (challenge, credential) -> Void in + XCTAssertEqual(challenge, .cancelAuthenticationChallenge) + XCTAssertEqual(credential, .none) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } + + func testParseURLSessionUpdateCertificatePinning() throws { + // swiftlint:disable:next line_length + ParseSwift.updateAuthentication({(_: URLAuthenticationChallenge, completion: (_: URLSession.AuthChallengeDisposition, _: URLCredential?) -> Void) in + completion(.cancelAuthenticationChallenge, .none) + }) + let expectation1 = XCTestExpectation(description: "Authentication") + URLSession.parse.delegate?.urlSession?(URLSession.parse, + didReceive: .init()) { (challenge, credential) -> Void in + XCTAssertEqual(challenge, .cancelAuthenticationChallenge) + XCTAssertEqual(credential, .none) + expectation1.fulfill() + } + wait(for: [expectation1], timeout: 10.0) + } } From 3686d1723a26decc5c8c01f89c847ef60a2d50e6 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:35:20 -0400 Subject: [PATCH 06/20] don't test on linux --- Tests/ParseSwiftTests/ParseSessionTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/ParseSwiftTests/ParseSessionTests.swift b/Tests/ParseSwiftTests/ParseSessionTests.swift index cdb85f33b..63f54e6b1 100644 --- a/Tests/ParseSwiftTests/ParseSessionTests.swift +++ b/Tests/ParseSwiftTests/ParseSessionTests.swift @@ -103,6 +103,7 @@ class ParseSessionTests: XCTestCase { XCTAssertEqual(session.endpoint.urlComponent, "/sessions/me") } + #if !os(Linux) && !os(Android) && !os(Windows) func testParseURLSession() throws { XCTAssertEqual(URLSession.parse.configuration.requestCachePolicy, ParseSwift.configuration.requestCachePolicy) @@ -163,4 +164,5 @@ class ParseSessionTests: XCTestCase { } wait(for: [expectation1], timeout: 10.0) } + #endif } From 165c315869b08864f605c90ec248a3aa0cca6236 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:40:04 -0400 Subject: [PATCH 07/20] fix build on old swift --- Tests/ParseSwiftTests/ParseFileTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index d64525619..aaf9688e1 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -692,23 +692,23 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // Add tasks ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main - XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues[task]?.count, 1) + XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues.count, 1) ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) - XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates[task]?.count, 1) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates.count, 1) ParseSwift.sessionDelegate.uploadDelegates[task] = uploadCompletion - XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates[task]?.count, 1) + XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates.count, 1) ParseSwift.sessionDelegate.downloadDelegates[task] = dowbloadCompletion - XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates[task]?.count, 1) + XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates.count, 1) // Remove tasks ParseSwift.sessionDelegate.taskCallbackQueues.removeValue(forKey: task) - XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues[task]?.count, 0) + XCTAssertEqual(ParseSwift.sessionDelegate.taskCallbackQueues.count, 0) ParseSwift.sessionDelegate.streamDelegates.removeValue(forKey: task) - XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates[task]?.count, 0) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates.count, 0) ParseSwift.sessionDelegate.uploadDelegates.removeValue(forKey: task) - XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates[task]?.count, 0) + XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates.count, 0) ParseSwift.sessionDelegate.downloadDelegates.removeValue(forKey: dowloadTask) - XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates[dowloadTask]?.count, 0) + XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates.count, 0) } #endif From 1493bc8c23be38f0ffdc155a99d9ce5ddb515e18 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:43:29 -0400 Subject: [PATCH 08/20] revert url session for linux tests --- Sources/ParseSwift/Extensions/URLSession.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index ca3e7fb38..37b148c38 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -27,8 +27,6 @@ internal extension URLSession { } else { let session = URLSession.shared session.configuration.urlCache = URLCache.parse - session.configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy - session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders Self.parse = URLSession.shared } } From b5c92518a3c30caaa7fba617995497e1e475798a Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:49:56 -0400 Subject: [PATCH 09/20] test not running user tests on linux --- Tests/ParseSwiftTests/ParseUserTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index 3fca61e97..b383f96d5 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -5,7 +5,7 @@ // Created by Corey Baker on 7/21/20. // Copyright © 2020 Parse Community. All rights reserved. // - +#if !os(Linux) && !os(Android) && !os(Windows) import Foundation import XCTest @testable import ParseSwift @@ -2766,3 +2766,4 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } // swiftlint:disable:this file_length +#endif From fb6b3c488921b5cb0bb05a73f253aa07dcf04eba Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 13:54:47 -0400 Subject: [PATCH 10/20] fix test on older linux --- Tests/ParseSwiftTests/ParseFileTests.swift | 2 +- Tests/ParseSwiftTests/ParseUserAsyncTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index aaf9688e1..04e7ed39c 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -697,7 +697,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates.count, 1) ParseSwift.sessionDelegate.uploadDelegates[task] = uploadCompletion XCTAssertEqual(ParseSwift.sessionDelegate.uploadDelegates.count, 1) - ParseSwift.sessionDelegate.downloadDelegates[task] = dowbloadCompletion + ParseSwift.sessionDelegate.downloadDelegates[dowloadTask] = dowbloadCompletion XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates.count, 1) // Remove tasks diff --git a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift index db3e314f3..41e5b6e6a 100644 --- a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift @@ -5,7 +5,7 @@ // Created by Corey Baker on 8/6/21. // Copyright © 2021 Parse Community. All rights reserved. // - +#if !os(Linux) && !os(Android) && !os(Windows) #if compiler(>=5.5.2) && canImport(_Concurrency) import Foundation #if canImport(FoundationNetworking) @@ -1514,3 +1514,4 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } #endif +#endif From 776a52faea114d20bc74aa628ef15abfe94f05a9 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 14:12:45 -0400 Subject: [PATCH 11/20] fix session for tests --- Sources/ParseSwift/Extensions/URLSession.swift | 4 +++- Tests/ParseSwiftTests/ParseUserAsyncTests.swift | 3 +-- Tests/ParseSwiftTests/ParseUserTests.swift | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 37b148c38..ec1f3224e 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -27,7 +27,9 @@ internal extension URLSession { } else { let session = URLSession.shared session.configuration.urlCache = URLCache.parse - Self.parse = URLSession.shared + session.configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy + session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders + Self.parse = session } } diff --git a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift index 41e5b6e6a..db3e314f3 100644 --- a/Tests/ParseSwiftTests/ParseUserAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseUserAsyncTests.swift @@ -5,7 +5,7 @@ // Created by Corey Baker on 8/6/21. // Copyright © 2021 Parse Community. All rights reserved. // -#if !os(Linux) && !os(Android) && !os(Windows) + #if compiler(>=5.5.2) && canImport(_Concurrency) import Foundation #if canImport(FoundationNetworking) @@ -1514,4 +1514,3 @@ class ParseUserAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } #endif -#endif diff --git a/Tests/ParseSwiftTests/ParseUserTests.swift b/Tests/ParseSwiftTests/ParseUserTests.swift index b383f96d5..3fca61e97 100644 --- a/Tests/ParseSwiftTests/ParseUserTests.swift +++ b/Tests/ParseSwiftTests/ParseUserTests.swift @@ -5,7 +5,7 @@ // Created by Corey Baker on 7/21/20. // Copyright © 2020 Parse Community. All rights reserved. // -#if !os(Linux) && !os(Android) && !os(Windows) + import Foundation import XCTest @testable import ParseSwift @@ -2766,4 +2766,3 @@ class ParseUserTests: XCTestCase { // swiftlint:disable:this type_body_length } } // swiftlint:disable:this file_length -#endif From ea86749a5352dae6f797f6d454b2e4f16b9c92b7 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 14:22:09 -0400 Subject: [PATCH 12/20] fix test session for linux --- Sources/ParseSwift/Extensions/URLSession.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index ec1f3224e..958583764 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -16,6 +16,7 @@ internal extension URLSession { static var parse = URLSession.shared static func updateParseURLSession() { + #if !os(Linux) && !os(Android) && !os(Windows) if !ParseSwift.configuration.isTestingSDK { let configuration = URLSessionConfiguration.default configuration.urlCache = URLCache.parse @@ -31,6 +32,16 @@ internal extension URLSession { session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders Self.parse = session } + #else + if !ParseSwift.configuration.isTestingSDK { + let configuration = URLSessionConfiguration.default + Self.parse = URLSession(configuration: configuration, + delegate: ParseSwift.sessionDelegate, + delegateQueue: nil) + } else { + Self.parse = URLSession.shared + } + #endif } static func reconnectInterval(_ maxExponent: Int) -> Int { From 680ef492b5a5cb41fa361c77bba5da1f078fda15 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 14:49:39 -0400 Subject: [PATCH 13/20] test old setup for linux --- .../ParseSwift/Extensions/URLSession.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 958583764..67cf83e68 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -13,9 +13,25 @@ import FoundationNetworking #endif internal extension URLSession { - static var parse = URLSession.shared + static var parse: URLSession = /* URLSession.shared */ { + if !ParseSwift.configuration.isTestingSDK { + let configuration = URLSessionConfiguration.default + configuration.urlCache = URLCache.parse + configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy + configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders + return URLSession(configuration: configuration, + delegate: ParseSwift.sessionDelegate, + delegateQueue: nil) + } else { + let session = URLSession.shared + session.configuration.urlCache = URLCache.parse + session.configuration.requestCachePolicy = ParseSwift.configuration.requestCachePolicy + session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders + return session + } + }() - static func updateParseURLSession() { + static func updateParseURLSession() { /* #if !os(Linux) && !os(Android) && !os(Windows) if !ParseSwift.configuration.isTestingSDK { let configuration = URLSessionConfiguration.default @@ -41,7 +57,7 @@ internal extension URLSession { } else { Self.parse = URLSession.shared } - #endif + #endif */ } static func reconnectInterval(_ maxExponent: Int) -> Int { From 49eaaab3f1c70730456c650bc22832ff613baaa1 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 14:58:12 -0400 Subject: [PATCH 14/20] working url session for all OS's --- Sources/ParseSwift/Extensions/URLSession.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index 67cf83e68..730eca314 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -13,6 +13,9 @@ import FoundationNetworking #endif internal extension URLSession { + #if !os(Linux) && !os(Android) && !os(Windows) + static var parse = URLSession.shared + #else static var parse: URLSession = /* URLSession.shared */ { if !ParseSwift.configuration.isTestingSDK { let configuration = URLSessionConfiguration.default @@ -30,8 +33,9 @@ internal extension URLSession { return session } }() + #endif - static func updateParseURLSession() { /* + static func updateParseURLSession() { #if !os(Linux) && !os(Android) && !os(Windows) if !ParseSwift.configuration.isTestingSDK { let configuration = URLSessionConfiguration.default @@ -48,16 +52,7 @@ internal extension URLSession { session.configuration.httpAdditionalHeaders = ParseSwift.configuration.httpAdditionalHeaders Self.parse = session } - #else - if !ParseSwift.configuration.isTestingSDK { - let configuration = URLSessionConfiguration.default - Self.parse = URLSession(configuration: configuration, - delegate: ParseSwift.sessionDelegate, - delegateQueue: nil) - } else { - Self.parse = URLSession.shared - } - #endif */ + #endif } static func reconnectInterval(_ maxExponent: Int) -> Int { From d770d082196cefda4722df27a3b2a9c154457299 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 17:46:13 -0400 Subject: [PATCH 15/20] add delegate tests --- .../API/ParseURLSessionDelegate.swift | 23 +-- .../ParseSwiftTests/ParseFileAsyncTests.swift | 158 ++++++++++++++---- 2 files changed, 133 insertions(+), 48 deletions(-) diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index 44bc4facd..33256f142 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -32,6 +32,7 @@ class ParseURLSessionDelegate: NSObject func removeDownload(_ task: URLSessionDownloadTask) { downloadDelegates.removeValue(forKey: task) + taskCallbackQueues.removeValue(forKey: task) } func updateUpload(_ task: URLSessionTask, @@ -41,6 +42,7 @@ class ParseURLSessionDelegate: NSObject func removeUpload(_ task: URLSessionTask) { uploadDelegates.removeValue(forKey: task) + taskCallbackQueues.removeValue(forKey: task) } func updateStream(_ task: URLSessionTask, @@ -50,6 +52,7 @@ class ParseURLSessionDelegate: NSObject func removeStream(_ task: URLSessionTask) { streamDelegates.removeValue(forKey: task) + taskCallbackQueues.removeValue(forKey: task) } func updateTask(_ task: URLSessionTask, @@ -106,9 +109,6 @@ extension ParseURLSessionDelegate: URLSessionDataDelegate { Task { if let callback = await delegates.uploadDelegates[task], let queue = await delegates.taskCallbackQueues[task] { - if totalBytesSent == totalBytesExpectedToSend { - await delegates.removeUpload(task) - } queue.async { callback(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } @@ -117,9 +117,6 @@ extension ParseURLSessionDelegate: URLSessionDataDelegate { #else if let callback = uploadDelegates[task], let queue = taskCallbackQueues[task] { - if totalBytesSent == totalBytesExpectedToSend { - uploadDelegates.removeValue(forKey: task) - } queue.async { callback(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } @@ -148,12 +145,17 @@ extension ParseURLSessionDelegate: URLSessionDataDelegate { Task { await delegates.removeUpload(task) await delegates.removeStream(task) - await delegates.removeTask(task) + if let downloadTask = task as? URLSessionDownloadTask { + await delegates.removeDownload(downloadTask) + } } #else uploadDelegates.removeValue(forKey: task) streamDelegates.removeValue(forKey: task) taskCallbackQueues.removeValue(forKey: task) + if let downloadTask = task as? URLSessionDownloadTask { + await downloadDelegates.removeValue(forKey: downloadTask) + } #endif } } @@ -168,9 +170,6 @@ extension ParseURLSessionDelegate: URLSessionDownloadDelegate { Task { if let callback = await delegates.downloadDelegates[downloadTask], let queue = await delegates.taskCallbackQueues[downloadTask] { - if totalBytesWritten == totalBytesExpectedToWrite { - await delegates.removeDownload(downloadTask) - } queue.async { callback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) } @@ -179,9 +178,6 @@ extension ParseURLSessionDelegate: URLSessionDownloadDelegate { #else if let callback = downloadDelegates[downloadTask], let queue = taskCallbackQueues[downloadTask] { - if totalBytesWritten == totalBytesExpectedToWrite { - downloadDelegates.removeValue(forKey: downloadTask) - } queue.async { callback(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) } @@ -195,7 +191,6 @@ extension ParseURLSessionDelegate: URLSessionDownloadDelegate { #if compiler(>=5.5.2) && canImport(_Concurrency) Task { await delegates.removeDownload(downloadTask) - await delegates.removeTask(downloadTask) } #else downloadDelegates.removeValue(forKey: downloadTask) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index a234af5b0..72696cab5 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -265,12 +265,12 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng @MainActor func testParseURLSessionDelegates() async throws { // swiftlint:disable:next line_length - let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) - let task = dowloadTask as URLSessionTask + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask // swiftlint:disable:next line_length let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, _: Int64, _: Int64) -> Void in } // swiftlint:disable:next line_length - let dowbloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } + let downloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } // Add tasks await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) @@ -282,7 +282,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count XCTAssertEqual(uploadCount, 1) - await ParseSwift.sessionDelegate.delegates.updateDownload(dowloadTask, callback: dowbloadCompletion) + await ParseSwift.sessionDelegate.delegates.updateDownload(downloadTask, callback: downloadCompletion) let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count XCTAssertEqual(downloadCount, 1) @@ -296,48 +296,138 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng await ParseSwift.sessionDelegate.delegates.removeUpload(task) let uploadCount2 = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count XCTAssertEqual(uploadCount2, 0) - await ParseSwift.sessionDelegate.delegates.removeDownload(dowloadTask) + await ParseSwift.sessionDelegate.delegates.removeDownload(downloadTask) let downloadCount2 = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count XCTAssertEqual(downloadCount2, 0) } - @MainActor - func testParseURLSessionDelegateCalls() async throws { + func testParseURLSessionDelegateUpload() async throws { // swiftlint:disable:next line_length - let dowloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) - let task = dowloadTask as URLSessionTask + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + // swiftlint:disable:next line_length - let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, _: Int64, _: Int64) -> Void in } + let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, sent: Int64, total: Int64) -> Void in + if sent < total { + Task { + let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(uploadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + Task { + let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(uploadCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + } + } + } + + // Add tasks + await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + task: task, + didSendBodyData: 0, + totalBytesSent: 0, + totalBytesExpectedToSend: 10) + wait(for: [expectation1, expectation2], timeout: 20.0) + } + + func testParseURLSessionDelegateDownload() async throws { // swiftlint:disable:next line_length - let dowbloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + guard let fileManager = ParseFileManager(), + let filePath = fileManager.dataItemPathForPathComponent("test.txt") else { + XCTFail("Should have unwrapped") + return + } + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + + // swiftlint:disable:next line_length + let downloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, sent: Int64, total: Int64) -> Void in + if sent < total { + Task { + let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(downloadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + downloadTask: downloadTask, + didFinishDownloadingTo: filePath) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + Task { + let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(downloadCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + } + } + } // Add tasks + await ParseSwift.sessionDelegate.delegates.updateDownload(downloadTask, callback: downloadCompletion) await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) - let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count - XCTAssertEqual(taskCount, 1) + + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + downloadTask: downloadTask, + didWriteData: 0, + totalBytesWritten: 0, + totalBytesExpectedToWrite: 10) + wait(for: [expectation1, expectation2], timeout: 20.0) + } + + func testParseURLSessionDelegateStream() async throws { + // swiftlint:disable:next line_length + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + + let streamCompletion: ((InputStream?) -> Void) = { (_: InputStream?) -> Void in + Task { + let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(streamCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + Task { + let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count + XCTAssertEqual(streamCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + } + } + + // Add tasks await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init(data: .init())) - let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count - XCTAssertEqual(streamCount, 1) - await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) - let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count - XCTAssertEqual(uploadCount, 1) - await ParseSwift.sessionDelegate.delegates.updateDownload(dowloadTask, callback: dowbloadCompletion) - let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count - XCTAssertEqual(downloadCount, 1) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) - // Remove tasks - await ParseSwift.sessionDelegate.delegates.removeTask(task) - let taskCount2 = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count - XCTAssertEqual(taskCount2, 0) - await ParseSwift.sessionDelegate.delegates.removeStream(task) - let streamCount2 = await ParseSwift.sessionDelegate.delegates.streamDelegates.count - XCTAssertEqual(streamCount2, 0) - await ParseSwift.sessionDelegate.delegates.removeUpload(task) - let uploadCount2 = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count - XCTAssertEqual(uploadCount2, 0) - await ParseSwift.sessionDelegate.delegates.removeDownload(dowloadTask) - let downloadCount2 = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count - XCTAssertEqual(downloadCount2, 0) + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, needNewBodyStream: streamCompletion) + wait(for: [expectation1, expectation2], timeout: 20.0) } } #endif From dc6d2e78a578c778770226114a4739b165779dec Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 18:02:45 -0400 Subject: [PATCH 16/20] add old swift tests --- CHANGELOG.md | 2 +- .../API/ParseURLSessionDelegate.swift | 2 +- Tests/ParseSwiftTests/ParseFileTests.swift | 117 ++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d06e49ae4..834d34205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ### 4.9.1 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.9.0...4.9.1) __Fixes__ -- Use an actor for the url session delegates to ensure thread safety when making async calls in parallel ([#394](https://github.com/parse-community/Parse-Swift/pull/394)), thanks to [Corey Baker](https://github.com/cbaker6). +- Corrects a memory leak where multiple Parse URLSessions can get created. Use an actor for the url session delegates to ensure thread safety when making async calls in parallel ([#394](https://github.com/parse-community/Parse-Swift/pull/394)), thanks to [Corey Baker](https://github.com/cbaker6). ### 4.9.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.8.0...4.9.0) diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index 33256f142..fe88e69df 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -154,7 +154,7 @@ extension ParseURLSessionDelegate: URLSessionDataDelegate { streamDelegates.removeValue(forKey: task) taskCallbackQueues.removeValue(forKey: task) if let downloadTask = task as? URLSessionDownloadTask { - await downloadDelegates.removeValue(forKey: downloadTask) + downloadDelegates.removeValue(forKey: downloadTask) } #endif } diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 04e7ed39c..fc7883922 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -710,6 +710,123 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length ParseSwift.sessionDelegate.downloadDelegates.removeValue(forKey: dowloadTask) XCTAssertEqual(ParseSwift.sessionDelegate.downloadDelegates.count, 0) } + + func testParseURLSessionDelegateUpload() throws { + // swiftlint:disable:next line_length + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + + // swiftlint:disable:next line_length + let uploadCompletion: ((URLSessionTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionTask, _: Int64, sent: Int64, total: Int64) -> Void in + if sent < total { + let uploadCount = ParseSwift.sessionDelegate.uploadDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(uploadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + let uploadCount = ParseSwift.sessionDelegate.uploadDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(uploadCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + } + + // Add tasks + ParseSwift.sessionDelegate.uploadDelegates[task] = uploadCompletion + ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + task: task, + didSendBodyData: 0, + totalBytesSent: 0, + totalBytesExpectedToSend: 10) + wait(for: [expectation1, expectation2], timeout: 20.0) + } + + func testParseURLSessionDelegateDownload() throws { + // swiftlint:disable:next line_length + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + guard let fileManager = ParseFileManager(), + let filePath = fileManager.dataItemPathForPathComponent("test.txt") else { + XCTFail("Should have unwrapped") + return + } + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + + // swiftlint:disable:next line_length + let downloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, sent: Int64, total: Int64) -> Void in + if sent < total { + let downloadCount = ParseSwift.sessionDelegate.downloadDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(downloadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + downloadTask: downloadTask, + didFinishDownloadingTo: filePath) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + let downloadCount = ParseSwift.sessionDelegate.downloadDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(downloadCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + } + + // Add tasks + ParseSwift.sessionDelegate.downloadDelegates[task] = downloadCompletion + ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + downloadTask: downloadTask, + didWriteData: 0, + totalBytesWritten: 0, + totalBytesExpectedToWrite: 10) + wait(for: [expectation1, expectation2], timeout: 20.0) + } + + func testParseURLSessionDelegateStream() throws { + // swiftlint:disable:next line_length + let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) + let task = downloadTask as URLSessionTask + + let expectation1 = XCTestExpectation(description: "Call delegate 1") + let expectation2 = XCTestExpectation(description: "Call delegate 2") + + let streamCompletion: ((InputStream?) -> Void) = { (_: InputStream?) -> Void in + let streamCount = ParseSwift.sessionDelegate.streamDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(streamCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + let streamCount = ParseSwift.sessionDelegate.streamDelegates.count + let taskCount = ParseSwift.sessionDelegate.taskCallbackQueues.count + XCTAssertEqual(streamCount, 0) + XCTAssertEqual(taskCount, 0) + expectation2.fulfill() + } + } + + // Add tasks + ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) + ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + + ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, needNewBodyStream: streamCompletion) + wait(for: [expectation1, expectation2], timeout: 20.0) + } #endif #if !os(Linux) && !os(Android) && !os(Windows) From 3edb42664f44800939d8d4f4ba28844b43a0a5d5 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 18:11:10 -0400 Subject: [PATCH 17/20] nit --- Tests/ParseSwiftTests/ParseFileTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index fc7883922..4039f26be 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -785,7 +785,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length } // Add tasks - ParseSwift.sessionDelegate.downloadDelegates[task] = downloadCompletion + ParseSwift.sessionDelegate.downloadDelegates[downloadTask] = downloadCompletion ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main ParseSwift.sessionDelegate.urlSession(URLSession.parse, From 17ea995db694b529f636018ef7b485addf8821ec Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 18:41:35 -0400 Subject: [PATCH 18/20] extend time --- Tests/ParseSwiftTests/ParseFileAsyncTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index 72696cab5..2155cf90c 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -319,7 +319,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(taskCount, 1) expectation1.fulfill() ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { Task { let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count @@ -369,7 +369,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng ParseSwift.sessionDelegate.urlSession(URLSession.parse, downloadTask: downloadTask, didFinishDownloadingTo: filePath) - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { Task { let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count @@ -410,7 +410,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(taskCount, 1) expectation1.fulfill() ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { Task { let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count From 25b7945a219bf227b43788ee55a9e84dd437d169 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 19:58:22 -0400 Subject: [PATCH 19/20] make delegates sendable --- Sources/ParseSwift/API/API+Command.swift | 3 +- .../API/ParseURLSessionDelegate.swift | 27 ++-------- .../ParseSwiftTests/ParseFileAsyncTests.swift | 51 +++++++++++-------- Tests/ParseSwiftTests/ParseFileTests.swift | 9 ++-- 4 files changed, 39 insertions(+), 51 deletions(-) diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift index afea63503..b1c14a748 100644 --- a/Sources/ParseSwift/API/API+Command.swift +++ b/Sources/ParseSwift/API/API+Command.swift @@ -60,16 +60,15 @@ internal extension API { case .success(let urlRequest): if method == .POST || method == .PUT || method == .PATCH { let task = URLSession.parse.uploadTask(withStreamedRequest: urlRequest) + ParseSwift.sessionDelegate.streamDelegates[task] = stream #if compiler(>=5.5.2) && canImport(_Concurrency) Task { await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadProgress) - await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: stream) await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: callbackQueue) task.resume() } #else ParseSwift.sessionDelegate.uploadDelegates[task] = uploadProgress - ParseSwift.sessionDelegate.streamDelegates[task] = stream ParseSwift.sessionDelegate.taskCallbackQueues[task] = callbackQueue task.resume() #endif diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index fe88e69df..108b19237 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -17,12 +17,11 @@ class ParseURLSessionDelegate: NSObject var authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? - + var streamDelegates = [URLSessionTask: InputStream]() #if compiler(>=5.5.2) && canImport(_Concurrency) - actor SessionDelegate { + actor SessionDelegate: Sendable { var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() - var streamDelegates = [URLSessionTask: InputStream]() var taskCallbackQueues = [URLSessionTask: DispatchQueue]() func updateDownload(_ task: URLSessionDownloadTask, @@ -45,16 +44,6 @@ class ParseURLSessionDelegate: NSObject taskCallbackQueues.removeValue(forKey: task) } - func updateStream(_ task: URLSessionTask, - stream: InputStream) { - streamDelegates[task] = stream - } - - func removeStream(_ task: URLSessionTask) { - streamDelegates.removeValue(forKey: task) - taskCallbackQueues.removeValue(forKey: task) - } - func updateTask(_ task: URLSessionTask, queue: DispatchQueue) { taskCallbackQueues[task] = queue @@ -70,7 +59,6 @@ class ParseURLSessionDelegate: NSObject #else var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() - var streamDelegates = [URLSessionTask: InputStream]() var taskCallbackQueues = [URLSessionTask: DispatchQueue]() #endif @@ -127,31 +115,22 @@ extension ParseURLSessionDelegate: URLSessionDataDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { - #if compiler(>=5.5.2) && canImport(_Concurrency) - Task { - if let stream = await delegates.streamDelegates[task] { - completionHandler(stream) - } - } - #else if let stream = streamDelegates[task] { completionHandler(stream) } - #endif } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + streamDelegates.removeValue(forKey: task) #if compiler(>=5.5.2) && canImport(_Concurrency) Task { await delegates.removeUpload(task) - await delegates.removeStream(task) if let downloadTask = task as? URLSessionDownloadTask { await delegates.removeDownload(downloadTask) } } #else uploadDelegates.removeValue(forKey: task) - streamDelegates.removeValue(forKey: task) taskCallbackQueues.removeValue(forKey: task) if let downloadTask = task as? URLSessionDownloadTask { downloadDelegates.removeValue(forKey: downloadTask) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index 2155cf90c..5ff5429d9 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -273,12 +273,11 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng let downloadCompletion: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void) = { (_: URLSessionDownloadTask, _: Int64, _: Int64, _: Int64) -> Void in } // Add tasks + ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates.count, 1) await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count XCTAssertEqual(taskCount, 1) - await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init(data: .init())) - let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count - XCTAssertEqual(streamCount, 1) await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count XCTAssertEqual(uploadCount, 1) @@ -287,12 +286,11 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(downloadCount, 1) // Remove tasks + ParseSwift.sessionDelegate.streamDelegates.removeValue(forKey: task) + XCTAssertEqual(ParseSwift.sessionDelegate.streamDelegates.count, 0) await ParseSwift.sessionDelegate.delegates.removeTask(task) let taskCount2 = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count XCTAssertEqual(taskCount2, 0) - await ParseSwift.sessionDelegate.delegates.removeStream(task) - let streamCount2 = await ParseSwift.sessionDelegate.delegates.streamDelegates.count - XCTAssertEqual(streamCount2, 0) await ParseSwift.sessionDelegate.delegates.removeUpload(task) let uploadCount2 = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count XCTAssertEqual(uploadCount2, 0) @@ -305,6 +303,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) let expectation1 = XCTestExpectation(description: "Call delegate 1") let expectation2 = XCTestExpectation(description: "Call delegate 2") @@ -315,11 +314,14 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng Task { let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count - XCTAssertEqual(uploadCount, 1) - XCTAssertEqual(taskCount, 1) - expectation1.fulfill() - ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + ParseSwift.sessionDelegate.urlSession(URLSession.parse, + task: task, + didCompleteWithError: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + XCTAssertEqual(uploadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + Task { let uploadCount = await ParseSwift.sessionDelegate.delegates.uploadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count @@ -334,7 +336,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng // Add tasks await ParseSwift.sessionDelegate.delegates.updateUpload(task, callback: uploadCompletion) - await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: queue) ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, @@ -348,6 +350,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) guard let fileManager = ParseFileManager(), let filePath = fileManager.dataItemPathForPathComponent("test.txt") else { XCTFail("Should have unwrapped") @@ -363,13 +366,14 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng Task { let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count - XCTAssertEqual(downloadCount, 1) - XCTAssertEqual(taskCount, 1) - expectation1.fulfill() ParseSwift.sessionDelegate.urlSession(URLSession.parse, downloadTask: downloadTask, didFinishDownloadingTo: filePath) DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + XCTAssertEqual(downloadCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + Task { let downloadCount = await ParseSwift.sessionDelegate.delegates.downloadDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count @@ -384,7 +388,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng // Add tasks await ParseSwift.sessionDelegate.delegates.updateDownload(downloadTask, callback: downloadCompletion) - await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: queue) ParseSwift.sessionDelegate.urlSession(URLSession.parse, downloadTask: downloadTask, @@ -398,21 +402,24 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) let expectation1 = XCTestExpectation(description: "Call delegate 1") let expectation2 = XCTestExpectation(description: "Call delegate 2") let streamCompletion: ((InputStream?) -> Void) = { (_: InputStream?) -> Void in Task { - let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + let streamCount = ParseSwift.sessionDelegate.streamDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count - XCTAssertEqual(streamCount, 1) - XCTAssertEqual(taskCount, 1) - expectation1.fulfill() ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, didCompleteWithError: nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + XCTAssertEqual(streamCount, 1) + XCTAssertEqual(taskCount, 1) + expectation1.fulfill() + Task { - let streamCount = await ParseSwift.sessionDelegate.delegates.streamDelegates.count + let streamCount = ParseSwift.sessionDelegate.streamDelegates.count let taskCount = await ParseSwift.sessionDelegate.delegates.taskCallbackQueues.count XCTAssertEqual(streamCount, 0) XCTAssertEqual(taskCount, 0) @@ -423,8 +430,8 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng } // Add tasks - await ParseSwift.sessionDelegate.delegates.updateStream(task, stream: .init(data: .init())) - await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: DispatchQueue.main) + ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) + await ParseSwift.sessionDelegate.delegates.updateTask(task, queue: queue) ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, needNewBodyStream: streamCompletion) wait(for: [expectation1, expectation2], timeout: 20.0) diff --git a/Tests/ParseSwiftTests/ParseFileTests.swift b/Tests/ParseSwiftTests/ParseFileTests.swift index 4039f26be..24afbdcff 100644 --- a/Tests/ParseSwiftTests/ParseFileTests.swift +++ b/Tests/ParseSwiftTests/ParseFileTests.swift @@ -715,6 +715,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) let expectation1 = XCTestExpectation(description: "Call delegate 1") let expectation2 = XCTestExpectation(description: "Call delegate 2") @@ -740,7 +741,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // Add tasks ParseSwift.sessionDelegate.uploadDelegates[task] = uploadCompletion - ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + ParseSwift.sessionDelegate.taskCallbackQueues[task] = queue ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, @@ -754,6 +755,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) guard let fileManager = ParseFileManager(), let filePath = fileManager.dataItemPathForPathComponent("test.txt") else { XCTFail("Should have unwrapped") @@ -786,7 +788,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // Add tasks ParseSwift.sessionDelegate.downloadDelegates[downloadTask] = downloadCompletion - ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + ParseSwift.sessionDelegate.taskCallbackQueues[task] = queue ParseSwift.sessionDelegate.urlSession(URLSession.parse, downloadTask: downloadTask, @@ -800,6 +802,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) let task = downloadTask as URLSessionTask + let queue = DispatchQueue.global(qos: .utility) let expectation1 = XCTestExpectation(description: "Call delegate 1") let expectation2 = XCTestExpectation(description: "Call delegate 2") @@ -822,7 +825,7 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length // Add tasks ParseSwift.sessionDelegate.streamDelegates[task] = .init(data: .init()) - ParseSwift.sessionDelegate.taskCallbackQueues[task] = DispatchQueue.main + ParseSwift.sessionDelegate.taskCallbackQueues[task] = queue ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, needNewBodyStream: streamCompletion) wait(for: [expectation1, expectation2], timeout: 20.0) From e52969c1628bca723ac6e5f96309d5cfbd44d47e Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sun, 28 Aug 2022 20:11:54 -0400 Subject: [PATCH 20/20] update test suite --- Tests/ParseSwiftTests/ParseFileAsyncTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift index 5ff5429d9..3da17c34e 100644 --- a/Tests/ParseSwiftTests/ParseFileAsyncTests.swift +++ b/Tests/ParseSwiftTests/ParseFileAsyncTests.swift @@ -299,6 +299,7 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng XCTAssertEqual(downloadCount2, 0) } + #if !os(iOS) func testParseURLSessionDelegateUpload() async throws { // swiftlint:disable:next line_length let downloadTask = URLSession.shared.downloadTask(with: .init(fileURLWithPath: "http://localhost:1337/1/files/applicationId/d3a37aed0672a024595b766f97133615_logo.svg")) @@ -436,5 +437,6 @@ class ParseFileAsyncTests: XCTestCase { // swiftlint:disable:this type_body_leng ParseSwift.sessionDelegate.urlSession(URLSession.parse, task: task, needNewBodyStream: streamCompletion) wait(for: [expectation1, expectation2], timeout: 20.0) } + #endif } #endif