Ali_C
Ali_C

Reputation: 225

getting a fully populated array outside the trailing closure after asynchronous function finishes executing in Swift

I am trying to populate finalArray with the result of asynchronous call from my extractProperties() function.

class ViewController: UIViewController {

var finalArray: [SingleRepository] = []

let extractor = Extractor()

override func viewDidLoad() {
    super.viewDidLoad()

    print("Starting the program... ")

    extractor.extractProperties { object, error in
        guard let object = object else {
            print("Extractor did not reutrn data")
            return
        }
        self.finalArray.append(object)
        print("Appended successfully --- \(self.finalArray.count) --- inside the trailing closure")

    }

    print("Size of the array --- \(self.finalArray) --- outside the trailing closure")


}

The issue is that I can't get the fully populated finalArray to work with outside the scope of trailing closure! Output log:

    Starting the program... 
Size of the array --- [] --- outside the trailing closure
Appended successfully --- 1 --- inside the trailing closure
Appended successfully --- 2 --- inside the trailing closure
Appended successfully --- 3 --- inside the trailing closure
.
.
.
Appended successfully --- 300 --- inside the trailing closure

I know why the print statement from outside get executed first, but I never can get my fully populated array with all 300 objects in it.

Please note that the following post did NOT solve my problem:Run code only after asynchronous function finishes executing

I even tried solution in that post by writing a the following function:

func constructingFinalArray(completionBlock: @escaping ([SingleRepository]) -> Void) {
        var fArrray: [SingleRepository] = []
        extractor.extractProperties { data, error in
            guard let data = data else {
                print("Extractor did not reutrn data")
                return
            }
            fArrray.append(data)
            completionBlock(fArrray)
        }
    }

and called it inside viewDidLoad() as following, but confusingly I got the same result and array gets populated element by element, therefore never be able to access a fully populated array from out of trailing closures!

constructingFinalArray { array in
        print("array size from constructingFinalArray function: \(array.count) ")
    }

output:

    Starting the program... 
array size from constructingFinalArray function: 1 
array size from constructingFinalArray function: 2 
array size from constructingFinalArray function: 3
.
.
.

extractProperties get called 300 times exactly and sometimes it returns no date(Error).

    // Class to extract the required properties and
// provide ready to use object for ViewController
class Extractor {
    private let client = RepoViewerAPIClient()
    private var allURLs: [RepositoryURL] = []
    var allRepositories: [SingleRepository] = []


    // Method to extract all the required properties
    // compromising of 2 asynchrounous call, (nested asynch call)
    // one to get the urls and another call within the first call to extract all the propreties
    func extractProperties(completionHandler: @escaping (SingleRepository?, RepoViewerErrors?) -> Void) {
        // implementation of nested asynchronous calls are deleted to shorten the question length 


    }

}

Upvotes: 0

Views: 189

Answers (1)

Luca Angeletti
Luca Angeletti

Reputation: 59496

It seems that after you call once

extractor.extractProperties {
    ...
}

The closure gets called exactly 300 times but sometimes it can return no data.

In this case you can follow this approach.

extractor.extractProperties { object, error in
    serialQueue.async { [weak self] in

        count += 1

        guard count < 300 else  {
            self?.didCompletePopulation()
            return
        }

        guard let object = object else {
            print("Extractor did not reutrn data")
            return
        }

        self?.finalArray.append(object)

    }
}

func didCompletePopulation() {
    // be aware, this is not called on the main thread
    // if you need to update the UI from here then use the main thread
    print("Final array is populated \(self.finalArray)")
}

How does it work?

The body of the closure is wrapped is wrapped into another closure executed through a Serial Queue. This way we are sure the share resources (finalArray and count) are accessed safely.

serialQueue.async { [weak self] in
    ...
}

Next every execution of the closure increments count by 1.

Then we make sure count is minor than 300, otherwise we stop the execution of the closure an call didCompletePopulation().

guard count < 300 else  {
    self?.didCompletePopulation()
    return
}

We check if the result contains a proper value otherwise we stop the execution of the current closure

guard let object = object else {
    print("Extractor did not reutrn data")
    return
}

And finally we add the new element to the array

self?.finalArray.append(object)

Upvotes: 4

Related Questions