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")
        })
}
6
  • 1
    When looking through the answers in the duplicate, the answer by mooney is best. Commented Dec 21, 2017 at 15:26
  • @rmaddy Thanks for replying. The comment on his answer looks skeptical about his solution. Can you confirm his answer Commented Dec 21, 2017 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. Commented Dec 21, 2017 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? Commented Dec 21, 2017 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. Commented Dec 21, 2017 at 16:36

1 Answer 1

-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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.