0

I'm developing an Photo editing application. I'm giving my users the option to import multiple assets to the edit View Controller. I'm creating a Dispatch Group for all the "PHAsset->Asset fetching" calls.
Each time the PHAsset fetch request completion block called, I need to append the fetched asset into an Mutable Array, and updating the UI (1/3 imported | 2/3 imported and so on) by the amount of the imported assets.
Because Swift Mutable Arrays aren't thread-safe, sometimes I'm getting a crash. I guess the completion handler called from multiple threads, causing some kind of a deadlock.

What would be the correct way to handle thread-safe read/write Array access with Concurrent Dispatch Group?

var assets = [Asset]()

func getAssetsFromPHAssets() {

    let dispatchGroup: DispatchGroup = DispatchGroup()

    for phAsset in phAssets {

        dispatchGroup.enter() // Enter group for each asset

        PHImageManager.default().requestPlayerItem(forVideo: phAsset, options: nil, resultHandler: { (item, info) in

            let asset = Asset(playerItem: item, position: 0)
            self.assets.append(asset)

            DispatchQueue.main.async {
                self.lblImport.text = "Importing \(self.assets.count)/3"
            }

            dispatchGroup.leave() // Leave group
        })
    }

    dispatchGroup.notify(queue: DispatchQueue.main, execute: {
            print("finished importing")
        })
}
Roi Mulia
  • 5,626
  • 11
  • 54
  • 105
  • 1
    When looking through the answers in the duplicate, the [answer by mooney](https://stackoverflow.com/a/40045439/1226963) is best. – rmaddy Dec 21 '17 at 15:26
  • @rmaddy Thanks for replying. The comment on his answer looks skeptical about his solution. Can you confirm his answer – Roi Mulia Dec 21 '17 at 15:40
  • I didn't actually test it but the approach is what you need. Create a concurrent queue. Use regular sync/async to read from your array and use a barrier sync/async to write to the array. This allows multiple concurrent readers but ensures a write is done on its own and blocks and concurrent reads or writes. – rmaddy Dec 21 '17 at 15:42
  • @rmaddy Thanks, I've implemented the answer you have linked. Now I need to think about how to test it lol. One small question, does the dispatchGroup.leave(), should be at the current location it is, or should I wrap it inside async/sync call as well? – Roi Mulia Dec 21 '17 at 16:33
  • 1
    Your calls to `leave` are fine. There's no need to move the call to `leave` inside the same block that updates the label. – rmaddy Dec 21 '17 at 16:36
  • @rmaddy Thanks rmaddym . You helped me so much. ! :) – Roi Mulia Dec 21 '17 at 16:52

1 Answers1

-1

I would suggest adding the new Asset in the main thread:

let asset = Asset(playerItem: item, position: 0)

DispatchQueue.main.async {
    self.assets.append(asset)
    self.lblImport.text = "Importing \(self.assets.count)/3"
}

Maybe - but this depends on what you are doing in the notify closure - you should also move the dispatchGroup.leave() call into the main queue.

Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34