TableView Daten Async Laden

  • TableView Daten Async Laden

    Hi,

    ich habe mal wieder ein Problem und ich finde auch mit googeln keine gute Lösung. Ich habe eine API. Die liefert mir Daten zurück, die ich in einen TableView anzeige. Soweit geht auch alles. Nun gibt diese API aber jeweils die Daten nur für 1 Seite zurück. Sprich Online gibt es eine Webseite die zeigt die Daten sagen wir mal auf 10 Seiten an. Jede Seite hat zwischen 10 und 30 Objekten. Nun kommt bei der API nicht alle Objekte auf einmal zurück, wäre ja auch ziemlich viel, sondern immer pro Seite. Also muss ich sozusagen kurz bevor der User bis ganz unten ist Seite 2 laden und dann immer so weiter.

    Nur wie löst man das am besten? Ich habe schon rausgefunden mit einem Async Task. Das ist logisch. Aber wann starte ich damit am besten? Oder ist es besser einen "Weiter" und "Zurück" Button zu machen? Wie würdet Ihr das machen? Bekommt man vom TableView gesagt, dass der User fast unten ist? Habt ihr schon mal sowas gelöst? Was macht man wenn man schnell scrollt, am besten einen Lade-Balken oder?

    Ich habe gegoogelt aber immer nur was zu einem Async Task gefunden aber nie wie ich das anwende und wann ich am besten anfange. Gibt es da von Apple gute Doku dazu?

    Viele Grüße
    Nils
  • AppleDeveloper schrieb:


    Aber wann starte ich damit am besten? Oder ist es besser einen "Weiter" und "Zurück" Button zu machen? Wie würdet Ihr das machen? Bekommt man vom TableView gesagt, dass der User fast unten ist? Habt ihr schon mal sowas gelöst? Was macht man wenn man schnell scrollt, am besten einen Lade-Balken oder?
    Hi Nils,

    Du wirst Dir eine Caching-Strategie überlegen müssen, die das Benutzerverhalten in Deiner App am besten widerspiegelt. Ich würde wahrscheinlich initial 3-5 Seiten fetchen und dann an jeder Scrollposition drei, wobei ich gelesene Seiten immer im Cache ließe. Konkret:
    1. Beim Aufruf werden immer die erste und letzte Seite geladen und gecacht
    2. Ebenfalls beim Aufruf wird die Seite der letzten Scroll-Position (bei "State Restoration") und deren Vorgänger / Nachfolger geladen und gecacht; macht zusammen mit den beiden von oben je nach Position 3 - 5 Seiten
    3. Bei jedem Scrollen wird die neue Seite und deren Vorgänger / Nachfolger geladen und gecacht
    Somit wäre die App schnell, wenn der Benutzer sequentiell blättert oder direkt zum Ende / Anfang springt. Alles andere dauert etwas und dort würde ich eine teil-transparente View mit UIActivityIndicator einblenden. Ein Ladebalken macht m. E. nur dann Sinn, wenn Du die noch benötigte Wartezeit (nicht die Datenmenge) abschätzen kannst. Leider nutzt ihn kaum jemand so (auch nicht Apple), was dann immer wieder zu Ladebalken führt, die ewig auf den letzten Prozenten hängen...

    Meine Meinung, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Ich habe das mal so gelöst bei einer großen Datenmenge, das ich beim ersten Aufruf nur soviele Daten geladen habe, das der sichtbare Tableview gefüllt war und den Rest im Hintergrund nachgeladen habe. Nach dem Laden wurden die Daten im Tableview ausgetauscht und ein reolad gemacht. Das war in der Anzeige nicht zu merken. Außer der User scrollt sofort ganz runter.
  • Ich fülle die Tableview immer erstmal mit Dummy Einträgem ala "loading..." oder meinetwegen mach in jede Zelle einen ActivityIndicator rein. Dann wird für jede Zelle eine Operation angestoßen die sich die Daten holt. Sobald die Daten vorliegen wird geschaut ob die Zelle noch da ist (Wegen dem reuse identifieziere ich die Zellen nicht nur Anhand ihres Pointes sondern auch noch mit einer UniqueID) und wenn ja, dann schick ich ihr eine Notification mit den Daten die sie dann aktualisiert.
    In Deinem Fall müßtest du unter die Operation halt noch eine Ebene haben die immer nur die Daten vom Webservice holt wenn sie nicht schon im Cache liegt bzw wenn sie sich nicht gerade im Status "receiving" befindet. Diese Ebene lädt dann immer auf Anforderung die richtige Seite und benachrichtigt wiederum die Operation wenn die gewünschte Seite vorliegt.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Danke für die Ansätze! Da wird das ganze schon klarer! Das habe ich verstanden! Nun noch eine technische Frage. Wann soll denn das ganze Laden am besten angestupst werden? Ich habe mir mal die Delegate Methoden mal angeschaut.

    Ich würde das hier in der Starten. Vlt. 20 Index Path vor Schluss der Tabelle. Ist das eine gute Idee? Und wenn der IndexPath wieder kleiner wird reagiere ich drauf und lade die Seiten davor oder?

    Quellcode

    1. - (void)tableView:(UITableView *)tableView
    2. didEndDisplayingCell:(UITableViewCell *)cell
    3. forRowAtIndexPath:(NSIndexPath *)indexPath
    Die Daten können dann nicht einfach mit Reload Data eingefügt werden sondern nur mit InsertRow... oder?
  • Das Problem hierbei ist jedoch die API. Ich bekomme leider nicht die Elemente einzeln. Ich muss ein Request für Seite 1 schicken und bekomme alle Einträge für Seite 1. Wenn die fertig ist muss ich einen neuen Request schicken mit page=2 usw.

    Mit deiner Methode kann ich dann immer die jeweiligen Daten füllen aber ich weiß doch gar nicht wie viele Zellen ich brauche. Also ich bekomme zwar zurück wie viel Elemente es sind aber das können schon mal 4000 auf sein. So viele Cellen kann ich doch nicht auf einmal hinzufügen.

    Also ich habe irgendwo einen grundlegenden Denkfehler oder?
  • AppleDeveloper schrieb:

    Das Problem hierbei ist jedoch die API. Ich bekomme leider nicht die Elemente einzeln. Ich muss ein Request für Seite 1 schicken und bekomme alle Einträge für Seite 1. Wenn die fertig ist muss ich einen neuen Request schicken mit page=2 usw.

    Mit deiner Methode kann ich dann immer die jeweiligen Daten füllen aber ich weiß doch gar nicht wie viele Zellen ich brauche. Also ich bekomme zwar zurück wie viel Elemente es sind aber das können schon mal 4000 auf sein. So viele Cellen kann ich doch nicht auf einmal hinzufügen.

    Also ich habe irgendwo einen grundlegenden Denkfehler oder?
    Anscheinend.

    Gehen wir es doch einfach mal durch. Es wird das erstemal cellForRow aufgerufen. Diese hat den Index 5. Du erstellst eine Operation, gibst Ihr den Zeiger auf die Zelle und merkst dir in die Zelle noch die 5. Die Operation kommt in die Queue. In die Zelle kommt irgendwas, dass der User sieht die ist noch nicht geladen. Fertig.

    Die Operation fragt jetzt wiederum bei einer Klasse, nennen wir sie Webservice an "Gib mir Eintrag 5". Der Webservice hat natuerlich jetzt noch nichts gemacht. Er muss also beim Server anfragen "Gib mir die Seite mit Eintrag 5". Wie auch immer das jetzt in Deinem Fall aussieht. Wenn es nicht anders geht, must du halt von Seite 1 bis X solange anfordern bis Eintrag 5 dabei ist. Das wäre natuerlich worst Case aber das würde bei allen Anwendungsfällen ein riesen Problem sein, wenn du mal zurück scrollen willst und eben nicht alle Einträge im Speicher halten kann.
    Der Webservice bekommt nun die Daten von Seit X mit den Einträgen 3-8 oder so. Die packst du wiederum in ein Array und gibst deiner Operation die Daten zu 5 zurück. Deine Operation ruft nun in der Celle (natuerlich mit performselectorOnMainThread) eine Methode auf, die diese Daten zur Anzeige bringt, wenn die Zelle noch den existiert und sie immer noch die 5 als Eintragsindex hat (Kann ja mitlerweile reused sein und nun plötzlich den Eintrag 22 haben)

    Wenn jetzt die nächste Operation Eintrag 10 abfragt, dann weiß der Webservice, dass er den noch nicht hat und muss ihn holen, ins Array packen und zurpck geben. Wird Eintrag 4 abgefragt, dann weiß der Webservice das er den schon hat und gibt ihn einfach zurück. Die maximale Anzahl an Einträgen in Deinem Array kannst Du dann so festlegen, dass eine vernünftiger Speicherverbrauch gewöhrleistet ist. Wird die maximale Anzahl erreicht, wirfst du den ältesten (ersten Eintrag) aus dem Array raus. Wenn Du es noch perfekte machen willst kannst du den ArrayElemeten noch einen lastUsed mitgeben, so das du das Element aus dem Array werfen kannst, was am längsten nicht angefragt wurde.

    Um noch performanter zu sein kannst du im cellWillEndVisible oder wie es heist auch noch die zugehörige Operation canceln.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)