我正在尝试链接两个dispatchGroup,但是我的代码总是在随机位置崩溃。
我的功能是这样的:
func getAllActivities(userUID: String, _ completionHandler: @escaping (_ activities: [ActivitiesForUIStruct]?, _ error: Error?) -> Void) {
let downloadGroup = DispatchGroup()
let detailGroup = DispatchGroup()
let _ = DispatchQueue.global(qos: .userInitiated)
var activities = [ActivitiesForUIStruct]()
getActivities(userUID: userUID) { (results, error) in
//Handle error
downloadGroup.enter()
if let results = results {
var groupTitle = ""
var originatorName = ""
var sourceName = ""
results.forEach({ (activity) in
let originatorUID = activity.originatorUserUID ?? ""
let groupID = activity.inspectionGroupID ?? ""
let sourceID = activity.sourceID ?? ""
detailGroup.enter()
self.getTitle(key: "inspectionGroups", value: groupID, { (title, error) in
...
if let title = title {
groupTitle = title
detailGroup.leave()
}
})
detailGroup.enter()
self.getName(userUID: originatorUID, { (name, error) in
...
if let name = name {
originatorName = name
detailGroup.leave()
}
})
detailGroup.enter()
self.getTitle(key: "users", value: sourceID, { (title, error) in
...
if let title = title {
sourceName = title
detailGroup.leave()
}
})
detailGroup.notify(queue: .global(qos: .userInitiated), execute: {
let activityUI = ActivitiesForUIStruct(activities: activity, originatorName: originatorName, sourceName: sourceName, inspectionGroupTitle: groupTitle)
activities.append(activityUI) //crash here
downloadGroup.leave() //crash here
})
})
}
downloadGroup.notify(queue: .main, execute: {
completionHandler(activities, nil)
})
}
}
我的代码的目的是从UID中获取名称和标题,然后将其附加到自定义的struct
上。但是,我的实现倾向于在detailGroup.notify
中标记的区域崩溃。有人会建议我哪里出问题了吗?
注意:我的函数调用正确,即,我能够打印出originatorName
,sourceName
和groupTitle
。
答案 0 :(得分:0)
您需要平衡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
可以在多线程中进行更新,则可能会导致意外行为,包括崩溃。更好地在UI线程中更新这样的数组。
考虑了所有这些问题,您的代码看起来像这样:
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)
})
}
}
在某些情况下,可能不需要我的一些建议,但是在使用DispatchGroup或异步任务唤醒时,最好记住所有3项(由Code Different建议的+1)。