2017-12-13 5 views
7

Ich erhalte zwei Arten von Informationen mit JSON und ich füge "operations" zu 2 verschiedenen Operations Queues-Klassen mit addObserver hinzu (forKeyPath: "operations" ...) . In der Funktion observeValue überprüfe ich ob operationQueue1.operations.isEmpty und dann aktualisiere ich meine Informationen in UI. Ich mache dasselbe mit if sonst mit operationQueue2, aber wenn die 2 Operationen gestartet werden, stürzt die Anwendung manchmal ab mit der Fehlermeldung: *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <AppName.ViewController 0x102977800> for the key path "operations" from <AppName.OperationQueue1 0x1c4a233c0> because it is not registered as an observer. ' Ich habe kein Problem, wenn nur 1 Operation gestartet wird. Irgendwelche Vorschläge?Swift - Anwendungsabsturz bei Verwendung zweier verschiedener OperationQueues mit KVO

func getInfo1(){//runned in viewDidLoad 
    operationQueue1.addObserver(forKeyPath:"operations"...) 
    operationQueue1.dataTask(URL:"..."....){ 
     DispatchQueue.main.async{ 
    NotificationCenter.default.postNotification(NSNotification.Name(rawValue: "NewDataReceived1", userInfo:infoFromTheWebsite) 
     } 
    } 
} 

func NewDataReceived1(){ 
    here I add the information to arrays to be loaded in tableView1 
} 

HERE IS THE CODE FOR 2ND INFO WHICH IS THE SAME 

override func observeValue(forKeyPath keyPath: String?, ....){ 
     if(object as? operationQueue1 == operationQueue1Class && keyPath == "operations" && context == context1){ 
      if(operationQueue1.operations.isEmpty){ 
        DispatchQueue.main.async{ 
         operationQueue1..removeObserver(self, forKeyPath:"operations") 
         Timer.scheduled("refreshingTableInformation1") 
        } 
      } 
     }else if(operationQueue2....){ 
      SAME AS OPERATION 1, BUT USING DIFFERENT FUNC TO REFRESH TABLE INFORMATION AND THE TABLES ARE DIFFERENT 
     }else{ 
      super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 
     } 
} 

func refreshingTableInformation1(){ 
    tableView1.reloadData() 
    Timer.scheduled("getInfo1", repeat:false) 
} 

func refreshingTableInformation2(){ 
    tableView2.reloadData() 
    Timer.scheduled("getInfo2", repeat:false) 
} 

Manchmal funktioniert es 10 Sekunden und Absturz und manchmal für mehr als 60 Sekunden arbeitet und dann abstürzen ...

+0

Wie der Fehler sagt, versuchen Sie, einen View-Controller vom Beobachten des Operationsschlüsselpfads Ihrer 'OperationQueue1' zu entfernen, obwohl der View-Controller nicht als Beobachter registriert ist. Dies könnte möglicherweise auftreten, wenn Sie den Verweis auf die VC geändert haben. Veröffentlichen Sie den Code Ihrer Operationswarteschlange und geben Sie den Beobachter an. –

+0

Ich füge den Beobachter vor den 2 Anfragen hinzu und sie sind in einem unendlichen Kreis eingeschlossen. Wenn ich die Informationen in der Benutzeroberfläche aktualisiere, setze ich den Beobachter erneut und ich starte die Anfrage, um die Information erneut zu bekommen. –

+0

Ihr Code ist nicht sehr klar. Wie auch immer, warum nicht stattdessen einen Beobachter auf das Array setzen, das 'NewDataReceived1' sammelt? wäre einfacher zu handhaben. Wenn Sie Synchronisierungsprobleme haben, ist es besser, für Ihre HTTP-Anfragen Bibliotheken wie Alamofire zu verwenden. Das würde dir eine Menge Ärger ersparen. – Alex

Antwort

4

Ihr Code hier verletzlich ist, Bedingungen zu fahren. Betrachten Sie das folgende Szenario:

  1. getInfo1() genannt wird, die eine Operation operationQueue1 hinzufügt.

  2. Der Vorgang wird abgeschlossen, was bedeutet, dass Ihre KVO-Beobachtung aufgerufen wird. Die Warteschlange ist jetzt leer, sodass Ihre Beobachtung die Entfernung Ihres Beobachters in der Hauptdispatch-Warteschlange plant.

  3. Nun, bevor der Betrieb in dem Haupt Warteschlange gesendet haben ist in der Lage zu laufen, ruft etwas anderes getInfo1(), die operationQueue1 eine neue Operation fügt hinzu, die vor der Operation abgeschlossen Sie in Schritt 2 die Warteschlange hat die Chance hatte, zu laufen (hey, vielleicht war die Hauptwarteschlange mit etwas beschäftigt; das ist einfach, da es sich um eine serielle Warteschlange handelt).

  4. Ihre Beobachtung für den ersten Aufruf von getInfo1() wird wieder genannt, während die Warteschlange leer ist, was andere Abmelde Block an der Hauptwarteschlange eingereicht werden.

  5. Die beiden Deregisterblöcke können schließlich in der Hauptwarteschlange ausgeführt werden. Der zweite stürzt das Programm ab, da Sie Ihren Beobachter bereits abgemeldet haben.

Sie könnten wahrscheinlich dieses Problem beheben (den Code unter der Annahme haben nicht mehr Probleme dieser Art), indem stattdessen Swift 4 des blockbasierten Beobachter verwenden und die Einstellung der Beobachter nil stattdessen explizit davon Deregistrierung. Jedoch schlage ich vor, dass KVO das wrong tool für das ist, was Sie versuchen zu tun. Wie die Anweisungen für das alte "Crystal Quest" -Spiel zu sagen pflegten, ist es ein bisschen so, als würde man eine Mücke mit einer Flak töten.

Aus dem obigen Code geht hervor, dass Sie KVO verwenden, um eine Benachrichtigung zu planen, wenn ein Vorgang oder eine Gruppe von Operationen, die Sie an die Warteschlange senden, abgeschlossen ist. Je nachdem, was Ihre dataTask Methode tatsächlich der Fall ist, ist hier, was ich stattdessen tun würde:

  • Wenn Sie nur eine Operation einreichen: legen Sie die completionBlock Eigenschaft Betrieb zu einem Verschluss kann das Tabelleninformationen aktualisiert.

  • Wenn Sie mehr als eine Operation senden: Erstellen Sie eine neue BlockOperation, die Ihre Tabelleninformationen aktualisiert, und rufen Sie bei jeder Operation, die Sie an die Warteschlange senden, addDependency auf. Dann senden Sie diesen Vorgang ein.

Dies wird Ihnen eine viel sauberere und problemlosere Möglichkeit zur Überwachung der Fertigstellung Ihrer Aufgaben bieten. Und da Sie die Warteschlange nicht mehr komplett leeren müssen, müssen Sie möglicherweise nicht mehr zwei separate Warteschlangen verwenden, je nachdem, was Sie sonst noch tun.

+0

Ich benutze KVO, weil ich dynamische Tabellenansichten habe und Wenn die Website mir keine Informationen zurückgibt, erzeuge ich keine Tabellenansicht –

+0

@BogdanBogdanov Können Sie bitte genauer angeben, was genau Sie tun? Ich verstehe nicht, warum die Verzögerung der Erstellung einer Tabellenansicht die Verwendung von KVO in der Operationswarteschlange erfordert, anstatt sie nur in einer Komplettierungsoperation auszuführen. –

+0

Ich stimme mit @CharlesSrstka überein, mein erster Gedanke war eine Race Condition und besonders Nummer 4 auf seiner obigen Liste. Mit den neueren 'tableView.beginUpdates()' und '.endUpdates()' oder 'performBatchUpdates (_: completion:)' ist es möglich, die tableView vom changeHandler eines Blockbeobachters zu aktualisieren. Beachten Sie, dass 'observe (_: options: changeHandler:)' bei Bedarf 'addObserver_: forKeyPath: options: context:)' und 'removeObserver (_: forKeyPath: context:)' aufruft. – RLoniello

Verwandte Themen