Return value when asynchronous tasks are completed - ios

I want to implement performFetchWithCompletionHandler in my appdelegate. I want to do something like this
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
DownloadManager.shared.startDownload(completion: { (done) in
if done{
completionHandler(.newData)
}else{
completionHandler(.noData)
}
})
I am not sure how to implement this on my DownloadManager. Its actual implementation looks like
class DownloadManager{
var pagesDownloadCompleted = false
var imagesDownloadCompleted = false
// Function to start the download
func startDownload(date: DataToDownload){
...
downloadPages(pages)
downloadImages(images)
}
}
// Function to download the pages
func downloadPages(pages: Pages){
....
if completed{
pagesDownloadCompleted = true
}
}
// Function to download the images
func downloadImages(images: Images){
....
if completed{
imagesDownloadCompleted = true
}
}
// Function to check if the doanload is done
func downloadCompleted() -> Bool{
return pagesDownloadCompleted == true && imagesDownloadCompleted == true
}
}
}
A possible solution is to start downloading the pages, when I am done I start downloading the images, and when the images are done I return true. But downloading the images and the pages will not be parallel. Do you see any other solution?

Should be something like this, you still need to handle failed and data flow though, try to use dispatch group is another solution also:
class DownloadManager {
var pagesDownloadCompleted = false
var imagesDownloadCompleted = false
// Function to start the download
func startDownload(date: String, completion: (_ completed: Bool)->()){
downloadPages(pages: page) { (completed) in
if completed {
pagesDownloadCompleted = true
}
self.downloadCompleted(completion: completion)
}
downloadImages(images: image) { (completed) in
if completed {
imagesDownloadCompleted = true
}
self.downloadCompleted(completion: completion)
}
}
// Function to download the pages
func downloadPages(pages: Pages, completion: (_ completed: Bool)->()){
completion(true) //When download finished
}
// Function to download the images
func downloadImages(images: Images, completion: (_ completed: Bool)->()){
completion(true) //When download finished
}
// Function to check if the doanload is done
func downloadCompleted(completion: (_ completed: Bool)->()){
if pagesDownloadCompleted == true && imagesDownloadCompleted == true {
completion(true)
}
}
}

IMO this kind of problem is best solved using DispatchGroup. Whenever you start a download, call enter, when that's finished call leave. group.notify is used to determine when all downloads are done.
class DownloadManager{
var group = DispatchGroup()
func startDownload(date: DataToDownload) {
downloadPages(pages)
downloadImages(images)
group.notify(queue: DispatchQueue.main) {
// all downloads completed
}
}
// Function to download the pages
func downloadPages(_ pages: Pages){
group.enter()
// do the download, when done, call:
group.leave()
}
// Function to download the images
func downloadImages(_ images: Images){
group.enter()
// do the download, when done, call:
group.leave()
}
}

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.

Completion Handler Call N times

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.

OperationQueue / DispatchGroup and recursion

I have a problem understanding how to use GCD when using asynchronous, recursive calls to the API.
Below is one of the three similar methods that contains the same logic, just for different data and API endpoint. If there is no next page request the method should finish and next method should start.
How would I make sure that fetchItems2 gets called after fetchItems1 finishes, and fetchItems3 after fetchItems2?
private func fetchItems1(completion: #escaping (Error?) -> Void) {
var _items = [Item]()
func handleReceivedItemsPage(_ page: PagingObject<Item>, _completion: ((Error?) -> Void)?) {
let newItems = page.items!
_tracks.append(contentsOf: newTracks)
if page.canMakeNextRequest {
page.getNext(success: { nextPage in
handleReceivedItemsPage(nextPage)
}) { nextError in
_completion?(nextError)
}
} else {
// Finished, next method can now start
self.items = _items
_completion?(nil)
}
}
API.getSavedItems(success: { page in
handleReceivedItemsPage(page, _completion: completion)
}, failure: completion)
}
private func fetchItems2(completion: #escaping (Error?) -> Void)) { ... }
private func fetchItems3(completion: #escaping (Error?) -> Void)) { ... }
You can keep an extra variable that keeps track of when the API calls are complete. In the completion block, increment this variable. Then, when the variable reaches the amount of API calls complete, perform your task.
I would use DispatchGroup:
public void FetchItems(completion: #escaping (Error?) -> Void) {
let group = DispatchGroup()
group.enter()
fetchItems1() { error in
completion(error)
group.leave()
}
group.wait()
group.enter()
fetchItems2() { error in
completion(error)
group.leave()
}
// 3rd function call
}
Code after group.wait() is not called until the number of group.enter() and group.leave() invocations is equal.

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.

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