2016-11-09 4 views
0

Je Thing et ThingManagerSwift 3, faire minuterie, ajouter au dictionnaire, ne sort jamais de

Si personne ne parle pendant un certain temps un Thing spécifique, je veux ThingManager l'oublier. J'ai essayé les minuteries par bloc et les minuteries basées sur RunLoop. Ils ne semblent jamais à « partir »

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) 
    } 
} 

mise à jour, la version à base de blocs: https://gist.github.com/lacyrhoades/f917b971e97fdecf9607669501ef6512

Répondre

1

Je crois que vous avez juste besoin d'ajouter la minuterie à la runloop actuelle au lieu de créer une nouvelle instance runloop .

Change:

fileprivate var thingWatchingRunLoop = RunLoop() 

à:

fileprivate var thingWatchingRunLoop = RunLoop.current 

et tout devrait fonctionner correctement!

+0

Merci! '.current' fonctionne bien dans l'instanciation, mais pas si vous le demandez à la demande, dans la file d'attente de répartition. '.main' semble toujours fonctionner indépendamment du contexte, donc je suis allé avec ça. Je ne suis pas sûr que je goupille complètement la relation des files d'attente pour exécuter des boucles ou vice versa. – snakeoil

+0

Mise à jour: Il n'y a peut-être pas besoin d'une file d'attente de répartition. J'étais inquiet de voir les choses se déranger mais eh. Docs: "La classe NSRunLoop n'est généralement pas considérée comme thread-safe et ses méthodes ne devraient être appelées que dans le contexte du thread courant.Vous ne devriez jamais essayer d'appeler les méthodes d'un NSRunLoop ..." – snakeoil

+0

Très vrai Je n'ai pas remarqué ça avant. Avec l'exemple que vous avez donné ci-dessus ne devrait pas être un problème. Vous ne faites référence à la chose que par la clé "name" passée à travers les informations de l'utilisateur. La file d'attente d'expédition semble juste comme la complexité inutile. –

1

plutôt qu'une minuterie par Thing j'attribuer un intervalle de caducité à chaque Thing dans ThingManager et une seule minuterie:

struct Thing { 
    var name: String 
} 

extension Thing: Equatable { 
    static func ==(lhs: Thing, rhs: Thing) -> Bool { 
     return lhs.name == rhs.name 
    } 
} 

extension Thing: Hashable { 
    var hashValue: Int { 
     return name.hashValue 
    } 
} 

class ThingManager { 
    let stalenessInterval: TimeInterval = -5 
    var things = [String: Thing]() 
    var staleness = [Thing: Date]() 
    fileprivate var pruningTimer: DispatchSourceTimer! 

    init() { 
     pruningTimer = DispatchSource.makeTimerSource(queue: .main) 
     pruningTimer.scheduleRepeating(deadline: DispatchTime.now(), interval: DispatchTimeInterval.milliseconds(500)) 
     pruningTimer.setEventHandler() { 
      for (name, thing) in self.things { 
       if let date = self.staleness[thing], date.timeIntervalSinceNow < self.stalenessInterval { 
        self.removeThing(named: name) 
       } 
      } 
     } 
     pruningTimer.resume() 
    } 

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

    func sawSomeThing(named name: String) { 
     if let thing = things[name] { 
      staleness[thing] = Date() 
     } 
    } 

    func removeThing(named name: String) { 
     if let removedThing = things.removeValue(forKey: name) { 
      staleness.removeValue(forKey: removedThing) 
     } 
    } 
} 
+0

J'aime l'idée d'une minuterie. Je vais l'utiliser. - En ce qui concerne 'resume()' cependant, cela ne semble pas s'appliquer à un ancien timer "planifié" (issu d'une méthode de classe comme 'Timer.scheduledTimer ...') ou un timer que j'ajoute manuellement à la boucle d'exécution principale/courante à la main, via 'runLoop.add()' Qu'est-ce qui donne? – snakeoil

+0

Vous avez raison, 'resume()' est pour les sources de dispatch. J'ai enlevé cette partie de la réponse. Ajouter un 'Timer' à une boucle d'exécution a fonctionné dans mes tests; avez-vous essayé la boucle d'exécution principale au lieu d'une boucle personnalisée? – sbooth

+0

Très intéressant! Je suis allé en utilisant la boucle 'RunLoop.main' comme une réponse rapide qui fonctionne, mais je veux convertir à votre réponse de minuterie plus tard aujourd'hui. La question était un peu longue mais je suis content que vous ayez apporté une réponse philosophique plus large. – snakeoil