Search code examples
swiftasynchronousdispatch

Create several DispatchQueue.main.asyncAfter methods at once in Swift 3


I'm trying to have locations plotted on a map, one after another with 1 second in between each plotted location, but it's not quite working. Here's what I have so far:

@IBAction func playButtonPressed(_ sender: Any) {
    var index = 0.0
    var i = 0
    var j = 0
    while i < sites.count {
        while j < sites[i].count {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0*index) {
                self.plot(day:i,site:j)
            }
            j += 1
            index += 1
        }
        j = 0
        i += 1
    }
}

func plot(day:Int,site:Int) {
    for letter in glossary {
        let siteToPlot = sites[day][site]
        if let location = letter[siteToPlot] {
            setUpMap(name: location.name!, lat: location.lat!, long: location.long!)
        }
    }
}

The way I have it set up, each item in the for loop will have a method called in the DispatchQueue.main.asyncAfter, but each item will be dispatched one second later. First will be 0 seconds later, then 1, then 2, etc.

It doesn't seem that the plot function arguments are saved however, as the first time the plot function is called, the day and site values are 9 and 0, which are their values when the loop is finished.


Solution

  • You can fix this by creating local variables:

    @IBAction func playButtonPressed(_ sender: Any) {
        var index = 0.0
        var i = 0
        var j = 0
        while i < sites.count {
            while j < sites[i].count {
                let day = i
                let site = j
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0 * index) {
                    self.plot(day: day, site: site)
                }
                j += 1
                index += 1
            }
            j = 0
            i += 1
        }
    }
    

    Or, as pointed out by Martin R in Pass value to closure?, you can "capture" these variables:

    @IBAction func playButtonPressed(_ sender: Any) {
        var index = 0.0
        var i = 0
        var j = 0
        while i < sites.count {
            while j < sites[i].count {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0 * index) { [i, j] in
                    self.plot(day: i, site: j)
                }
                j += 1
                index += 1
            }
            j = 0
            i += 1
        }
    }
    

    Or, personally, I'd probably use for loops to clean this up a bit:

    @IBAction func playButtonPressed(_ sender: Any) {
        var delay = 0.0
        for i in 0 ..< sites.count {
            for j in 0 ..< sites[i].count {
                DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [i, j] in
                    self.plot(day: i, site: j)
                }
                delay += 1
            }
        }
    }