swiftswift3nstimernsrunlooprunloop

Swift 3, make timer, add to dictionary, never goes off


I have Thing and ThingManager

If no one mentions a specific Thing for a while, I want ThingManager to forget about it.

let manager = ThingManager()
let thing1 = Thing(name: "thing1")
manager.addThing(thing1)
manager.sawSomeThing(named: thing1.name)
print("Manager has this many things: ", manager.things.count)
Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { (timer) in
    // By now, the manager should have forgotten about the thing
    print("Manager has this many things: ", manager.things.count)
})

I have tried both block-based timers and RunLoop-based timers. They don't ever seem to "go off"

struct Thing {
    var name: String
}

class ThingManager {
    var things: [String: Thing] = [:]
    fileprivate var thingWatchingRunLoop = RunLoop()
    fileprivate var thingWatchingQueue = DispatchQueue.global(qos: .utility)
    fileprivate var thingWatchingTimers: [String: Timer] = [:]

    func addThing(_ thing: Thing) {
        self.things[thing.name] = thing
    }

    func sawSomeThing(named name: String) {
        self.thingWatchingQueue.async {
            // re-up the timer so we don't forget about that thing
            if let timer = self.thingWatchingTimers[name] {
                timer.invalidate()
            }
            let timer = Timer(timeInterval: 5.0, target: self, selector: #selector(self.timerWentOff(_:)), userInfo: ["name":name], repeats: false)
            self.thingWatchingRunLoop.add(timer, forMode: .commonModes)
            self.thingWatchingTimers[name] = timer
        }
    }

    @objc func timerWentOff(_ timer: Timer) {
        let info = timer.userInfo as! [String: String]
        let name = info["name"]
        self.removeThing(named: name!)
    }

    func removeThing(named name: String) {
        self.things.removeValue(forKey: name)
    }
}

Update, block-based version: https://gist.github.com/lacyrhoades/f917b971e97fdecf9607669501ef6512


Solution

  • I believe you just need to add the timer to the current runloop instead of creating a new Runloop instance.

    Change:

    fileprivate var thingWatchingRunLoop = RunLoop()
    

    to:

    fileprivate var thingWatchingRunLoop = RunLoop.current
    

    and everything should be working properly!