So I have a paginated API that I have to call every time a user enters a screen. The below code works fine but has one issue if say API calls are in progress and the user backs down and comes again then rather both execution starts simultaneously. and has to wait for both executions to be completed. So, What I want is to stop ongoing API calls and start new ones when requested new one If this setup is wrong in any way please notify me as well what am I doing wrong?
func storeAllData(matchID: Int64) {
GlobalVariable.isFetchingData = true
dbManager = DbManager.shared
if let lastScore = dbManager.getLastBallForCurrentMatch(matchID: matchID) {
self.fetchAllScore(scoreID: lastScore.id ?? 0, matchID: matchID)
} else {
self.fetchAllScore(scoreID: 0, matchID: matchID)
}
}
func fetchAllScore(scoreID: Int = 0, matchID: Int64) {
let id = matchID
backgroundQueue.async { [self] in
autoreleasepool {
fetchPaginatedData(matchID:Int(id), scoreID: scoreID, page: 1)
dispatchGroup.wait()
print("All API calls finished")
}
}
}
func fetchPaginatedData(matchID: Int,scoreID: Int = 0, page: Int) {
dispatchGroup.enter()
MatchesNetworkManager.sharedInstance.getAllScore(matchID: Int64(matchID), scoreID: Int64(scoreID), page: Int64(page), completionHandler: { [weak self] (allSocre, isSuccess, errorStr) in
if isSuccess {
if allSocre?.match?.scores != nil {
self?.oldScores.append(contentsOf: ((allSocre?.match?.scores)!))
self?.backgroundQueue.async {
if let scores = allSocre?.match?.scores as? [Scores] {
self?.dbManager.insertRecords(matchID: matchID, records: scores)
DispatchQueue.main.async {
self?.oldScores = []
if allSocre?.page_num ?? 0 == 1 {
self?.updateScoreView()
}
if (allSocre?.page_num ?? 0) < (allSocre?.total_pages ?? 0) {
self?.fetchPaginatedData(matchID: matchID,page: (allSocre?.page_num ?? 1) + 1)
} else {
// All pages have been fetched
GlobalVariable.isFetchingData = false
print("All API calls finished")
NotificationCenter.default.post(name: .refreshMarketView, object: nil, userInfo: nil)
self?.dispatchGroup.leave()
}
}
}
}
} else {
self?.updateScoreView()
GlobalVariable.isFetchingData = false
}
} else {
print("API call error: \(errorStr)")
self?.updateScoreView()
GlobalVariable.isFetchingData = false
self?.dispatchGroup.leave()
}
})
}
The reality of most concurrency abstractions on these (and similar) platforms is that, once started, work units are almost completely unable to be forcibly cancelled (other than by terminating the process.)
The way one would generally handle this would be to check a flag from within the work units themselves that indicates that they should be canceled, at which point they can do whatever cleanup is needed (if any), and return early. If you had a dispatch group, you could associate a flag with the group, and then explicitly check the flag at the beginning of each operation (or at the top of each iteration of a loop within the operation), and returning early if the group is cancelled.
You can do this ad hoc. You can use an existing abstraction (like
NSOperation'scancelmethod) but even there, you'll note that the first line of the Discussion section says: "This method does not force your operation code to stop." Your operation's code must still check the flag.For example, you could use
NSBlockOperation, create the operation instance, then capture a zeroing, weak reference to theNSBlockOperationinstance inside the closure of the block that does the real work, and then use that reference to check whether theNSBlockOperationhas been canceled (or deallocated).Long story short, there's no proverbial free lunch here.