Во-первых,
вам нужно сбалансировать enter()
и leave()
. Ваш код вызывает downloadGroup.enter()
только один раз из results.forEach
, но downloadGroup.leave()
вызывается внутри цикла несколько раз. Так, если results
имеет два или более элементов, downloadGroup.leave()
вызывается больше раз, чем downloadGroup.enter()
.
Во-вторых,
Некоторые, казалось бы, асинхронные методы могут вызывать обработчик завершения синхронно. (Например, при попадании в кеш.) Итак, некоторые detailGroup.leave()
могут быть вызваны до detailGroup.enter()
.
detailGroup.enter() +1
detailGroup.leave() 0 --> detailGroup.notify fires!
detailGroup.enter() +1
detailGroup.leave() 0 --> detailGroup.notify fires!
detailGroup.enter() +1
detailGroup.leave() 0 --> detailGroup.notify fires!
(detailGroup.notify
срабатывает каждый раз, когда внутренний счетчик получает 0
.)
Как показано выше, это может вызвать detailGroup.notify
возгорание несколько раз. Чтобы заставить detailGroup.notify
стрелять только один раз, когда все задачи завершены, вам нужно гарантировать все detailGroup.enter()
, выполненные до любого detailGroup.leave()
.
detailGroup.enter() +1
detailGroup.enter() +2
detailGroup.enter() +3
detailGroup.leave() +2
detailGroup.leave() +1
detailGroup.leave() 0 --> detailGroup.notify fires!
В-третьих,
Swift Array
не является поточно-ориентированным. Таким образом, если ваш activities
может быть обновлен в несколько потоков, это может привести к неожиданному поведению, включая сбой. Лучше обновить такой массив в потоке пользовательского интерфейса.
Учитывая все вышесказанное, ваш код будет выглядеть примерно так:
func getAllActivities(userUID: String, _ completionHandler: @escaping (_ activities: [ActivitiesForUIStruct]?, _ error: Error?) -> Void) {
let downloadGroup = DispatchGroup()
let detailGroup = DispatchGroup()
var activities = [ActivitiesForUIStruct]()
getActivities(userUID: userUID) { (results, error) in
//Handle error
if let results = results {
var groupTitle = ""
var originatorName = ""
var sourceName = ""
results.forEach({ (activity) in
//### 1. Balance enter() and leave()
downloadGroup.enter()
let originatorUID = activity.originatorUserUID ?? ""
let groupID = activity.inspectionGroupID ?? ""
let sourceID = activity.sourceID ?? ""
//### 2. Guarantee all enter() executed before leave()
detailGroup.enter() //for getTitle(key: "inspectionGroups"...
detailGroup.enter() //for getName(userUID: originatorUID...
detailGroup.enter() //for getTitle(key: "users"...
self.getTitle(key: "inspectionGroups", value: groupID, { (title, error) in
//...
if let title = title {
groupTitle = title
}
detailGroup.leave()
})
self.getName(userUID: originatorUID, { (name, error) in
//...
if let name = name {
originatorName = name
}
detailGroup.leave()
})
self.getTitle(key: "users", value: sourceID, { (title, error) in
//...
if let title = title {
sourceName = title
}
detailGroup.leave()
})
//### 3. Use main queue to safely modify thread-unsafe objects
detailGroup.notify(queue: .main, execute: {
let activityUI = ActivitiesForUIStruct(activities: activity, originatorName: originatorName, sourceName: sourceName, inspectionGroupTitle: groupTitle)
activities.append(activityUI)
downloadGroup.leave()
})
})
}
downloadGroup.notify(queue: .main, execute: {
completionHandler(activities, nil)
})
}
}
В некоторых условиях некоторые из моих предложений могут не понадобиться, но вам лучше помнить все 3 (+1 предложено Code Different) при работе с DispatchGroup или асинхронных задачах.