From ffdcdf9dbdf317b7d44da9108cc22872a224e86d Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Tue, 12 Jan 2021 13:32:48 -0500 Subject: [PATCH 1/2] Improve threading --- Sources/ParseSwift/API/API+Commands.swift | 100 ++---- .../API/ParseURLSessionDelegate.swift | 23 +- .../API/URLSession+extensions.swift | 4 + .../LiveQuery/LiveQuerySocket.swift | 2 +- .../Objects/ParseInstallation.swift | 268 +++++++++------ Sources/ParseSwift/Objects/ParseObject.swift | 229 ++++++++----- Sources/ParseSwift/Objects/ParseUser.swift | 322 +++++++++++------- Sources/ParseSwift/Types/ParseCloud.swift | 18 +- Sources/ParseSwift/Types/ParseFile.swift | 88 +++-- Sources/ParseSwift/Types/Pointer.swift | 6 +- Sources/ParseSwift/Types/Query.swift | 56 ++- Tests/ParseSwiftTests/ParseObjectTests.swift | 2 + 12 files changed, 669 insertions(+), 449 deletions(-) diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index fcd24db1c..902be756d 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -50,6 +50,7 @@ internal extension API { // MARK: Synchronous Execution func executeStream(options: API.Options, + callbackQueue: DispatchQueue, childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, @@ -61,6 +62,7 @@ internal extension API { let task = URLSession.parse.uploadTask(withStreamedRequest: urlRequest) ParseConfiguration.sessionDelegate.uploadDelegates[task] = uploadProgress ParseConfiguration.sessionDelegate.streamDelegates[task] = stream + ParseConfiguration.sessionDelegate.taskCallbackQueues[task] = callbackQueue task.resume() return } @@ -70,6 +72,7 @@ internal extension API { } func execute(options: API.Options, + callbackQueue: DispatchQueue, childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, @@ -78,7 +81,7 @@ internal extension API { let group = DispatchGroup() group.enter() self.executeAsync(options: options, - callbackQueue: nil, + callbackQueue: callbackQueue, childObjects: childObjects, childFiles: childFiles, uploadProgress: uploadProgress, @@ -97,7 +100,8 @@ internal extension API { // MARK: Asynchronous Execution // swiftlint:disable:next function_body_length cyclomatic_complexity - func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, + func executeAsync(options: API.Options, + callbackQueue: DispatchQueue, childObjects: [String: PointerType]? = nil, childFiles: [UUID: ParseFile]? = nil, uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil, @@ -114,18 +118,9 @@ internal extension API { switch result { case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - + completion(.success(decoded)) case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } + completion(.failure(error)) } } case .failure(let error): @@ -140,26 +135,20 @@ internal extension API { case .success(let urlRequest): - URLSession.parse.uploadTask(with: urlRequest, - from: uploadData, - from: uploadFile, - progress: uploadProgress, - mapper: mapper) { result in + URLSession + .parse + .uploadTask(callbackQueue: callbackQueue, + with: urlRequest, + from: uploadData, + from: uploadFile, + progress: uploadProgress, + mapper: mapper) { result in switch result { case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - + completion(.success(decoded)) case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } + completion(.failure(error)) } } case .failure(let error): @@ -173,24 +162,18 @@ internal extension API { childFiles: childFiles) { case .success(let urlRequest): - URLSession.parse.downloadTask(with: urlRequest, - progress: downloadProgress, - mapper: mapper) { result in + URLSession + .parse + .downloadTask(callbackQueue: callbackQueue, + with: urlRequest, + progress: downloadProgress, + mapper: mapper) { result in switch result { case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - + completion(.success(decoded)) case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } + completion(.failure(error)) } } case .failure(let error): @@ -202,18 +185,9 @@ internal extension API { switch result { case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - + completion(.success(decoded)) case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } + completion(.failure(error)) } } } else { @@ -596,8 +570,7 @@ internal extension API { var responseResult: Result? let group = DispatchGroup() group.enter() - self.executeAsync(options: options, - callbackQueue: nil) { result in + self.executeAsync(options: options) { result in responseResult = result group.leave() } @@ -611,7 +584,7 @@ internal extension API { } // MARK: Asynchronous Execution - func executeAsync(options: API.Options, callbackQueue: DispatchQueue?, + func executeAsync(options: API.Options, completion: @escaping(Result) -> Void) { switch self.prepareURLRequest(options: options) { @@ -620,18 +593,9 @@ internal extension API { switch result { case .success(let decoded): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.success(decoded)) } - } else { - completion(.success(decoded)) - } - + completion(.success(decoded)) case .failure(let error): - if let callbackQueue = callbackQueue { - callbackQueue.async { completion(.failure(error)) } - } else { - completion(.failure(error)) - } + completion(.failure(error)) } } case .failure(let error): diff --git a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift index e7c6c86fd..554b46dfa 100644 --- a/Sources/ParseSwift/API/ParseURLSessionDelegate.swift +++ b/Sources/ParseSwift/API/ParseURLSessionDelegate.swift @@ -20,6 +20,7 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg var downloadDelegates = [URLSessionDownloadTask: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)]() var uploadDelegates = [URLSessionTask: ((URLSessionTask, Int64, Int64, Int64) -> Void)]() var streamDelegates = [URLSessionTask: InputStream]() + var taskCallbackQueues = [URLSessionTask: DispatchQueue]() init (callbackQueue: DispatchQueue, authentication: ((URLAuthenticationChallenge, @@ -35,7 +36,9 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if let authentication = authentication { - authentication(challenge, completionHandler) + callbackQueue.async { + authentication(challenge, completionHandler) + } } else { completionHandler(.performDefaultHandling, nil) } @@ -46,8 +49,10 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { - if let callBack = uploadDelegates[task] { - callbackQueue.async { + if let callBack = uploadDelegates[task], + let queue = taskCallbackQueues[task] { + + queue.async { callBack(task, bytesSent, totalBytesSent, totalBytesExpectedToSend) if totalBytesSent == totalBytesExpectedToSend { @@ -63,8 +68,9 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - if let callBack = downloadDelegates[downloadTask] { - callbackQueue.async { + if let callBack = downloadDelegates[downloadTask], + let queue = taskCallbackQueues[downloadTask] { + queue.async { callBack(downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) if totalBytesWritten == totalBytesExpectedToWrite { self.downloadDelegates.removeValue(forKey: downloadTask) @@ -77,6 +83,7 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { downloadDelegates.removeValue(forKey: downloadTask) + taskCallbackQueues.removeValue(forKey: downloadTask) } func urlSession(_ session: URLSession, @@ -86,4 +93,10 @@ class ParseURLSessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDeleg completionHandler(stream) } } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + uploadDelegates.removeValue(forKey: task) + streamDelegates.removeValue(forKey: task) + taskCallbackQueues.removeValue(forKey: task) + } } diff --git a/Sources/ParseSwift/API/URLSession+extensions.swift b/Sources/ParseSwift/API/URLSession+extensions.swift index 4e5e238c8..97ad5b557 100755 --- a/Sources/ParseSwift/API/URLSession+extensions.swift +++ b/Sources/ParseSwift/API/URLSession+extensions.swift @@ -101,6 +101,7 @@ extension URLSession { extension URLSession { internal func uploadTask( // swiftlint:disable:this function_parameter_count + callbackQueue: DispatchQueue, with request: URLRequest, from data: Data?, from file: URL?, @@ -126,11 +127,13 @@ extension URLSession { } if let task = task { ParseConfiguration.sessionDelegate.uploadDelegates[task] = progress + ParseConfiguration.sessionDelegate.taskCallbackQueues[task] = callbackQueue task.resume() } } internal func downloadTask( + callbackQueue: DispatchQueue, with request: URLRequest, progress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?, mapper: @escaping (Data) throws -> U, @@ -142,6 +145,7 @@ extension URLSession { responseError: responseError, mapper: mapper)) } ParseConfiguration.sessionDelegate.downloadDelegates[task] = progress + ParseConfiguration.sessionDelegate.taskCallbackQueues[task] = callbackQueue task.resume() } diff --git a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift index bceb8cfbc..2b9c775b5 100644 --- a/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift +++ b/Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift @@ -27,8 +27,8 @@ final class LiveQuerySocket: NSObject { func closeAll() { delegates.forEach { (_, client) -> Void in client.close(useDedicatedQueue: false) - authenticationDelegate = nil } + authenticationDelegate = nil } } diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index e710d6e78..0eb3ebd54 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -297,7 +297,7 @@ extension ParseInstallation { // MARK: Fetchable extension ParseInstallation { - internal static func updateKeychainIfNeeded(_ results: [Self], deleting: Bool = false) throws { + internal static func updateKeychainIfNeeded(_ results: [Self], deleting: Bool = false) { guard BaseParseUser.current != nil, let currentInstallation = BaseParseInstallation.current else { return @@ -330,8 +330,9 @@ extension ParseInstallation { - important: If an object fetched has the same objectId as current, it will automatically update the current. */ public func fetch(options: API.Options = []) throws -> Self { - let result: Self = try fetchCommand().execute(options: options) - try? Self.updateKeychainIfNeeded([result]) + let result: Self = try fetchCommand() + .execute(options: options, callbackQueue: .main) + Self.updateKeychainIfNeeded([result]) return result } @@ -351,16 +352,24 @@ extension ParseInstallation { completion: @escaping (Result) -> Void ) { do { - try fetchCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in - if case .success(let foundResult) = result { - try? Self.updateKeychainIfNeeded([foundResult]) + try fetchCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + callbackQueue.async { + if case .success(let foundResult) = result { + Self.updateKeychainIfNeeded([foundResult]) + } + completion(result) + } } - completion(result) - } } catch let error as ParseError { - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } catch { - completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + } } } @@ -407,9 +416,10 @@ extension ParseInstallation { let result: Self = try saveCommand() .execute(options: options, + callbackQueue: .main, childObjects: childObjects, childFiles: childFiles) - try? Self.updateKeychainIfNeeded([result]) + Self.updateKeychainIfNeeded([result]) return result } @@ -429,18 +439,24 @@ extension ParseInstallation { ) { self.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, error) in guard let parseError = error else { - self.saveCommand().executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: savedChildObjects, - childFiles: savedChildFiles) { result in - if case .success(let foundResults) = result { - try? Self.updateKeychainIfNeeded([foundResults]) + self.saveCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: savedChildObjects, + childFiles: savedChildFiles) { result in + callbackQueue.async { + if case .success(let foundResults) = result { + Self.updateKeychainIfNeeded([foundResults]) + } + + completion(result) + } } - completion(result) - } return } - completion(.failure(parseError)) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -485,7 +501,7 @@ extension ParseInstallation { */ public func delete(options: API.Options = []) throws { _ = try deleteCommand().execute(options: options) - try? Self.updateKeychainIfNeeded([self], deleting: true) + Self.updateKeychainIfNeeded([self], deleting: true) } /** @@ -495,7 +511,7 @@ extension ParseInstallation { - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute when completed. - It should have the following argument signature: `(Result)`. + It should have the following argument signature: `(ParseError?)`. - important: If an object deleted has the same objectId as current, it will automatically update the current. */ public func delete( @@ -504,20 +520,27 @@ extension ParseInstallation { completion: @escaping (ParseError?) -> Void ) { do { - try deleteCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in - switch result { - - case .success: - try? Self.updateKeychainIfNeeded([self], deleting: true) - completion(nil) - case .failure(let error): - completion(error) + try deleteCommand() + .executeAsync(options: options) { result in + callbackQueue.async { + switch result { + + case .success(let error): + Self.updateKeychainIfNeeded([self], deleting: true) + completion(error) + case .failure(let error): + completion(error) + } + } } - } } catch let error as ParseError { - completion(error) + callbackQueue.async { + completion(error) + } } catch { - completion(ParseError(code: .unknownError, message: error.localizedDescription)) + callbackQueue.async { + completion(ParseError(code: .unknownError, message: error.localizedDescription)) + } } } @@ -602,11 +625,12 @@ public extension Sequence where Element: ParseInstallation { let currentBatch = try API.Command .batch(commands: $0) .execute(options: options, + callbackQueue: .main, childObjects: childObjects, childFiles: childFiles) returnBatch.append(contentsOf: currentBatch) } - try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) return returnBatch } @@ -627,74 +651,85 @@ public extension Sequence where Element: ParseInstallation { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - var childObjects = [String: PointerType]() - var childFiles = [UUID: ParseFile]() - var error: ParseError? - - let installations = map { $0 } - for installation in installations { - let group = DispatchGroup() - group.enter() - installation.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in - //If an error occurs, everything should be skipped - if parseError != nil { - error = parseError - } - savedChildObjects.forEach {(key, value) in - if error != nil { - return + let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, + attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + queue.sync { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var childObjects = [String: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let installations = map { $0 } + for installation in installations { + let group = DispatchGroup() + group.enter() + installation + .ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError } - if childObjects[key] == nil { - childObjects[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - } - savedChildFiles.forEach {(key, value) in - if error != nil { - return + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - if childFiles[key] == nil { - childFiles[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + group.leave() + } + group.wait() + if let error = error { + callbackQueue.async { + completion(.failure(error)) } + return } - group.leave() } - group.wait() - if let error = error { - completion(.failure(error)) - return - } - } - var returnBatch = [(Result)]() - let commands = map { $0.saveCommand() } - let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) - var completed = 0 - for batch in batches { - API.Command - .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles) { results in - switch results { - - case .success(let saved): - returnBatch.append(contentsOf: saved) - if completed == (batches.count - 1) { - try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) - completion(.success(returnBatch)) + var returnBatch = [(Result)]() + let commands = map { $0.saveCommand() } + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + callbackQueue.async { + Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + completion(.success(returnBatch)) + } + } + completed += 1 + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + return } - completed += 1 - case .failure(let error): - completion(.failure(error)) - return } } } @@ -729,7 +764,7 @@ public extension Sequence where Element: ParseInstallation { message: "objectId \"\(uniqueObjectId)\" was not found in className \"\(Self.Element.className)\""))) } } - try? Self.Element.updateKeychainIfNeeded(fetchedObjects) + Self.Element.updateKeychainIfNeeded(fetchedObjects) return fetchedObjectsToReturn } else { throw ParseError(code: .unknownError, message: "all items to fetch must be of the same class") @@ -771,15 +806,21 @@ public extension Sequence where Element: ParseInstallation { message: "objectId \"\(uniqueObjectId)\" was not found in className \"\(Self.Element.className)\""))) } } - try? Self.Element.updateKeychainIfNeeded(fetchedObjects) - completion(.success(fetchedObjectsToReturn)) + callbackQueue.async { + Self.Element.updateKeychainIfNeeded(fetchedObjects) + completion(.success(fetchedObjectsToReturn)) + } case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } } } else { - completion(.failure(ParseError(code: .unknownError, - message: "all items to fetch must be of the same class"))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, + message: "all items to fetch must be of the same class"))) + } } } @@ -814,7 +855,7 @@ public extension Sequence where Element: ParseInstallation { returnBatch.append(contentsOf: currentBatch) } - try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) + Self.Element.updateKeychainIfNeeded(compactMap {$0}) return returnBatch } @@ -852,30 +893,35 @@ public extension Sequence where Element: ParseInstallation { for batch in batches { API.Command .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue) { results in + .executeAsync(options: options) { results in switch results { case .success(let saved): returnBatch.append(contentsOf: saved) if completed == (batches.count - 1) { - try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(returnBatch)) + callbackQueue.async { + Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) + completion(.success(returnBatch)) + } } completed += 1 case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } return } } } } catch { - guard let parseError = error as? ParseError else { - completion(.failure(ParseError(code: .unknownError, - message: error.localizedDescription))) - return + callbackQueue.async { + guard let parseError = error as? ParseError else { + completion(.failure(ParseError(code: .unknownError, + message: error.localizedDescription))) + return + } + completion(.failure(parseError)) } - completion(.failure(parseError)) } } } // swiftlint:disable:this file_length diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index 834e1ae80..a50448b6b 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -120,6 +120,7 @@ public extension Sequence where Element: ParseObject { let currentBatch = try API.Command .batch(commands: $0) .execute(options: options, + callbackQueue: .main, childObjects: childObjects, childFiles: childFiles) returnBatch.append(contentsOf: currentBatch) @@ -143,73 +144,84 @@ public extension Sequence where Element: ParseObject { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - var childObjects = [String: PointerType]() - var childFiles = [UUID: ParseFile]() - var error: ParseError? + let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, + attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + queue.sync { - let objects = map { $0 } - for object in objects { - let group = DispatchGroup() - group.enter() - object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in - //If an error occurs, everything should be skipped - if parseError != nil { - error = parseError - } - savedChildObjects.forEach {(key, value) in - if error != nil { - return + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var childObjects = [String: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let objects = map { $0 } + for object in objects { + let group = DispatchGroup() + group.enter() + object.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError } - if childObjects[key] == nil { - childObjects[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - } - savedChildFiles.forEach {(key, value) in - if error != nil { - return + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - if childFiles[key] == nil { - childFiles[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + group.leave() + } + group.wait() + if let error = error { + callbackQueue.async { + completion(.failure(error)) } + return } - group.leave() - } - group.wait() - if let error = error { - completion(.failure(error)) - return } - } - var returnBatch = [(Result)]() - let commands = map { $0.saveCommand() } - let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) - var completed = 0 - for batch in batches { - API.Command - .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles) { results in - switch results { - - case .success(let saved): - returnBatch.append(contentsOf: saved) - if completed == (batches.count - 1) { - completion(.success(returnBatch)) + var returnBatch = [(Result)]() + let commands = map { $0.saveCommand() } + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + callbackQueue.async { + completion(.success(returnBatch)) + } + } + completed += 1 + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + return } - completed += 1 - case .failure(let error): - completion(.failure(error)) - return } } } @@ -283,14 +295,20 @@ public extension Sequence where Element: ParseObject { message: "objectId \"\(uniqueObjectId)\" was not found in className \"\(Self.Element.className)\""))) } } - completion(.success(fetchedObjectsToReturn)) + callbackQueue.async { + completion(.success(fetchedObjectsToReturn)) + } case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } } } else { - completion(.failure(ParseError(code: .unknownError, - message: "all items to fetch must be of the same class"))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, + message: "all items to fetch must be of the same class"))) + } } } @@ -359,29 +377,34 @@ public extension Sequence where Element: ParseObject { for batch in batches { API.Command .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue) { results in + .executeAsync(options: options) { results in switch results { case .success(let saved): returnBatch.append(contentsOf: saved) if completed == (batches.count - 1) { - completion(.success(returnBatch)) + callbackQueue.async { + completion(.success(returnBatch)) + } } completed += 1 case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } return } } } } catch { - guard let parseError = error as? ParseError else { - completion(.failure(ParseError(code: .unknownError, - message: error.localizedDescription))) - return + callbackQueue.async { + guard let parseError = error as? ParseError else { + completion(.failure(ParseError(code: .unknownError, + message: error.localizedDescription))) + return + } + completion(.failure(parseError)) } - completion(.failure(parseError)) } } } @@ -427,7 +450,8 @@ extension ParseObject { - throws: An Error of `ParseError` type. */ public func fetch(options: API.Options = []) throws -> Self { - try fetchCommand().execute(options: options) + try fetchCommand().execute(options: options, + callbackQueue: .main) } /** @@ -445,11 +469,21 @@ extension ParseObject { completion: @escaping (Result) -> Void ) { do { - try fetchCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + try fetchCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + callbackQueue.async { + completion(result) + } + } } catch let error as ParseError { - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } catch { - completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + } } } @@ -510,7 +544,11 @@ extension ParseObject { throw error } - return try saveCommand().execute(options: options, childObjects: childObjects, childFiles: childFiles) + return try saveCommand() + .execute(options: options, + callbackQueue: .main, + childObjects: childObjects, + childFiles: childFiles) } /** @@ -531,11 +569,16 @@ extension ParseObject { self.saveCommand().executeAsync(options: options, callbackQueue: callbackQueue, childObjects: savedChildObjects, - childFiles: savedChildFiles, - completion: completion) + childFiles: savedChildFiles) { result in + callbackQueue.async { + completion(result) + } + } return } - completion(.failure(parseError)) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -633,7 +676,9 @@ extension ParseObject { // MARK: Savable Encodable Version internal extension ParseType { func save(options: API.Options = []) throws -> PointerType { - try saveCommand().execute(options: options) + try saveCommand() + .execute(options: options, + callbackQueue: .main) } func saveCommand() throws -> API.Command { @@ -669,7 +714,7 @@ extension ParseObject { - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute when completed. - It should have the following argument signature: `(Result)`. + It should have the following argument signature: `(ParseError?)`. */ public func delete( options: API.Options = [], @@ -677,19 +722,25 @@ extension ParseObject { completion: @escaping (ParseError?) -> Void ) { do { - try deleteCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in - switch result { + try deleteCommand().executeAsync(options: options) { result in + callbackQueue.async { + switch result { - case .success(let error): - completion(error) - case .failure(let error): - completion(error) + case .success(let error): + completion(error) + case .failure(let error): + completion(error) + } } } } catch let error as ParseError { - completion(error) + callbackQueue.async { + completion(error) + } } catch { - completion(ParseError(code: .unknownError, message: error.localizedDescription)) + callbackQueue.async { + completion(ParseError(code: .unknownError, message: error.localizedDescription)) + } } } diff --git a/Sources/ParseSwift/Objects/ParseUser.swift b/Sources/ParseSwift/Objects/ParseUser.swift index f87846ff9..2115e845b 100644 --- a/Sources/ParseSwift/Objects/ParseUser.swift +++ b/Sources/ParseSwift/Objects/ParseUser.swift @@ -149,7 +149,11 @@ extension ParseUser { completion: @escaping (Result) -> Void ) { loginCommand(username: username, password: password) - .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } internal static func loginCommand(username: String, @@ -196,13 +200,15 @@ extension ParseUser { */ public static func logout(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (ParseError?) -> Void) { - logoutCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in - switch result { + logoutCommand().executeAsync(options: options) { result in + callbackQueue.async { + switch result { - case .success: - completion(nil) - case .failure(let error): - completion(error) + case .success: + completion(nil) + case .failure(let error): + completion(error) + } } } } @@ -257,13 +263,15 @@ extension ParseUser { public static func passwordReset(email: String, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (ParseError?) -> Void) { - passwordResetCommand(email: email).executeAsync(options: options, callbackQueue: callbackQueue) { result in - switch result { + passwordResetCommand(email: email).executeAsync(options: options) { result in + callbackQueue.async { + switch result { - case .success(let error): - completion(error) - case .failure(let error): - completion(error) + case .success(let error): + completion(error) + case .failure(let error): + completion(error) + } } } } @@ -305,15 +313,17 @@ extension ParseUser { callbackQueue: DispatchQueue = .main, completion: @escaping (ParseError?) -> Void) { verificationEmailRequestCommand(email: email) - .executeAsync(options: options, - callbackQueue: callbackQueue) { result in - switch result { + .executeAsync(options: options) { result in + callbackQueue.async { - case .success(let error): - completion(error) - case .failure(let error): - completion(error) - } + switch result { + + case .success(let error): + completion(error) + case .failure(let error): + completion(error) + } + } } } @@ -356,7 +366,7 @@ extension ParseUser { - returns: Returns whether the sign up was successful. */ public func signup(options: API.Options = []) throws -> Self { - try signupCommand().execute(options: options) + try signupCommand().execute(options: options, callbackQueue: .main) } /** @@ -372,7 +382,13 @@ extension ParseUser { */ public func signup(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - signupCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + signupCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in + callbackQueue.async { + completion(result) + } + } } /** @@ -396,7 +412,11 @@ extension ParseUser { completion: @escaping (Result) -> Void ) { signupCommand(username: username, password: password) - .executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } internal static func signupCommand(username: String, @@ -470,8 +490,10 @@ extension ParseUser { - important: If an object fetched has the same objectId as current, it will automatically update the current. */ public func fetch(options: API.Options = []) throws -> Self { - let result: Self = try fetchCommand().execute(options: options) - try? Self.updateKeychainIfNeeded([result]) + let result: Self = try fetchCommand() + .execute(options: options, + callbackQueue: .main) + try Self.updateKeychainIfNeeded([result]) return result } @@ -491,16 +513,36 @@ extension ParseUser { completion: @escaping (Result) -> Void ) { do { - try fetchCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in + try fetchCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue) { result in if case .success(let foundResult) = result { - try? Self.updateKeychainIfNeeded([foundResult]) + do { + try Self.updateKeychainIfNeeded([foundResult]) + } catch let error { + let returnError: ParseError! + if let parseError = error as? ParseError { + returnError = parseError + } else { + returnError = ParseError(code: .unknownError, message: error.localizedDescription) + } + callbackQueue.async { + completion(.failure(returnError)) + } + } + } + callbackQueue.async { + completion(result) } - completion(result) } } catch let error as ParseError { - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } catch { - completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription))) + } } } @@ -547,9 +589,10 @@ extension ParseUser { let result: Self = try saveCommand() .execute(options: options, + callbackQueue: .main, childObjects: childObjects, childFiles: childFiles) - try? Self.updateKeychainIfNeeded([result]) + try Self.updateKeychainIfNeeded([result]) return result } @@ -569,18 +612,23 @@ extension ParseUser { ) { self.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, error) in guard let parseError = error else { - self.saveCommand().executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: savedChildObjects, - childFiles: savedChildFiles) { result in + self.saveCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: savedChildObjects, + childFiles: savedChildFiles) { result in if case .success(let foundResults) = result { try? Self.updateKeychainIfNeeded([foundResults]) } - completion(result) + callbackQueue.async { + completion(result) + } } return } - completion(.failure(parseError)) + callbackQueue.async { + completion(.failure(parseError)) + } } } @@ -624,7 +672,7 @@ extension ParseUser { */ public func delete(options: API.Options = []) throws { _ = try deleteCommand().execute(options: options) - try? Self.updateKeychainIfNeeded([self], deleting: true) + try Self.updateKeychainIfNeeded([self], deleting: true) } /** @@ -634,7 +682,7 @@ extension ParseUser { - parameter callbackQueue: The queue to return to after completion. Default value of .main. - parameter completion: The block to execute when completed. - It should have the following argument signature: `(Result)`. + It should have the following argument signature: `(ParseError?)`. - important: If an object deleted has the same objectId as current, it will automatically update the current. */ public func delete( @@ -643,20 +691,28 @@ extension ParseUser { completion: @escaping (ParseError?) -> Void ) { do { - try deleteCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in + try deleteCommand().executeAsync(options: options) { result in switch result { - case .success: + case .success(let error): try? Self.updateKeychainIfNeeded([self], deleting: true) - completion(nil) + callbackQueue.async { + completion(error) + } case .failure(let error): - completion(error) + callbackQueue.async { + completion(error) + } } } } catch let error as ParseError { - completion(error) + callbackQueue.async { + completion(error) + } } catch { - completion(ParseError(code: .unknownError, message: error.localizedDescription)) + callbackQueue.async { + completion(ParseError(code: .unknownError, message: error.localizedDescription)) + } } } @@ -740,11 +796,12 @@ public extension Sequence where Element: ParseUser { let currentBatch = try API.Command .batch(commands: $0) .execute(options: options, + callbackQueue: .main, childObjects: childObjects, childFiles: childFiles) returnBatch.append(contentsOf: currentBatch) } - try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + try Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) return returnBatch } @@ -765,74 +822,84 @@ public extension Sequence where Element: ParseUser { callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[(Result)], ParseError>) -> Void ) { - let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit - var childObjects = [String: PointerType]() - var childFiles = [UUID: ParseFile]() - var error: ParseError? - - let users = map { $0 } - for user in users { - let group = DispatchGroup() - group.enter() - user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in - //If an error occurs, everything should be skipped - if parseError != nil { - error = parseError - } - savedChildObjects.forEach {(key, value) in - if error != nil { - return + let queue = DispatchQueue(label: "com.parse.saveAll", qos: .default, + attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil) + queue.sync { + let batchLimit = limit != nil ? limit! : ParseConstants.batchLimit + var childObjects = [String: PointerType]() + var childFiles = [UUID: ParseFile]() + var error: ParseError? + + let users = map { $0 } + for user in users { + let group = DispatchGroup() + group.enter() + user.ensureDeepSave(options: options) { (savedChildObjects, savedChildFiles, parseError) -> Void in + //If an error occurs, everything should be skipped + if parseError != nil { + error = parseError } - if childObjects[key] == nil { - childObjects[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + savedChildObjects.forEach {(key, value) in + if error != nil { + return + } + if childObjects[key] == nil { + childObjects[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - } - savedChildFiles.forEach {(key, value) in - if error != nil { - return + savedChildFiles.forEach {(key, value) in + if error != nil { + return + } + if childFiles[key] == nil { + childFiles[key] = value + } else { + error = ParseError(code: .unknownError, message: "circular dependency") + return + } } - if childFiles[key] == nil { - childFiles[key] = value - } else { - error = ParseError(code: .unknownError, message: "circular dependency") - return + group.leave() + } + group.wait() + if let error = error { + callbackQueue.async { + completion(.failure(error)) } + return } - group.leave() - } - group.wait() - if let error = error { - completion(.failure(error)) - return } - } - var returnBatch = [(Result)]() - let commands = map { $0.saveCommand() } - let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) - var completed = 0 - for batch in batches { - API.Command - .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue, - childObjects: childObjects, - childFiles: childFiles) { results in - switch results { - - case .success(let saved): - returnBatch.append(contentsOf: saved) - if completed == (batches.count - 1) { - try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) - completion(.success(returnBatch)) + var returnBatch = [(Result)]() + let commands = map { $0.saveCommand() } + let batches = BatchUtils.splitArray(commands, valuesPerSegment: batchLimit) + var completed = 0 + for batch in batches { + API.Command + .batch(commands: batch) + .executeAsync(options: options, + callbackQueue: callbackQueue, + childObjects: childObjects, + childFiles: childFiles) { results in + switch results { + + case .success(let saved): + returnBatch.append(contentsOf: saved) + if completed == (batches.count - 1) { + try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()}) + callbackQueue.async { + completion(.success(returnBatch)) + } + } + completed += 1 + case .failure(let error): + callbackQueue.async { + completion(.failure(error)) + } + return } - completed += 1 - case .failure(let error): - completion(.failure(error)) - return } } } @@ -867,7 +934,7 @@ public extension Sequence where Element: ParseUser { message: "objectId \"\(uniqueObjectId)\" was not found in className \"\(Self.Element.className)\""))) } } - try? Self.Element.updateKeychainIfNeeded(fetchedObjects) + try Self.Element.updateKeychainIfNeeded(fetchedObjects) return fetchedObjectsToReturn } else { throw ParseError(code: .unknownError, message: "all items to fetch must be of the same class") @@ -910,14 +977,20 @@ public extension Sequence where Element: ParseUser { } } try? Self.Element.updateKeychainIfNeeded(fetchedObjects) - completion(.success(fetchedObjectsToReturn)) + callbackQueue.async { + completion(.success(fetchedObjectsToReturn)) + } case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } } } else { - completion(.failure(ParseError(code: .unknownError, - message: "all items to fetch must be of the same class"))) + callbackQueue.async { + completion(.failure(ParseError(code: .unknownError, + message: "all items to fetch must be of the same class"))) + } } } @@ -951,7 +1024,7 @@ public extension Sequence where Element: ParseUser { .execute(options: options) returnBatch.append(contentsOf: currentBatch) } - try? Self.Element.updateKeychainIfNeeded(compactMap {$0}) + try Self.Element.updateKeychainIfNeeded(compactMap {$0}) return returnBatch } @@ -989,30 +1062,35 @@ public extension Sequence where Element: ParseUser { for batch in batches { API.Command .batch(commands: batch) - .executeAsync(options: options, - callbackQueue: callbackQueue) { results in + .executeAsync(options: options) { results in switch results { case .success(let saved): returnBatch.append(contentsOf: saved) if completed == (batches.count - 1) { try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0}) - completion(.success(returnBatch)) + callbackQueue.async { + completion(.success(returnBatch)) + } } completed += 1 case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } return } } } } catch { - guard let parseError = error as? ParseError else { - completion(.failure(ParseError(code: .unknownError, - message: error.localizedDescription))) - return + callbackQueue.async { + guard let parseError = error as? ParseError else { + completion(.failure(ParseError(code: .unknownError, + message: error.localizedDescription))) + return + } + completion(.failure(parseError)) } - completion(.failure(parseError)) } } } // swiftlint:disable:this file_length diff --git a/Sources/ParseSwift/Types/ParseCloud.swift b/Sources/ParseSwift/Types/ParseCloud.swift index 0b8a5a622..6fa1bf4e0 100644 --- a/Sources/ParseSwift/Types/ParseCloud.swift +++ b/Sources/ParseSwift/Types/ParseCloud.swift @@ -30,7 +30,7 @@ extension ParseCloud { - returns: Returns a JSON response of `AnyCodable` type. */ public func runFunction(options: API.Options = []) throws -> AnyCodable { - try runFunctionCommand().execute(options: options) + try runFunctionCommand().execute(options: options, callbackQueue: .main) } /** @@ -44,8 +44,11 @@ extension ParseCloud { callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { runFunctionCommand() - .executeAsync(options: options, - callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options, callbackQueue: callbackQueue) { result in + callbackQueue.async { + completion(result) + } + } } internal func runFunctionCommand() -> API.Command { @@ -73,7 +76,7 @@ extension ParseCloud { - returns: Returns a JSON response of `AnyCodable` type. */ public func startJob(options: API.Options = []) throws -> AnyCodable { - try startJobCommand().execute(options: options) + try startJobCommand().execute(options: options, callbackQueue: .main) } /** @@ -87,8 +90,11 @@ extension ParseCloud { callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { startJobCommand() - .executeAsync(options: options, - callbackQueue: callbackQueue, completion: completion) + .executeAsync(options: options, callbackQueue: callbackQueue) { result in + callbackQueue.async { + completion(result) + } + } } internal func startJobCommand() -> API.Command { diff --git a/Sources/ParseSwift/Types/ParseFile.swift b/Sources/ParseSwift/Types/ParseFile.swift index 47a112e95..d5e6a892f 100644 --- a/Sources/ParseSwift/Types/ParseFile.swift +++ b/Sources/ParseSwift/Types/ParseFile.swift @@ -167,7 +167,7 @@ extension ParseFile { throw ParseError(code: .unknownError, message: "You must specify \"useMasterKey\" in \"options\" in order to delete a file.") } - _ = try deleteFileCommand().execute(options: options) + _ = try deleteFileCommand().execute(options: options, callbackQueue: .main) } /** @@ -185,19 +185,22 @@ extension ParseFile { options = options.union(self.options) if !options.contains(.useMasterKey) { - completion(ParseError(code: .unknownError, - // swiftlint:disable:next line_length - message: "You must specify \"useMasterKey\" in \"options\" in order to delete a file.")) + callbackQueue.async { + completion(ParseError(code: .unknownError, + // swiftlint:disable:next line_length + message: "You must specify \"useMasterKey\" in \"options\" in order to delete a file.")) + } return } - deleteFileCommand().executeAsync(options: options, - callbackQueue: callbackQueue) { result in - switch result { - - case .success: - completion(nil) - case .failure(let error): - completion(error) + deleteFileCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in + callbackQueue.async { + switch result { + + case .success: + completion(nil) + case .failure(let error): + completion(error) + } } } } @@ -266,7 +269,11 @@ extension ParseFile { options.insert(.tags(tags)) } options = options.union(self.options) - return try uploadFileCommand().executeStream(options: options, uploadProgress: progress, stream: stream) + return try uploadFileCommand() + .executeStream(options: options, + callbackQueue: .main, + uploadProgress: progress, + stream: stream) } /** @@ -291,9 +298,9 @@ extension ParseFile { options = options.union(self.options) if isDownloadNeeded { let fetched = try fetch(options: options) - return try fetched.uploadFileCommand().execute(options: options) + return try fetched.uploadFileCommand().execute(options: options, callbackQueue: .main) } - return try uploadFileCommand().execute(options: options) + return try uploadFileCommand().execute(options: options, callbackQueue: .main) } /** @@ -352,9 +359,13 @@ extension ParseFile { options = options.union(self.options) if isDownloadNeeded { let fetched = try fetch(options: options) - return try fetched.uploadFileCommand().execute(options: options, uploadProgress: progress) + return try fetched + .uploadFileCommand() + .execute(options: options, + callbackQueue: .main, + uploadProgress: progress) } - return try uploadFileCommand().execute(options: options, uploadProgress: progress) + return try uploadFileCommand().execute(options: options, callbackQueue: .main, uploadProgress: progress) } /** @@ -426,18 +437,26 @@ extension ParseFile { fetched.uploadFileCommand() .executeAsync(options: options, callbackQueue: callbackQueue, - uploadProgress: progress, - completion: completion) + uploadProgress: progress) { result in + callbackQueue.async { + completion(result) + } + } case .failure(let error): - completion(.failure(error)) + callbackQueue.async { + completion(.failure(error)) + } } } } else { uploadFileCommand() .executeAsync(options: options, callbackQueue: callbackQueue, - uploadProgress: progress, - completion: completion) + uploadProgress: progress) { result in + callbackQueue.async { + completion(result) + } + } } } @@ -470,7 +489,10 @@ extension ParseFile { options.insert(.tags(tags)) } options = options.union(self.options) - return try downloadFileCommand().executeStream(options: options, stream: stream) + return try downloadFileCommand() + .executeStream(options: options, + callbackQueue: .main, + stream: stream) } /** @@ -492,7 +514,9 @@ extension ParseFile { options.insert(.tags(tags)) } options = options.union(self.options) - return try downloadFileCommand().execute(options: options) + return try downloadFileCommand() + .execute(options: options, + callbackQueue: .main) } /** @@ -548,7 +572,10 @@ extension ParseFile { options.insert(.tags(tags)) } options = options.union(self.options) - return try downloadFileCommand().execute(options: options, downloadProgress: progress) + return try downloadFileCommand() + .execute(options: options, + callbackQueue: .main, + downloadProgress: progress) } /** @@ -610,9 +637,14 @@ extension ParseFile { options.insert(.tags(tags)) } options = options.union(self.options) - downloadFileCommand().executeAsync(options: options, - callbackQueue: callbackQueue, - downloadProgress: progress, completion: completion) + downloadFileCommand() + .executeAsync(options: options, + callbackQueue: callbackQueue, + downloadProgress: progress) { result in + callbackQueue.async { + completion(result) + } + } } internal func downloadFileCommand() -> API.Command { diff --git a/Sources/ParseSwift/Types/Pointer.swift b/Sources/ParseSwift/Types/Pointer.swift index 417280dcb..88d9ad5a2 100644 --- a/Sources/ParseSwift/Types/Pointer.swift +++ b/Sources/ParseSwift/Types/Pointer.swift @@ -57,7 +57,11 @@ extension Pointer { API.NonParseBodyCommand(method: .GET, path: path) { (data) -> T in try ParseCoding.jsonDecoder().decode(T.self, from: data) - }.executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + }.executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } } diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index bd58ed1fa..93122eed5 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -773,7 +773,11 @@ extension Query: Queryable { */ public func find(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result<[ResultType], ParseError>) -> Void) { - findCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + findCommand().executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } /** @@ -789,8 +793,11 @@ extension Query: Queryable { public func find(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - findCommand(explain: explain, hint: hint).executeAsync(options: options, - callbackQueue: callbackQueue, completion: completion) + findCommand(explain: explain, hint: hint).executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } /** @@ -832,17 +839,20 @@ extension Query: Queryable { */ public func first(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - firstCommand().executeAsync(options: options, callbackQueue: callbackQueue) { result in - - switch result { - case .success(let first): - guard let first = first else { - completion(.failure(ParseError(code: .objectNotFound, message: "Object not found on the server."))) - return + firstCommand().executeAsync(options: options) { result in + + callbackQueue.async { + switch result { + case .success(let first): + guard let first = first else { + completion(.failure(ParseError(code: .objectNotFound, + message: "Object not found on the server."))) + return + } + completion(.success(first)) + case .failure(let error): + completion(.failure(error)) } - completion(.success(first)) - case .failure(let error): - completion(.failure(error)) } } } @@ -861,8 +871,11 @@ extension Query: Queryable { public func first(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - firstCommand(explain: explain, hint: hint).executeAsync(options: options, - callbackQueue: callbackQueue, completion: completion) + firstCommand(explain: explain, hint: hint).executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } /** @@ -901,7 +914,11 @@ extension Query: Queryable { */ public func count(options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - countCommand().executeAsync(options: options, callbackQueue: callbackQueue, completion: completion) + countCommand().executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } /** @@ -916,8 +933,11 @@ extension Query: Queryable { public func count(explain: Bool, hint: String? = nil, options: API.Options = [], callbackQueue: DispatchQueue = .main, completion: @escaping (Result) -> Void) { - countCommand(explain: explain, hint: hint).executeAsync(options: options, - callbackQueue: callbackQueue, completion: completion) + countCommand(explain: explain, hint: hint).executeAsync(options: options) { result in + callbackQueue.async { + completion(result) + } + } } } diff --git a/Tests/ParseSwiftTests/ParseObjectTests.swift b/Tests/ParseSwiftTests/ParseObjectTests.swift index 045092991..bbc4b51f1 100644 --- a/Tests/ParseSwiftTests/ParseObjectTests.swift +++ b/Tests/ParseSwiftTests/ParseObjectTests.swift @@ -1151,6 +1151,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length guard let savedGame = try? game .saveCommand() .execute(options: [], + callbackQueue: .main, childObjects: savedChildren, childFiles: savedChildFiles) else { XCTFail("Should have saved game") @@ -1360,6 +1361,7 @@ class ParseObjectTests: XCTestCase { // swiftlint:disable:this type_body_length guard let savedGame = try? game .saveCommand() .execute(options: [], + callbackQueue: .main, childObjects: savedChildren, childFiles: savedChildFiles) else { XCTFail("Should have saved game") From 005406fb9c17789acb4b933e09f603b848eaf4d6 Mon Sep 17 00:00:00 2001 From: Corey Date: Tue, 12 Jan 2021 13:44:44 -0500 Subject: [PATCH 2/2] Drop patch in codecov for now --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 0783c91b4..8f078c0e1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,7 +5,7 @@ coverage: status: patch: default: - target: auto + target: 67 changes: false project: default: