Thanks in advance for help,
I have two API calls, both are concurrent and any call could be success first(I don't want call in sequence), after success of both calls, I have to stop my activity indicator and reload my tableView,
Here is my code but I don't know is this the right way or not and how to reload my tableView and stop my activity indicator.
func downloadDetails(){
let operationQueue: OperationQueue = OperationQueue()
let operation1 = BlockOperation() {
WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
}
}
})
let operation2 = BlockOperation() {
webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
}
}
})
}
operationQueue.addOperation(operation2)
}
operationQueue.addOperation(operation1)
}
downloadDetails() "calling function"
This is exactly the use case for DispatchGroup. Enter the group for each call, leave the group when the call finishes, and add a notification handler to fire when they're all done. There's no need for a separate operation queue; these are already asynchronous operations.
func downloadDetails(){
let dispatchGroup = DispatchGroup()
dispatchGroup.enter() // <<---
WebServiceManager.getAData(format:A, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
dispatchGroup.leave() // <<----
}
}
})
dispatchGroup.enter() // <<---
webServiceManager.getBData(format: B, withCompletion: {(data: Any? , error: Error?) -> Void in
if let success = data {
DispatchQueue.main.async {
(success code)
dispatchGroup.leave() // <<----
}
}
})
dispatchGroup.notify(queue: .main) {
// whatever you want to do when both are done
}
}
I would use OperationQueue.
It is preferred for long running task and give you control to cancel request, if needed.
At the end of each operation you can check operation count to know remaining operations.
I have added pseudo code.
let operationQueue: OperationQueue = OperationQueue()
func downloadDetails(){
let operation1 = BlockOperation() { [weak self] in
guard let strongSelf = self else {
return
}
sleep(2)
DispatchQueue.main.async {
strongSelf.handleResponse()
}
let operation2 = BlockOperation() { [weak self] in
guard let strongSelf = self else {
return
}
sleep(2)
DispatchQueue.main.async {
strongSelf.handleResponse()
}
}
strongSelf.operationQueue.addOperation(operation2)
}
self.operationQueue.addOperation(operation1)
}
func handleResponse() {
print("OPERATIONS IN PROGRESS: \(self.operationQueue.operations.count)")
if self.operationQueue.operations.count == 0 {
print("ALL OPERATIONS ARE COMPLETE")
}
}
func cancelOperation() {
self.operationQueue.cancelAllOperations()
}
This prints
OPERATIONS IN PROGRESS: 1
OPERATIONS IN PROGRESS: 0
ALL OPERATIONS ARE COMPLETE
Related
I am trying to exit from for loop according to condition but I ran into the issue as it does not exit even from the loop.
Here is a loop of my code.
var isFailure = true
let dispatchGroup = DispatchGroup()
var myFailureTask: Int?
for item in 1...5 {
dispatchGroup.enter()
test(item: item, completion: {
print("Success\(item)")
dispatchGroup.leave()
}, failureBlock: {
print("Failure\(item)")
myFailureTask = item
dispatchGroup.leave()
return
})
dispatchGroup.wait()
}
dispatchGroup.notify(queue: .main) {
if let myFailure = myFailureTask {
print("task failure \(myFailure)")
} else {
print("all task done")
}
}
func test(item: Int,completion: #escaping(() -> ()), failureBlock: #escaping(() -> ())) {
Thread.sleep(forTimeInterval: TimeInterval(item))
isFailure = !isFailure
if isFailure {
failureBlock()
} else {
completion()
}
}
The return returns from current scope.
In this case it's returning from the failureBlock: {} and NOT from the for loop scope.
You have to refactor the code to achieve what you are trying to do.
EITHER (in case this code is executing synchronously) you can return a success value true | false from the function by making function's return type Bool and removing the failureBlock argument.
OR (in case this code is executing asynchronously) you have to think of waiting on one task to complete/fail before triggering the other.
UPDATE
I think following might be a simplified version of this code -
var isFailure: Bool = false
func callTest(for item: Int) {
print("task initiated \(item)")
test(item: item, completion: {
print("task succeeded \(item)")
if item < 5 {
callTest(for: item+1)
} else {
print("all tasks done")
}
}, failureBlock: {
print("task failed \(item)")
})
}
func test(item: Int, completion: #escaping (() -> Void), failureBlock: #escaping (() -> Void)) {
Thread.sleep(forTimeInterval: TimeInterval(item))
isFailure.toggle()
if isFailure {
failureBlock()
} else {
completion()
}
}
callTest(for: 1)
var isFailure = false
let dispatchGroup = DispatchGroup()
var myFailureTask: Int?
for item in 1...5 {
dispatchGroup.enter()
test(item: item, completion: {
print("Success\(item)")
dispatchGroup.leave()
}, failureBlock: {
print("Failure\(item)")
myFailureTask = item
dispatchGroup.leave()
})
if isFailure == true {
break
}
dispatchGroup.wait()
}
dispatchGroup.notify(queue: .main) {
if let myFailure = myFailureTask {
print("task failure \(myFailure)")
} else {
print("all task done")
}
}
func test(item: Int,completion: #escaping(() -> ()), failureBlock: #escaping(() -> ())) {
Thread.sleep(forTimeInterval: TimeInterval(item))
isFailure = !isFailure
if isFailure {
failureBlock()
} else {
completion()
}
}```
Made few changes work like a charm.
Any one has better idea please comment
I am trying to delete all the cart items through a for loop like this:
func removeRelatedProductFromUserCart(selectArrayNodeIds: [String], completion: #escaping(Error?)->()) {
let semaphore = DispatchSemaphore(value: 0)
let serialQueue = DispatchQueue(label: "com.queue.Serial")
serialQueue.async {
for nodeId in selectArrayNodeIds {
FirebaseManager.shared.removeProductFromUsersWishOrCartList(isForWishList: false, item: nodeId) { (error) in
completion(error)
semaphore.signal()
}
semaphore.wait()
}
}
}
And Firebasemanager:
func removeProductFromUsersWishOrCartList(isForWishList: Bool, item: String?, completion: #escaping (Error?) -> ()) {
let uid = UserDefaults.standard.string(forKey: "uid")!
if isForWishList == true {
Firestore.firestore().collection("user").document(uid).updateData([
"wishList": FieldValue.arrayRemove([item!])
]) { (error) in
completion(error)
}
} else {
Firestore.firestore().collection("user").document(uid).updateData([
"cart": FieldValue.arrayRemove([item!])
]) { (error) in
completion(error)
}
}
}
And then when I am trying to fetch the cart items from the same field, I am getting the updated list of cart items after a delay. I observed the delete process opening firebase console and what I have found is that the delete happens in console with a delay. But the error response (error == nill) doesn't wait till then in removeRelatedProductFromUserCart. So how can I wait till delete all cart items on Firestore and then load them? Thanks a lot in advance.
1- Firebase runs in a background thread so no need for
let serialQueue = DispatchQueue(label: "com.queue.Serial")
2- You need a DispatchGroup
func removeRelatedProductFromUserCart(selectArrayNodeIds: [String], completion: #escaping(Bool)->()) {
let g = DispatchGroup()
for nodeId in selectArrayNodeIds {
g.enter()
FirebaseManager.shared.removeProductFromUsersWishOrCartList(isForWishList: false, item: nodeId) { (error) in
q.leave()
}
}
g.notify(queue:.main) {
completion(true)
}
}
I want to run 2 pieces of asynchronous code in one function and escape them. I want first to download the Reciter information and then download with these information the images that is associated with the Reciter. I'm using Firestore. I tried to work with DispatchQueue and DispatchGroup but I couldn't figure something out. I hope someone can help me :)
func getReciters(completion: #escaping (Bool) -> Void) {
var reciters = [Reciter]()
self.BASE_URL.collection(REF_RECITERS).getDocuments { (snapchot, error) in
if let error = error {
debugPrint(error)
completion(false)
// TODO ADD UIALTERCONTROLLER MESSAGE
return
}
guard let snapchot = snapchot else { debugPrint("NO SNAPSHOT"); completion(false); return }
for reciter in snapchot.documents {
let data = reciter.data()
let reciterName = data[REF_RECITER_NAME] as? String ?? "ERROR"
let numberOfSurahs = data[REF_NUMBER_OF_SURAHS] as? Int ?? 0
// **HERE I WANT TO DOWNLOAD THE IMAGES**
self.downloadImage(forDocumentID: reciter.documentID, completion: { (image) in
let reciter = Reciter(name: reciterName, image: nil, surahCount: numberOfSurahs, documentID: reciter.documentID)
reciters.append(reciter)
})
}
}
UserDefaults.standard.saveReciters(reciters)
completion(true)
}
You need DispatchGroup.
In the scope of the function declare an instance of DispatchGroup.
In the loop before the asynchronous block call enter.
In the loop inside the completion handler of the asynchronous block call leave.
After the loop call notify, the closure will be executed after all asynchronous tasks have finished.
func getReciters(completion: #escaping (Bool) -> Void) {
var reciters = [Reciter]()
self.BASE_URL.collection(REF_RECITERS).getDocuments { (snapchot, error) in
if let error = error {
debugPrint(error)
completion(false)
// TODO ADD UIALTERCONTROLLER MESSAGE
return
}
guard let snapchot = snapchot else { debugPrint("NO SNAPSHOT"); completion(false); return }
let group = DispatchGroup()
for reciter in snapchot.documents {
let data = reciter.data()
let reciterName = data[REF_RECITER_NAME] as? String ?? "ERROR"
let numberOfSurahs = data[REF_NUMBER_OF_SURAHS] as? Int ?? 0
group.enter()
self.downloadImage(forDocumentID: reciter.documentID, completion: { (image) in
let reciter = Reciter(name: reciterName, image: nil, surahCount: numberOfSurahs, documentID: reciter.documentID)
reciters.append(reciter)
group.leave()
})
}
group.notify(queue: .main) {
UserDefaults.standard.saveReciters(reciters)
completion(true)
}
}
}
I am trying to use dispatch group as suggested here https://stackoverflow.com/a/35906703/406322
However, it seems like myGroup.notify is being called before all the iterations of the for loop is completed. What am I doing wrong?
let myGroup = DispatchGroup()
for channel in channels.subscribedChannels() {
myGroup.enter()
buildUser(channel) { (success, user) in
if success {
addUser(user)
}
print("Finished request \(user.id)")
myGroup.leave()
}
}
myGroup.notify(queue: .main) {
print("Finished all requests.")
}
The output is this:
Finished request 1
Finished all requests.
Finished request 2
Not sure, but isn't your print("Finished request \(user.id)") being called from a thread and therefore can be called after your print("Finished all requests.") since it's on a main priority queue ?
try replacing
print("Finished request \(user.id)")
by:
DispatchQueue.main.async {
print("Finished request \(user.id)")
}
Testing it out in a playground works fine:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
class User {
var id: Int
init(id: Int) {
self.id = id
}
}
class Channel {
var user: User
init(user: User) {
self.user = user
}
}
var subscribedChannels: [Channel] = []
let user1 = User(id: 1)
let user2 = User(id: 2)
subscribedChannels.append(Channel(user: user1))
subscribedChannels.append(Channel(user: user2))
let myGroup = DispatchGroup()
let bgQueue = DispatchQueue.global(qos: .background)
func doSomething(channel: Channel, callback: #escaping (Bool, User) -> Void) {
print("called for \(channel.user.id)")
bgQueue.asyncAfter(deadline: .now() + 1) {
callback(true, channel.user)
}
}
for channel in subscribedChannels {
myGroup.enter()
doSomething(channel: channel) { (success, user) in
if success {
//
}
print("Finished request \(user.id)")
myGroup.leave()
}
}
myGroup.notify(queue: .main) {
print("Finished all requests.")
}
this prints
called for 1
called for 2
then 1 second later
Finished request 1
Finished request 2
Finished all requests.
I don't know your classes and methods so it's hard for me to know more
lets propose this scenario
a method with async network operations
func asyncMethodA() -> String?
{
result : String?
Alamofire.manager.request(.POST, "https://www.apiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200)
{
//DO SOME HEAVY LIFTING
}
}
return result //string
}
another method with async network operations
func asyncMethodB() -> String?
{
result : String?
Alamofire.manager.request(.POST, "https://www.yetanotherapiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200)
{
//DO SOME HEAVY LIFTING
}
}
return result //string
}
a method in which i shall call those methods A and B, to do some operations
func displayResult
{
1) let a = asyncMethodA()
2) let b = asyncMethodB()
3) println(a + b) //some chaotic stuff might happen :(
}
so the question is how i could make that (2) waits for (1) to run, and (3) waits for (2) and so on (that 1,2 and 3 run syncronised)?
(i know that one answer is to chain asyncMethodA and displayResult into asyncMethodB, but want to know if there is some other method)
thank you!.
func anAsyncMethod(resultHandler: (result: AnyObject) -> Void) {
...
}
func anotherAsyncMethod(resultHandler: (result: AnyObject) -> Void) {
...
}
let operationQueue = NSOperationQueue()
func performWithCompletionHandler(completion: (AnyObject?, AnyObject?) -> Void) {
var resultOfOperation1: AnyObject?
var resultOfOperation2: AnyObject?
let operation1 = NSBlockOperation {
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
self.anAsyncMethod {
result in
resultOfOperation1 = result
dispatch_group_leave(dispatchGroup)
}
// wait until anAsyncMethod is completed
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
}
let operation2 = NSBlockOperation {
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
self.anotherAsyncMethod {
result in
resultOfOperation2 = result
dispatch_group_leave(dispatchGroup)
}
// wait until anotherAsyncMethod is completed
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
}
let completionOperation = NSBlockOperation {
// send all results to completion callback
completion(resultOfOperation1, resultOfOperation2)
}
// configuring interoperation dependencies
operation2.addDependency(operation1)
completionOperation.addDependency(operation2)
operationQueue.addOperations([operation1, operation2, completionOperation], waitUntilFinished: false)
}
Thanks Yimin for the code above. I've updated it to the latest Swift syntax so just posting to be helpful:
func anAsyncMethod(resultHandler: (_ result: AnyObject) -> Void) {
...
}
func anotherAsyncMethod(resultHandler: (_ result: AnyObject) -> Void) {
...
}
func performWithCompletionHandler(completion: #escaping (AnyObject?, AnyObject?) -> Void) {
let operationQueue = OperationQueue()
var resultOfOperation1: AnyObject?
var resultOfOperation2: AnyObject?
let operation1 = BlockOperation {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
self.anAsyncMethod {
result in
resultOfOperation1 = result
dispatchGroup.leave()
}
// wait until anAsyncMethod is completed
dispatchGroup.wait(timeout: DispatchTime.distantFuture)
}
let operation2 = BlockOperation {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
self.anotherAsyncMethod {
result in
resultOfOperation2 = result
dispatchGroup.leave()
}
// wait until anotherAsyncMethod is completed
dispatchGroup.wait(timeout: DispatchTime.distantFuture)
}
let completionOperation = BlockOperation {
// send all results to completion callback
completion(resultOfOperation1, resultOfOperation2)
}
// configuring interoperation dependencies
operation2.addDependency(operation1)
completionOperation.addDependency(operation2)
operationQueue.addOperations([operation1, operation2, completionOperation], waitUntilFinished: false)
}
With the below, you can launch both async methods at the same time and do your heavy lifting after whichever one finishes last.
var methodAFinished = false
var methodBFinished = false
func asyncMethodA() -> String?
{
Alamofire.manager.request(.POST, "https://www.apiweb.com/apimethod", parameters: parameters, encoding:.JSON)
.response { (request, response, rawdata, error) in
if (response?.statusCode == 200) {
methodAFinished = true
doStuff()
}
}
return result //string
}
The guts of asyncMethodB would be methodBFinished = true; doStuff()
func doStuff() {
if methodAFinished && methodBFinished {
// do crazy stuff
}
}