迅捷:无法链接DispatchGroup

时间:2018-09-02 14:18:03

标签: swift

我正在尝试链接两个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中标记的区域崩溃。有人会建议我哪里出问题了吗?

注意:我的函数调用正确,即,我能够打印出originatorNamesourceNamegroupTitle

1 个答案:

答案 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)。