Completion Handler Call N times - ios

In the Button action, I call getLatLongValues method, It will return completion property after API success.
Hear problem is, If I click N th times in button action, getLatLongValues method execute N times.
Like I click the button in 1st time getLatLongValues execute 1 time, I'm click 2nd time not two times getLatLongValues method execute 2 times.
#IBAction func updateDeliveryAddress() {
guard let address = self.addressTextField.text else { return }
self.getLatLongValues(address, true, viewModel) { success in
if success {
//Success
} else {
// Error
}
}
}
func getLatLongValues(address: String, setAsDefault: Bool, viewModel:ViewModel, completion: #escaping (_ success: Bool) -> Void) {
viewModel.location.subscribe(onNext: { [weak self] results in
guard self != nil else { return }
if let result = results {
completion(true) // Success
}
}).disposed(by: disposeBag)
viewModel.fetchLocation(address: address)
}
Why getLatLongValues Execute N times?

because each time you are creating a new subscription.
a subscription is not a completion handler that gets executed once.
viewModel.location.subscribe(onNext: { [weak self] results in
should only be called once, and on each location update, you get the result in the block

If you want the completion handler to only be called once, you should set a flag:
var completionHandlerExecuted = false /// false at first
func getLatLongValues(address: String, setAsDefault: Bool, viewModel:ViewModel, completion: #escaping (_ success: Bool) -> Void) {
if completionHandlerExecuted == false {
completionHandlerExecuted = true /// set to true, so it won't be called again
viewModel.location.subscribe(onNext: { [weak self] results in
guard self != nil else { return }
if let result = results {
completion(true) // Success
}
}).disposed(by: disposeBag)
viewModel.fetchLocation(address: address)
}
}
Completion handlers are like any other instruction you put in your functions.
Every time getLatLongValues is called, you are doing a subscribe, which will call the completion handler once it's finished.

Related

Background thread - two network calls [duplicate]

This question already has answers here:
DispatchGroup logical workflow
(2 answers)
Closed 1 year ago.
I have two methods in the completeOnboarding method and both of them have network operation which should be done in the background thread as follows. However, I am wondering if I am doing why completion(true) gets called first, how could I able to handle that issue?
DispatchQueue.global(qos: .background).async {
self?.completeOnboarding( completion: { (success) in
DispatchQueue.main.async {
if success {
print("success")
} else {
print("failed")
}
}
})
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
// has network post operation
classLocation() { (success) in
if !success {
completion(false)
return
}
}
completion(true)
}
The final completion(true) is not waiting for classLocation() and classRegistration() calls. If you have multiple network calls and you want to wait for all of them to finish you could (one approach) add them to a DispatchGroup and wait for that one to finish:
func dispatchAndWait(completion: #escaping () -> Void) {
func networkOne(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
func networkTwo(completion: #escaping (_ success: Bool) -> Void) {
print("[DEBUG] Enter \(#function)")
DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0...3)) {
completion(Bool.random())
}
print("[DEBUG] Return \(#function)")
}
// Create a DispatchGroup and add both calls
let dispatchGroup = DispatchGroup()
// Enter first network call
dispatchGroup.enter()
networkOne { success in
print("[DEBUG] Complete networkOne with success: \(success)")
// Exit first network call
dispatchGroup.leave()
}
// Enter second network call
dispatchGroup.enter()
networkTwo { success in
print("[DEBUG] Complete networkTwo with success: \(success)")
// Exit second network call
dispatchGroup.leave()
}
// notify gets called when all tasks have exited
dispatchGroup.notify(queue: DispatchQueue.main) {
completion()
}
}
One more advise:
classRegistration() {(success) in
if !success {
completion(false)
return
}
}
will never complete in case of success==true, you should make sure that the completion is called on every path
classRegistration() {(success) in
// ... do whatever needs to be done here
completion(success)
}
Assuming classRegistration needs to succeed for classLocation to begin --
quick & dirty --
func completeOnboarding(completion: #escaping(Bool) -> Void){
// has network post operation
classRegistration() {(success) in
if success {
// has network post operation
classLocation() { (success) in
completion(success)
}
} else {
completion(false)
}
}
}
a proper way (others include - NSOperations with dependency, Dispatch group)
func completeOnboarding(completion: #escaping(Bool) -> Void){
let serialQueue = DispatchQueue(label: "classname.serial")
var proceedWithSuccess = true
serialQueue.async {
serialQueue.suspend() //run 1 operation at a time
classRegistration() {(success) in
proceedWithSuccess = success
serialQueue.resume() //let next operation run
}
}
serialQueue.async {
guard proceedWithSuccess else { return }
serialQueue.suspend()
classLocation() { (success) in
proceedWithSuccess = success
serialQueue.resume()
}
}
serialQueue.async {
completion(proceedWithSuccess)
}
}
If you want classLocation() to fire even if registration fails - just get rid of guard statement above.
If it were up to me I'd use a custom NSOperation subclass for Async operation & explicitly mention dependency between operations but it needs a ton of boilerplate code (perhaps something to look into later); serial queue (or dispatch group from the other answer) oughta be enough for you in this case though.

How to skip completion handler in Swift

I have one function which return Int value in completion handler, however sometimes, I want to skip completion handler while calling from other class and just have Int value. Below is my code. Here totalEvents is with completion handler.
Like I need to call below method
let initialDBCount = self.totalEvents()
func totalEvents(completion: #escaping (_ eventsCount: Int? ) -> Void ) {
self.fetchEvents(forPredicate: nil, withSort: nil, andLimit: nil, completion: { (events) -> Void in
guard let fetchEvents = events else {
return
}
if fetchEvents.count > 0 {
completion(fetchEvents.count)
}
})
}
Make the completion handler as optional and set nil as its default value, i.e.
func totalEvents(completion: ((_ eventsCount: Int?)->())? = nil)
Usage:
totalEvents can be called in both the ways,
1. Without completion handler
totalEvents()
2. With completion handler
totalEvents { (value) in
print(value)
}
Make optional the completion (_eventsCount: Int?) -> () )? = nil or call the completion like
if fetchEvents.count > 0 {
completion(fetchEvents.count)
}

How to call multiple functions in order one after another as long as the previous is complete

I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.

How to use DispatchGroup to make asynchronous calls within a for loop

In my example code below, I call complete(false) on failure. However, since I'm using a DispatchGroup object to make sure all asynchronous requests are complete, I cannot just call syncGroup.leave() on failure, as the notify will be called, which contains complete(true), making this function return true, when it should be returning false for failure.
Am I correct in not calling syncGroup.leave() on failure to complete my function correctly? Or should I be calling syncGroup.leave() and somehow trying to determine what the result is, so I can return false on failure?
let syncGroup = DispatchGroup()
syncGroup.enter()
for track in unsynced {
register(time: time, withCompletion: { (success: Bool) -> () in
if success {
self.debug.log(tag: "SyncController", content: "Registered")
syncGroup.leave()
}
else {
complete(false)
}
})
}
//all requests complete
syncGroup.notify(queue: .main) {
self.debug.log(tag: "SyncController", content: "Finished registering")
complete(true)
}
You have to enter the group within your for loop. You might want to introduce an additional error flag.
Example implementation:
var fail = false
let syncGroup = DispatchGroup()
for track in unsynced {
syncGroup.enter()
register(time: time, withCompletion: { (success: Bool) -> () in
if success {
self.debug.log(tag: "SyncController", content: "Registered")
syncGroup.leave()
}
else {
fail = true
syncGroup.leave()
}
})
}
//all requests complete
syncGroup.notify(queue: .main) {
if fail {
complete(false)
} else {
self.debug.log(tag: "SyncController", content: "Finished registering")
complete(true)
}
}

True if completionhandler is done

My app is heavily dependent on the data that is coming. I want it to run the activity indicator and disable user interaction on the view while the data is being downloaded.
Is there a way to check or return something when the completion handler is done?
typealias CompletionHandler = (success:Bool) -> Void
func downloadFileFromURL(url: NSURL,completionHandler: CompletionHandler) {
**download code**
let flag = true
true if download succeed,false otherwise
completionHandler(success: flag)
}
How to use it.
downloadFileFromURL(NSURL(string: "url_str")!, { (success) -> Void in
**When download completes,control flow goes here.**
if success {
} else {
}
})
Define a property, which keeps completion handler, and call it when all the data is obtained:
var didObtainAllData: (() -> Void)?
func obtainData() {
....
// When data is obtained.
didObtainAllData?()
}
You can write
func processingTask(condition: String, completionHandler:(finished: Bool)-> Void) ->Void {
}
Use
processingTask("test") { (finished) in
if finished {
// To do task you want
}
}

Resources