2016-11-08 8 views
0

I eine „Metalldetektor“ Art von APP aufgebaut habe, welche die „calculateDistance“ -Funktion alle 2 Sekunden ausgeführt wird, die den Abstand in Metern zwischen dem Benutzerstandort und einem Satz Marker berechnet, setzt, das zu var globalDistanz. Abhängig davon, ob diese Entfernung aus Gründen der Einfachheit> = 10 Meter oder < 10 Meter ist, spiele ich einen geplanten Timer, der die "Audioplayer" -Funktion aufruft, die alle 2 Sekunden einen Signalton abgibt (wenn Entfernung> = 10m) oder alle 0,5 Sekunden (wenn Abstand < 10m).geschleift Timer nicht ungültig, wie angewiesen

Problem ist, die Zeitgeber nie ungültig machen, wie ich sie zu unterweisen. Wenn ich also mit meinem Gerät von < 10m auf> 10m gehe, wird das 0,5sec-Piepen fortgesetzt. Ich mache audioTimer.invalidate() um den Timer von der vorherigen Iteration zu stoppen.

Jede Idee, was ich mit meinem Code falsch mache? Vielen Dank

func calculateDistance { 
    //here there is code that successfully calculates distance, every 2 seconds 

    var timerSeconds = 0.0 
    var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true) 

    if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad 
     timerSeconds = 2 
    } 

    if globalDistance >= 0 && globalDistance <= 10 { 
     timerSeconds = 0.5 
    } 

    audioTimer.invalidate() 

    audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true) 
    audioTimer.fire() 
} 

func audioPlayer(){ 
    AudioServicesPlaySystemSound(1104) 
} 

Antwort

2

Die Grundidee ist, um sicherzustellen, dass es keine Codepfad, durch den ein Timer ohne Unterbrechung jede vorherige eines gestartet wird. Ihr aktueller Code verfügt über einige Pfade, bei denen ein vorhandener Timer vor dem Start des nächsten nicht ungültig gemacht wird.

Außerdem würde ich vorschlagen, dass Sie nur die alte Timer ungültig machen, wenn die neue Beep-Frequenz von der alten Beep-Frequenz abweicht. (Warum ungültig machen die 2 Sekunden Wiederholung Piepen und anderen Timer gestartet werden, wenn der alte 2 Sekunden-Timer wird die Arbeit in Ordnung tun?)

Also, das bedeutet, dass Sie:

  • ziehen sowohl die Timer und die TimeInterval Variablen außerhalb der Funktion;
  • Führen Sie den Prozess "new timer" nur aus, wenn sich das Beep-Intervall geändert hat. und
  • stellen Sie sicher, immer den alten Timer ungültig zu machen, bevor Sie einen neuen erstellen.

Zum Beispiel:

private var audioTimer: Timer? 
private var beepInterval: TimeInterval? 

private func updateBeepIntervalIfNeeded() { 
    // here there is code that successfully calculates distance, called with whatever frequency you want 

    let newBeepInterval: TimeInterval 

    if globalDistance > 10 { 
     newBeepInterval = 2 
    } else if globalDistance >= 0 { 
     newBeepInterval = 0.5 
    } else { 
     fatalError("less than 0?!") // I'm inferring from your code that this cannot happen, but by using `let` above, Swift warned me that we had a path of execution we hadn't previously considered 
    } 

    if beepInterval != newBeepInterval { 
     beepInterval = newBeepInterval 
     audioTimer?.invalidate() 
     audioTimer = Timer.scheduledTimer(timeInterval: beepInterval!, target: self, selector: #selector(beep(_:)), userInfo: nil, repeats: true) 
     audioTimer!.fire() 
    } 
} 

@objc func beep(_ timer: Timer) { 
    // perform beep here 
} 
+0

Sie können einen Schalter verwenden, um Bereiche zu überprüfen :) – Alexander

+0

Ja, ich Ich nehme das in Betracht, aber mit zwei offenen Bereichen ('> 10' und' <0') fügst du dann mindestens eine 'where'-Klausel hinzu, an der ich denke, dass eine einfache' if'-'else' ist viel intuitiver, IMHO. Es ist natürlich eine Frage der persönlichen Meinung ... – Rob

+0

Wahr. Ich habe einen Präfix/Postfix Bereich Operator für halb offene Bereiche definiert, p – Alexander

0

Youre ein neues, unendlich wiederholt, Timer einmal zu schaffen, ungültig macht es sofort (warum?), Und erstellen Sie dann eine andere (warum?), Die für immer ausgelaufen ist.

+0

Danke für die Beantwortung - ich annulliere es, denn wenn ich nicht, dann wird es für immer von der vorherigen Schleife (die entweder 2s oder 0,5s Intervalle sein könnte) laufen. Ich erstelle die neue, coz alle 2 Sekunden, dass ich die ganze findDistance Funktion aufrufen (von einem anderen Timer in ViewDidLoad), ich möchte überprüfen, ob die Entfernung> 10m oder <10m (vom physikalischen Standort des Geräts), Intervall Sekunden auf 2s oder 0.5s, und spielen Sie Sound mit seinem jeweiligen Intervall. Dann alle 2 Sekunden wird das Ganze wieder zurückgesetzt. Es ruft den richtigen Timer das erste Mal, aber wenn ich durch 10m bewegen, überlappen die Timer. Thx – Apneist

+0

Was Ihre Invalidierung betrifft den Timer, den Sie gerade erstellt haben. Der zweite Timer, den du erschaffst, wird niemals ungültig gemacht (es ist durchgesickert) und es läuft ewig – Alexander

0

Sie einen neuen Timer erstellen, ungültig zu machen dann einen Timer wieder zu schaffen.

Sie könnten versuchen, den Timer zu erstellen und beim Aufruf der audioPlayer-Funktion nach dem zu spielenden Sound zu suchen, abhängig vom Wert der timerSeconds-Variable.

+0

Danke - ich dachte daran und das sollte funktionieren, aber versuchen zu verstehen, warum mein Code nicht funktioniert und wie mein Sound-Effekt erreicht werden könnte Indem Sie den gleichen 1 Ton verwenden, wiederholen Sie ihn je nach Entfernung schneller oder langsamer. Grund ist, dass ich nicht 2, sondern 6 verschiedene "Piep" -Frequenzen hinzufügen möchte, je nachdem, wie nah der Benutzer sein soll (also wäre das Erstellen der Soundeffekte viel weniger zeiteffizient). – Apneist

+0

Je nachdem, wie Sie den Sound spielen, könnten Sie etwas verwenden Wie AVPlayer Rate, um eine Wiedergaberate mit demselben Sound zu definieren. [https://developer.apple.com/reference/avfoundation/avplayer/1388846-rate](https://developer.apple.com/reference/avfoundation/avplayer/1388846-rate) – Pugin

+0

Vielen Dank, ich tat Ich weiß nichts darüber, werde jetzt nachsehen.Ich habe es auch mit AVplayer versucht, aber ich habe einen Systemsound für das hier abgebildete Beispiel zur besseren Veranschaulichung (hat exakt dasselbe Ergebnis in Bezug auf die Häufigkeit der Ausgabe mit meinem Code) – Apneist

1

Das Problem

Hier gibt es auf der Hand einige Probleme gibt.

Zum einen möchte ich Betonung wie der Unterschied zwischen Referenzen und Instanzen. Wenn Sie einen Initialisierer aufrufen, ordnet das System einen Speicher für ein neues Objekt zu und gibt Ihnen eine Referenz an diesen Speicher, die in der Variablen gespeichert wird, der Sie ihn zuweisen. Sie können diese Referenz anderen Variablen zuweisen, die Kopien der Referenz erstellen. Jede dieser Variablen verweist auf dasselbe Originalobjekt. Dieses Objekt wird weiterhin im Speicher existieren, bis keine Variablen mehr darauf verweisen.

In Ihrem Fall rufen Sie nicht direkt einen Initialisierer auf, sondern Sie rufen eine statische Methode auf, die einem ähnlichen Zweck dient. Ein neues Objekt wird in Ihrem Auftrag zugewiesen, und Sie erhalten eine Referenz, die Sie dann audioTimer zuweisen. Es gibt jedoch einen Haken. Wenn Sie Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:) aufrufen, wird der neu erstellte Zeitgeber für Sie in der aktuellen Ausführungsschleife geplant. Die Run-Schleife sorgt dafür, dass der Timer zum richtigen Zeitpunkt ausgelöst wird. Die Konsequenz daraus ist, dass jetzt der Runloop auf Ihren Timer verweist und verhindert, dass das Timer-Objekt zerstört wird. Sofern Sie Ihren Timer nicht für ungültig erklären, um ihn aus seiner Runloop-Liste zu entfernen, wird der Timer weiterhin bestehen bleiben und für immer feuern, selbst wenn Sie Ihre Einstellung dafür löschen.

Lassen Sie uns jetzt auf den Code werfen Sie einen Blick, mit einer Erklärung darüber, was los ist:

func calculateDistance { 
    //here there is code that successfully calculates distance, every 2 seconds 

    var timerSeconds = 0.0 

    // 1) Initialize timer #1 
    var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true) 

    if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad 
     timerSeconds = 2 
    } 

    if globalDistance >= 0 && globalDistance <= 10 { 
     timerSeconds = 0.5 
    } 

    // 2) Invalidate timer #1 (timer #1 is useless) 
    audioTimer.invalidate() 

    // 3) Initialize timer #1 
    audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true) 

    // 4) Fire timer #2 immediately 
    audioTimer.fire() 


} // At the end of this method body: 
// - Timer #2 was never invalidated 
// - audioTimer no longer references Timer #2, but: 
// - Timer #2's runloop still references it, keeping it alive 
// - Timer #2 is leaked 
// ... and will continue firing forever. 

func audioPlayer(){ 
    AudioServicesPlaySystemSound(1104) 
} 

Wir können sehen, dass ein Timer ein in Abschnitt gemacht wird, was in timerSeconds Sekunden abfeuern sollte, 0 In Abschnitt 2 wird dieser Timer ungültig gemacht. Auch wenn der Timer in 0 Sekunden ausgelöst wurde, ist es fast sicher, dass seine Laufschleife noch keine Chance hatte, ihn auszulösen. Daher wird diese Zeit erstellt, nie ausgelöst und dann ungültig gemacht. Es gibt keinen Grund dafür, dass es dort überhaupt existiert.

Dann wird in Abschnitt 3 Timer # 2 erstellt und geplant. es wird manuell in Abschnitt 4 abgefeuert, und dann ist es permanent undicht.

Die Lösung

Sie benötigen eine Instanz-Variable, die Bezug auf die Timer hält. Ohne dies haben Sie keine Möglichkeit, den Timer, der bereits geplant wurde, ungültig zu machen.

Zweitens müssen Sie den Timer zum richtigen Zeitpunkt ungültig machen.

Ich schlage vor, Sie betrachten Robs Antwort für ein Beispiel.

+0

extrem nützliche Erklärung und Lösung, danke Sir – Apneist

+0

Haben ich Ihre Frage beantwortet? – Alexander

+0

Ja hast du - wirklich schätzen deine Zeit, die mir dabei hilft - es funktionierte jetzt nach deinen und Robs Vorschlägen :) – Apneist