2017-08-28 4 views
-1

Ich habe einen TabBar-Controller mit 2 Tabs: TabA, die ClassA und TabB enthält, die ClassB enthält. Ich sende Daten an die Firebase-Datenbank in TabA/ClassA und beobachte die Datenbank in TabB/ClassB, wo ich es abrufe und es zu einem TableView hinzufüge. In der Zelle der Tabelle zeige ich die Anzahl der Sneaker, die sich momentan in der Datenbank befinden.Swift iOS - Wie neu TableView außerhalb von Firebase Observer .childAdded, um doppelte Werte zu filtern?

Ich kenne den Unterschied zwischen .observeSingleEvent(.value) vs .observe(.childAdded). Ich brauche Live-Updates, denn während die Daten in TabA gesendet werden, wenn ich zu TabB wechseln möchte, möchte ich sehen, dass die neuen Daten zum TableView hinzugefügt werden, sobald tabA/ClassA fertig ist.

In ClassB habe ich meinen Beobachter in ViewWillAppear. Ich stelle es in eine pullDataFromFirebase() Funktion und jedes Mal, wenn die Ansicht erscheint, läuft die Funktion. Ich habe auch Notification-Beobachter, der auf die Daten wartet, die in tabA/ClassA gesendet werden, damit es die TableView aktualisiert. Das Benachrichtigungsereignis wird wieder pullDataFromFirebase()

In ClassA innerhalb des Rückrufs des Aufrufs zu Firebase habe ich den Benachrichtigungsposten, um die pullDataFromFirebase()-Funktion in ClassB auszuführen.

Das Problem, das ich bin, ist, wenn ich in TabB bin, während die neuen Daten aktualisiert werden, wenn es abgeschlossen ist, hat die Zelle, die die Daten anzeigt, eine Zählung und die Anzahl wird abgeworfen. Ich debuggte es und das SneakerModels-Array, das die Daten enthält, dupliziert und verdreifacht manchmal die neu hinzugefügten Daten.

Zum Beispiel, wenn ich in der Klasse B bin, und es gibt zwei Paar Turnschuhe in der Datenbank, die pullDataFromFirebase() func läuft, und die Tableview Zelle wird „Sie haben 2 Paar Turnschuhe“

Was geschah Wenn ich zu TabA/ClassA wechselte, dann fügte ich 1 Paar Sneakers hinzu, während es aktualisiert wurde, wechselte ich zu TabB/ClassB, die Zelle würde immer noch sagen "Du hast 2 Paar Turnschuhe", aber sobald es aktualisiert wurde, würde es sagen: "Du habe 5 Paar Turnschuhe "und 5 Zellen würden erscheinen? Wenn ich Tabs änderte und zurückkam, würde es korrekt "Sie haben 3 Paar Turnschuhe" und die korrekte Menge an Zellen zeigen.

Das ist, wo die Benachrichtigung kam. Sobald ich fügte hinzu, wenn ich durch den gleichen Prozess ging und mit 2 Turnschuhen begann die Zelle würde sagen: "Sie haben 2 Paar Turnschuhe", gehe ich TabA, ein weiteres Paar, wechseln zurück zu TabB und sehe immer noch "Du hast 2 Paar Sneaker". Sobald die Daten gesendet wurden, würde die Zelle kurz "Du hast 5 Paar Turnschuhe" zeigen und 5 Zellen zeigen, dann würde sie korrekt auf "Du hast 3 Paar Turnschuhe" und die korrekte Anzahl an Zellen aktualisieren (ich musste es nicht tun) Registerkarten wechseln).

Die Benachrichtigung schien zu funktionieren, aber es gab diesen kurzen inkorrekten Moment.

Ich habe einige Nachforschungen gemacht und das meiste, was ich finden konnte, waren einige Beiträge, die sagten, dass ich ein Semaphor verwenden muss, aber anscheinend von mehreren Personen, die unten Kommentare hinterlassen haben, sagten, dass Semaphore nicht asynchron verwendet werden sollen. Ich musste meine Frage aktualisieren, um die Semaphor-Referenz auszuschließen.

Momentan führe ich tableView.ReloadData() im Completion-Handler von pullDataFromFirebase().

Wie lade ich die TabelleView außerhalb des Beobachters neu, wenn es fertig ist, um die doppelten Werte zu verhindern?

Modell:

class SneakerModel{ 
    var sneakerName:String? 
} 

Tabb/ClassB:

ClassB: UIViewController, UITableViewDataSource, UITableViewDelegate{ 

var sneakerModels[SneakerModel] 

override func viewDidLoad() { 
    super.viewDidLoad() 

    NotificationCenter.default.addObserver(self, selector: #selector(pullDataFromFirebase), name: NSNotification.Name(rawValue: "pullFirebaseData"), object: nil) 
} 

override func viewWillAppear(_ animated: Bool){ 
    super.viewWillAppear(animated) 

    pullDataFromFirebase() 
} 

func pullDataFromFirebase(){ 

    sneakerRef?.observe(.childAdded, with: { 
     (snapshot) in 

     if let dict = snapshot.value as? [String:Any]{ 
      let sneakerName = dict["sneakerName"] as? String 

      let sneakerModel = SneakerModel() 
      sneakerModel.sneakerName = sneakerName 

      self.sneakerModels.append(sneakerModel) 

      //firebase runs on main queue 
      self.tableView.reloadData() 
     } 
    }) 
} 

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    return sneakerModels.count 
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCell(withIdentifier: "SneakerCell", for: indexPath) as! SneakerCell 

    let name = sneakerModels[indePath.row] 
    //I do something else with the sneakerName and how pairs of each I have 

    cell.sneakerCount = "You have \(sneakerModels.count) pairs of sneakers" 

    return cell 
} 

} 
} 

taba/KlasseA:

ClassA : UIViewController{ 

@IBAction fileprivate func postTapped(_ sender: UIButton) { 

    dict = [String:Any]() 
    dict.updateValue("Adidas", forKey: "sneakerName") 

    sneakerRef.?.updateChildValues(dict, withCompletionBlock: { 
       (error, ref) in 

     //1. show alert everything was successful 

     //2. post notification to ClassB to update tableView 
     NotificationCenter.default.post(name: Notification.Name(rawValue: "pullFirebaseData"), object: nil) 
    } 
} 
} 
+0

Verwenden Sie Semaphore grundsätzlich nicht, um asynchrone Datenverarbeitung zu umgehen. Wäre es eine Option, die Benachrichtigung zu posten und den asynchronen Code ('updateChildValues') im Empfänger der Benachrichtigung auszuführen? Oder übergeben Sie das hinzugefügte Objekt in der Benachrichtigung und aktualisieren Sie das Datenquellenarray, ohne die Daten * neu zu ziehen. – vadian

+0

@vadian danke für die Hilfe. Mache ich das nicht? Der Post wird in ClassA gesendet und der Empfänger ist ClassB. Die updateChildValues ​​wird in der pullFirebaseData-Funktion in ClassB ausgeführt. Warum nicht Semaphoren für asynchrone Prozesse verwenden? –

+0

Es gibt einen richtigen Weg, asynchron Dinge zu tun. Semaphore sind es nicht. – matt

Antwort

0

In anderen Teilen meiner app verwende ich eine filterDuplicates Methode, die ich hinzugefügt, wie ein extension zu einem Array, um doppelte Elemente herauszufiltern. Ich habe es von filter array duplicates:

extension Array { 

    func filterDuplicates(_ includeElement: @escaping (_ lhs:Element, _ rhs:Element) -> Bool) -> [Element]{ 
     var results = [Element]() 

     forEach { (element) in 

      let existingElements = results.filter { 
       return includeElement(element, $0) 
      } 

      if existingElements.count == 0 { 
       results.append(element) 
      } 
     } 

     return results 
    } 
} 

ich nichts besonders auf SO zu meiner Situation finden konnte, so habe ich die filterDuplicates Methode, die sehr praktisch war.

In meinem ursprünglichen Code habe ich eine Datumseigenschaft, die ich der Frage hinzugefügt haben sollte. Jede Art und Weise ich das Hinzufügen bin hier und das Datum Eigentum ist das, was ich in der filterDuplicates Methode verwenden muß, um mein Problem zu lösen:

Modell:

class SneakerModel{ 
    var sneakerName:String? 
    var dateInSecs: NSNumber? 
} 

Innen taba/KlasseA gibt es keine Notwendigkeit zu verwenden, Die Benachrichtigung innerhalb des Firebase-Callbacks fügt jedoch dateInSecs zum dict hinzu.

taba/KlasseA:

ClassA : UIViewController{ 

@IBAction fileprivate func postTapped(_ sender: UIButton) { 

    //you must add this or whichever date formatter your using 
    let dateInSecs:NSNumber? = Date().timeIntervalSince1970 as NSNumber? 

    dict = [String:Any]() 
    dict.updateValue("Adidas", forKey: "sneakerName") 
    dict.updateValue(dateInSecs!, forKey: "dateInSecs")//you must add this 

    sneakerRef.?.updateChildValues(dict, withCompletionBlock: { 
       (error, ref) in 
      // 1. show alert everything was successful 

      // 2. no need to use the Notification so I removed it 
    } 
} 
} 

Und in tabb/ClassB innerhalb des Handlers Abschluss des Firebase Beobachter im pullDataFromFirebase() Funktion I die filterDuplicates Methode verwendet, um die doppelten Elemente herauszufiltern, die nach oben zeigten.

tabb/ClassB:

func pullDataFromFirebase(){ 

    sneakerRef?.observe(.childAdded, with: { 
     (snapshot) in 

     if let dict = snapshot.value as? [String:Any]{ 
      let sneakerName = dict["sneakerName"] as? String 

      let sneakerModel = SneakerModel() 
      sneakerModel.sneakerName = sneakerName 

      self.sneakerModels.append(sneakerModel) 

      // use the filterDuplicates method here 
      self.sneakerModels = self.sneakerModels.filterDuplicates{$0.dateInSecs == $1.dateInSecs} 

      self.tableView.reloadData() 
     } 
    }) 
} 

Grundsätzlich ist das Verfahren filterDuplicates Schleifen durch das Array sneakerModels jedes Element auf die dateInSecs verglichen, und wenn er sie findet schließt sie die Kopien. Ich reinitialisiere dann die SneakerModels mit den Ergebnissen und alles ist gut.

Beachten Sie auch, dass es keine Notwendigkeit für die Notification-Beobachter in ClassB's viewDidLoad, also habe ich es entfernt.

Verwandte Themen