恐怖と嫌悪とページネーション

問題は何ですか?



典型的なページネーション







これを否定したくない限り、典型的なiOS開発者はタブレットでの作業の大半を費やしていることを実践が示しています。 サービスレイヤーの設計は興味深く、アプリケーションでのユニバーサルルーティングの開発は刺激的であり、柔軟なキャッシュポリシーの設定は不要になりますが、テーブルインターフェイスの操作はグレーのルーチンです。 それにもかかわらず、光線がこの領域に落ちる場合があり、制約のある別の混乱の代わりに、ページネーションを実装するタスクに直面する可能性があります-または、モバイルアプリケーションでそれを呼び出すことが流行しているため、無限スクロール。









リモートリソースからすべてのニュース、お知らせ、映画リストを一度にダウンロードすることは、少なくとも効果的ではありません。そのため、ほとんどの場合、サーバーはデータ全体を限られたサイズに分割するさまざまなメカニズムをクライアントに提供します。







彼らが言うように、これまでのところとても良い。 データの最初のバッチをロードし、それをテーブルに表示し、最後までスクロールし、次のバッチをロードします-等々。 残念ながら-まあ、または幸いなことに、私たちは挑戦が大好きです-これはほんの始まりに過ぎません。







プロジェクトの複雑さと松葉杖APIの程度に応じて、かなり単純なタスクはSomethingに変質するリスクを負います。 このクリーチャーは最終的に自分の人生を見つけ、それに触れるすべての運命を台無しにし始めるため、大文字の何か。 ポイントに近づいて、私は実生活から例を挙げますが、それは完全に抽象的なプロジェクトで起こりました。







働く必要があった条件:









悪いニュースは、そのような場合に正しいことをするのは難しいということです。 幸いなことに、私は松葉杖の構造化された武器庫を在庫しているため、発生するほとんどの質問や問題に対処できます。







分解



任意の複雑な問題または問題は、限られた数のステップに分割されると解決可能になります。 私たちの場合も例外ではありません。 アプリケーションのページングシステム全体の原理を理解するには、それをいくつかのセクションに分解するだけで十分です。







  1. 出力の要素数を変更するためのルール:

    • テープは静的です。 例:編集セレクションの施設のリスト。 コンパイルすると、変更されなくなります。
    • 新しいアイテムは厳密に上部に追加されます。 例:アプリケーションの閲覧履歴。 最も単純な形式では、唯一の変更点は、リストの上部に新しいページビューを追加することです。
    • 発行の一部は変更される場合があります。 例:メールクライアントの文字のリスト。 ユーザーは新しい文字を削除、移動、受信できるため、リストは任意の位置で変更できます。
  2. 発行の関連性を変更するためのルール:

    • 発行は常に関連したままです。 例:すべて同じメールアプリケーション。 その要素は徐々に変化する可能性があり、ある状態から別の状態への厳密に決定された遷移点はありません。
    • 発行は、未定義の時点で改革できます。 例:インテリジェントなランキングニュースアプリ。 1時間に1回、ニュースが再構成され、最も関連性の高い資料がリストの最上部に移動します。
  3. コンテンツ更新ルール:

    • 表示されたデータは更新されません。 例:レストランの名前を持つセル。 名前の変更は非常にまれなので、この確率は安全に無視できます。
    • 表示されるデータは変更される場合があります。 例:likeカウンターを持つセル。 時間が経つにつれて、このカウンターは変化します。既にキャッシュされているアイテムのこのデータを更新できるようにする必要があります。


ページネーションを実装するタスクは、ほとんどの場合、2つの大きな部分に分割されます。データのロードとテープの更新で、通常はプルトゥリフレッシュで行われます。 これらのタスクにはそれぞれ独自のアプローチが必要であり、上記のスキームに完全に依存します。







松葉杖



このセクションは、すべての資料の重要な部分です。 限界/オフセットでのページングの複雑なメカニズムを実装するときに使用しなければならなかったすべての松葉杖を収集しました。







小さな教育プログラム。 制限/オフセットは、ページ分割されたデータのロードを実装するための最も一般的なアプローチです。 オフセットは、最初の要素に対する相対的なシフトです。 制限-ダウンロードされたデータの量。



例:ユーザーが20番目から10個のニュースをダウンロードしたいとします。 オフセット:20、制限:10


ダウンロード/テープの変更



どのようにテープを変更できる場合、次のページを要求しようとすると、要素が上下に移動することがあります。 問題の解決策は2つのステップで構成されます。 1つ目は、下方への変位に関するものです。 5つの要素の最初のページが既に読み込まれている状況を想像してください。 次の要求まで、サーバー上のデータ構造が変更され、2つの新しい要素が出力の上に追加されました。 これで、次のページをロードするときに、すでにキャッシュにある2つの要素を取得できます。







ダウンロード/テープの変更







同期が行われていないときに、limitプロパティで設定されているよりも多くの要素がサーバーに追加された場合、状況はさらに悪化する可能性があります。1つの新しい要素は取得されません。 キャッシュ内の要素の総数をオフセットとして使用すると、既にロードされている要素を引き続き要求するため、この時点で永久にスタックします。







問題は非常に簡単に解決されます。 次のデータバッチを受信すると、キャッシュされたコンテンツとの交差の数をカウントします。将来的には、結果の値をオフセットシフトとして使用します。 上記の例では、このシフトは2になります。







paging.startIndex = cachedPosts.count + intersections; paging.count = 5;
      
      





ダウンロード/配送のどの部分も変更可能



次の状況を考慮してください。ページをロードしましたが、最初の2つの要素は削除されました。 次のデータを要求すると、2つの要素に穴が空く。 何もしなければ、アプリケーションはそれについて何も知らず、データはロードされません。







ダウンロード/配送のどの部分も変更可能







この状況から抜け出す方法は、常に1つの要素に重ねられたデータを要求することです。 データを受信して​​も交差点が見つからない場合は、クエリ結果をキャンセルするか、他のパラメーターで繰り返します。







 paging.startIndex = startIndex - 1; paging.count = 5;
      
      





貯金箱への小さなライフハック-テープの要素が失われた場合、いつでも真剣に顔を合わせ、クライアントで実行されたデータの知的ランキングについて講演することができます。 彼らはそれを信じないだろう-Facebookを見せなさい、それに対して各デバイスの同じ出力は常に完全に異なって見える。


ダウンロード/配信はランダムに再構築できます



この場合、最も単純な実装オプションは、いわゆるテープキャストで動作することです。 この用語は通常、問題のすべてのレコードの識別子のリストを意味します。 最初のオープニングで、そのような印象を要求してデータベースに保存するか、単に記憶に保存します。 次に、次のページを取得するために、標準の制限/オフセットではなく、より複雑な要求を使用します。サーバーに特定の20の識別子の投稿を送信するように依頼します。







 NSRange pageRange = NSMakeRange = (startIndex, 20); NSArray *postIds = [snapshot subarrayWithRange:pageRange]; [self makeRequestWithIds:postIds];
      
      





発行が予期せず再構築されたとしても、これは私たちを傷つけることはありません-私たちは最初のリクエスト時に関連していた印象で作業しています-クライアントのために、テープは最初のオープニングのときとまったく同じようにソートされます。







ダウンロード/配信はランダムに再構築できます







リボンの更新/要素が上部に追加されます。



この場合、まず最初に、最後の同期以降に多数の新しい要素が追加された場合に形成されるホールの存在を判断できる必要があります。

一定量のデータを要求し、キャッシュのデータとの交差をチェックします。 交差点がある場合-すべてが正常であれば、通常どおり作業を続けることができます。 交差点がない場合、これはいくつかの投稿を見逃したことを意味します。







リボンの更新/要素が上部に追加されます。







この場合の対処方法-特定のアプリケーションごとに決定する必要があります。 要求された5つの項目を除くすべてのデータをリセットできます。 交差点が検出されるまで、アイテムをトップからページにロードし続けることができます。







 if (intersections == 0) { [self dropCache]; }
      
      





テープの更新/問題の一部を変更できます



この問題には2つの解決策があります。







  1. サーバーは、たとえば、Last-Modifiedで保存された最後の同期出力状態に基づいて、変更の差分をテープに送信します。 サーバーの応答ヘッダーからLast-Modifiedパラメーターを取得します。 この場合、クライアントはこれらの変更をデータベースの状態に適用するだけです。







     for (ShortPost *post in diff) { [self updateCacheWith:post]; }
          
          





  2. サーバーがこれを行う方法を知らない場合は、クライアント上でさらに100行を書き込む必要があります。 (前の段落の1つと同様に)ポストキャストを取得し、キャッシュに格納されているデータの現在の状態と比較する必要があります。







     for (ShortPost *post in snapshot) { if (![cachedPosts containsObject:post]) { [self downloadPost:post]; } } for (Post *post in cachedPosts) { if (![snapshot containsObject:post]) { [self deletePost:post]; } }
          
          







金型にない要素をすべて削除し、不足している要素をすべてダウンロードします。







リボンの更新/並べ替えの変更



引き渡しが再構築された場合、私たちは見つけなければなりません。 これを行う最も簡単な方法は、サーバーにヘッドリクエストを送信し、etagまたはLast-Modifiedパラメーターを保存されている値と比較することです。







 NSString *lastModified = [self makeFeedHeadRequest]; if (![lastModified isEqual:cachedLastModified]) { [self dropCache]; [self obtainPostSnapshot]; [self obtainFirstPage]; }
      
      





比較結果が負の場合、キャッシュ状態はリセットされ、IDキャストが更新されます。







リボンの更新/発行アイテムが変更される場合があります



リスト項目に、評価やいいねの数など、時間とともに変化するデータが含まれている場合、それらを更新する必要があります。 この問題の解決策は、原則として、以前のポイントの1つと相関しています。サーバーから返されたdiffを使用するか、重要なフィールドのみを含む短いデータ構造を要求します。 その後、要素の状態を手動で更新します。







 for (NSUInteger i = 0; i < cachedPosts.count; i++) { Post *cachedPost = cachedPosts[i]; ShortPost *post = snapshot[i]; if (![cachedPost isEqual:post]) { [cachedPost updatePostWithShortPost:post]; } }
      
      





主なことは、転送されるデータの量を最小限にすることです。







将来のヒント



結論として、有用性の程度を変えるためのヒントをいくつか提供したいと思います。







ヒント1、明らか



現在の問題を説明する特別なオブジェクトがあると非常に便利です。 それをリスト、カテゴリ、フィードと呼びます-それは重要ではありません。







 @interface Feed: NSObject @property (nonatomic, copy) NSArray <Post *> *posts; @property (nonatomic, copy) NSArray <NSString *> *snapshot; @property (nonatomic, assign) NSUInteger offset; @property (nonatomic, assign) NSUInteger maxCount; @property (nonatomic, strong) NSDate *lastModified; @end
      
      





現在のオフセット、リボン要素の最大数、最終変更日、IDナゲット-要素のリストを記述するために必要なすべてを含めることができます。







評議会2、建築



データと処理結果の要求を1か所に混在させないでください。 サーバーから受信したデータをすぐに表示しようとすると、多くの問題と制限が発生する可能性があります。

データを要求するロジックを、受信、処理、表示のロジックから分離します。







データを要求するロジックを、受信、処理、表示のロジックから分離する







この記事で説明した困難や松葉杖を避けてください。 私たちの主な目標は、キャッシュと表示状態の継続的な同期を提供することです。 画面がデータベースにあるものと同じものを表示する場合-人生ははるかに簡単になります。







たとえば、 NSFetchedResultsController



、CoreData通知、または他のORMの同様のメカニズムがNSFetchedResultsController



役立ちます。







ヒント3、カモフラージュ



テープとページネーションを更新するロジック全体を個別のオブジェクト(直接読み込みを処理するサービスのファサード)にカプセル化します。







最も恐ろしいもの-ネストされたブロック、インデックスと交差点の調整、論理分岐-がこのオブジェクトにあります。 消費者にとって、ファサードインターフェースは非常にシンプルに見え、ページ分割されたデータを操作するための基本的な方法を提供します。 このオブジェクトは可能な限りテストする必要があるという事実に特に注意してください。







ヒント4、最も重要な



最近まで、サーバーでのページネーションの通常の実装を主張するようにしてください。 もちろん、制限/オフセットはかなり柔軟なソリューションですが、クライアントはシンプルでなければなりません。 いいえ、ただし、可能な限り簡単です!







便利なリンク






All Articles