Reputation: 2913
Perhaps I have an underlying issue understanding the overall premise, but I am trying to figure out the best approach to do something with an array of items, and end the test early once a certain criteria is found.
For example, I have an array of names;
var names = ["Bob", "Billy", "Sarah", "Brandon", "Brian", "Rick"]
I would like to test each name in the array and see if it exists in a database. To do this, I'm calling another function with a completion handler;
for name in names {
TestName(name) { response in
if response {
// END THE LOOP
} else {
// KEEP GOING
}
}
I've not been able to figure out the // END THE LOOP. For the purposes of this example, I'm only concerned when the response is true the first time (if Billy exists in the array, I have no further interest in testing Sarah, Brandon, Brian, or Rick).
Thank you!
Upvotes: 2
Views: 2848
Reputation: 75
I had to do the same thing as PEEJWEEJ. My async tasks are web queries and parsing and quite intensive and the for loop always finished before those tasks did so there was no way to stop them.
So I converted it to recursive and set a flag when I wanted it to stop and it works fine now.
// ************************************************************************************************************
// MARK: Recursive function
// I changed the loop to be recursive so I could stop when I need to - i.e. when selecting another race
// I set the stopFetching flag in the seque when changing races and the current set of BG queries stops
// ************************************************************************************************************
func getRaceResultsRecursive(race: Race, bibs: [Bib], index: Int, completion: @escaping (Bool?) -> ())
{
guard index < bibs.count else{
completion(nil)
return
}
var url: URL
self.stopFetching = false
let bibID = bibs[index]
url = self.athleteResultAPI.formatURL(baseURL: (race.baseURL)!,
rd: (race.rdQueryItem)!,
race: (race.raceQueryItem)!,
bibID: "\(bibID.bib!)",
detail: (race.detailQueryItem)!,
fragment: (race.detailQueryItem)!,
webQueryType: (race.webQueryType!))
self.athleteResultAPI.fetchAthleteResults(url: url, webQueryType: race.webQueryType!)
{
(athleteReturnCode) in
switch athleteReturnCode
{
case let .success(athleteResult):
self.athleteResults.append(athleteResult) // Add to collection
self.delegate?.athleteResultsUpdated() // update Delegate to notify of change
case let .failure(error):
print(error)
break
}
if self.stopFetching {
completion(true)
}
else {
self.getRaceResultsRecursive(race: race, bibs: bibs, index: index + 1, completion: completion)
}
}
}
// ************************************************************************************************************
// getRaceResults
// Get all the bibs to track for this race from the iCloudKit database
// ************************************************************************************************************
func getRaceResults(race: Race)
{
// get all the races and bibs an put in the Races Store
raceStore.fetchBibsForRace(race: race, foreignKey: race.recordID)
{
(results, error) in
if let error = error {
print("Error in fetchBibsForRace(): \(error)")
return
}
// clear the store since we are on a new race
self.athleteResults.removeAll()
self.getRaceResultsRecursive(race: race, bibs: race.bibs, index: 0)
{
(stopFetching) in
return
}
}
}
Upvotes: 0
Reputation: 535335
Before you start the loop, set a flag:
var exitEarly = false
for name in names {
Test the flag each time thru the loop:
for name in names {
if exitEarly {
break
}
In the TestName response block, set the flag:
TestName(name) { response in
if response {
exitEarly = true
} else {
// KEEP GOING
}
}
Note, however, that if TestName's block is executed asynchronously, that won't work, because the whole loop precedes the calling of any of the asynchronous blocks (that is the nature of asynchronous-ness).
Upvotes: 3
Reputation: 7746
Your case isn't really what a loop is designed for. It's possible that the loop may finish before the closures within the loop are executed.
Instead, try a recursive function with a completion block:
func databaseHasAName(names: [String], index: Int, completion: (String?) -> ()) {
guard index < names.count else{
completion(nil)
return
}
let name = names[index]
TestName(name) { response in
if response {
completion(name)
} else {
databaseHasName(names, index: index + 1, completion: completion)
}
}
}
This insures that only one call is happening at a time, regardless of the synchronicity of the response block
Upvotes: 1
Reputation: 89192
Add the @noescape
attribute to TestName's closure parameter to indicate that the closure does not escape the call.
Use a variable outside the TestName call, set to false, and set it to true inside the loop if you want to stop.
After the TestName call, check the variable to see if you need to break.
or
Upvotes: 0