Reputation: 225
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
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)")
}
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