rambler.ios #6 - pagination demystified
TRANSCRIPT
PAGINATION DEMYSTIFIED
Egor Tolstoy @igrekde
DISCLAIMER
ВЫСТУПЛЕНИЕ ОСНОВАНО НА ЛИЧНОМ СТРАДАНИЯХ ОПЫТЕ.
ДОКЛАД СОДЕРЖИТ ДЕТАЛЬНОЕ ОПИСАНИЕ ОТВРАТИТЕЛЬНЫХ КОСТЫЛЕЙ
И ПОЭТОМУ НЕ РЕКОМЕНДУЕТСЯ К ПРОСМОТРУ НИКОМУ.
Локальная фильтрация
Динамический размер страницы
Пересобирается раз в час
Выдача лежит на CDN
Все еще limit/offset
authorId = postId >> 32
Техники пагинации
Как подступиться
Загрузка вниз
Обновление ленты
limit/offset
httр://api.соm/entries?offset=100&limit=20
0
1
2
cache server
0
1
2
3
4
5
6
7
0
1
2
cache server
0
1
2
3
4
5
6
7
offset: 3 limit: 5
3
4
5
6
7
0
1
2
cache server
0
1
2
3
4
5
6
7
offset: 3 limit: 5
3
4
5
6
7
3
4
5
6
7
3
4
5
6
7
Номера страниц
httр://api.соm/entries?page_number=3
cache server
0
1
2
0
cache server
0
1
2
0
page: 1
1
cache server
0
1
2
0
page: 1
11
2
1
Позиции элементов не меняются
Условия
Курсоры
httр://api.соm/entries?since=752284800&limit=10
httр://api.соm/entries? max_id=228&limit=10
httр://api.соm/entries? after=MTAxNTE&limit=10
cache server
0
1
2
21:04-
21:02-
19:35-
19:18-
17:44-
16:02-
15:58-
15:49-
15:40-
3
4
5
6
7
8
0
1
2
19:35-
19:18-
17:44-
15:21- 9
cache server
0
1
2
21:04-
21:02-
19:35-
19:18-
17:44-
16:02-
15:58-
15:49-
15:40-
3
4
5
6
7
8
0
1
2
19:35-
19:18-
17:44-
since: 17:44 limit: 5
15:21- 9
3
4
5
6
7
16:02-
15:58-
15:49-
15:40-
15:21-
cache server
0
1
2
21:04-
21:02-
19:35-
19:18-
17:44-
16:02-
15:58-
15:49-
15:40-
3
4
5
6
7
8
0
1
2
19:35-
19:18-
17:44-
since: 17:44 limit: 5
15:21- 9
3
4
5
6
7
16:02-
15:58-
15:49-
15:40-
15:21-
16:02-
15:58-
15:49-
15:40-
5
6
7
8
15:21- 9
Уникальный параметр каждого элемента
Сортировка выдачи не меняется
Условия
Изменение количества элементов
Актуальность выдачи
Обновление контента
• Лента статична • Новые элементы добавляются сверху
• Любая часть выдачи может быть изменена
• Выдача всегда актуальна
• Выдача может быть переформирована
• Отображаемые данные не обновляются
• Отображаемые данные могут быть изменены
1. Количество элементов не
меняется
2. Выдача всегда актуальна
3.Данные могут быть
изменены
Афиша.Рестораны
1. Новые элементы
добавляются только сверху
2. Выдача всегда актуальна
3. Данные могут быть
изменены
Афиша
1. Количество элементов не
меняется
2. Выдача может быть
переформирована
3.Данные не могут быть
изменены
Рамблер.Новости
1.Любая часть выдачи может
быть изменена
2. Выдача всегда актуальна
3.Данные могут быть
изменены
Рамблер.Почта
тем временем в офисе бэкенда
Решения проблем
пагинации на
клиенте
item 0
item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
Загрузка вниз
Случай 1:
Лента статична
1/5
item 0
item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
Загрузка вниз
Случай 1:
Лента статична
2/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
Загрузка вниз
Случай 1:
Лента статична
3/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
offset: 5 limit: 5
Загрузка вниз
Случай 1:
Лента статична
4/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5 (cached)
item 6 (cached)
item 7 (cached)
item 8 (cached)
item 9 (cached)
offset: 0 limit: 5
offset: 5 limit: 5
Загрузка вниз
Случай 1:
Лента статична
5/5
paging.startIndex = cachedPosts.count;
paging.count = 5;
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
Загрузка вниз
Случай 2:
Добавились новые
элементы
1/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
item 0' (new)
item 2' (new)
Загрузка вниз
Случай 2:
Добавились новые
элементы
2/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
offset: 5 limit: 5
item 0' (new)
item 2' (new)
Загрузка вниз
Случай 2:
Добавились новые
элементы
3/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5 (cached)
item 6 (cached)
item 7 (cached)
item 8
item 9
offset: 0 limit: 5
offset: 5 limit: 5
item 0' (new)
item 2' (new)
Загрузка вниз
Случай 2:
Добавились новые
элементы
4/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5 (cached)
item 6 (cached)
item 7 (cached)
item 8
item 9
offset: 0 limit: 5
offset: 5 limit: 5
offset: 8 + 2
item 0' (new)
item 2' (new)
Загрузка вниз
Случай 2:
Добавились новые
элементы
5/5
paging.startIndex = cachedPosts.count
+ intersections; paging.count = 5;
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
Загрузка вниз
Случай 3:
Удалены старые
элементы
1/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
item 10
item 11
Загрузка вниз
Случай 3:
Удалены старые
элементы
2/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
item 10
item 11
offset: 5 limit: 5
Загрузка вниз
Случай 3:
Удалены старые
элементы
3/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7 (cached)
item 8 (cached)
item 9 (cached)
offset: 0 limit: 5
item 10 (cached)
item 11 (cached)
offset: 5 limit: 5
Загрузка вниз
Случай 3:
Удалены старые
элементы
4/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
...
Загрузка вниз
Случай 3:
Удалены старые
элементы
5/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
offset: 0 limit: 5
...
offset: 4 limit: 5
Загрузка вниз
Случай 3:
Удалены старые
элементы
6/7
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5 (cached)
item 6 (cached)
item 7 (cached)
item 8 (cached)
item 9
offset: 0 limit: 5
...
offset: 4 limit: 5
Загрузка вниз
Случай 3:
Удалены старые
элементы
7/7
paging.startIndex = startIndex - 1;
paging.count = 5;
еще одна шутка про Facebook
item 0
item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
Загрузка вниз
Случай 4:
Порядок выдачи
пересобирается
1/5
item 0
item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
items: 0,1,2,3,4
Загрузка вниз
Случай 4:
Порядок выдачи
пересобирается
2/5
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5
item 6
item 7
item 8
item 9
items: 0,1,2,3,4
Загрузка вниз
Случай 4:
Порядок выдачи
пересобирается
3/5
item 6
item 1 (cached)
item 4 (cached)
item 5
item 8
item 9
item 2 (cached)
item 0 (cached)
item 7
item 3 (cached)
Загрузка вниз
Случай 4:
Порядок выдачи
пересобирается
4/5
item 6 (cached)
item 1 (cached)
item 4 (cached)
item 5 (cached)
item 8 (cached)
item 9 (cached)
item 2 (cached)
item 0 (cached)
item 7 (cached)
item 3 (cached)
items: 5,6,7,8,9
Загрузка вниз
Случай 4:
Порядок выдачи
пересобирается
5/5
NSRange pageRange = NSMakeRange(startIndex, 5);
NSArray *postIds = [snapshot
subarrayWithRange:pageRange];
item 0 (new)
item 1 (new)
item 2 (new)
item 3 (new)
item 4 (new)
item 5 (new)
item 7 (cached)
item 8 (cached)
item 9 (cached)
item 6 (new)
Обновление
Случай 1:
Появилось много
новых элементов
1/3
item 7 (cached)
item 8 (cached)
item 9 (cached)
offset: 0 limit: 5
item 0 (new)
item 1 (new)
item 2 (new)
item 3 (new)
item 4 (new)
item 5 (new)
item 6 (new)
Обновление
Случай 1:
Появилось много
новых элементов
2/3
item 0 (cached)
item 1 (cached)
item 2 (cached)
item 3 (cached)
item 4 (cached)
item 5 (new)
item 7 (cached)
item 8 (cached)
item 9 (cached)
offset: 0 limit: 5
item 6 (new)
Обновление
Случай 1:
Появилось много
новых элементов
3/3
if (intersections == 0) {
[self dropCache]; }
item 0 (cached)
item 1 (cached)
item 3 (cached)
item 4 (cached)
item 6 (cached)
item 7 (cached)
item 8 (deleted)
item 9 (cached)
item 5 (new)
item 2 (new)
Обновление
Случай 2:
Свободно
изменяемая лента
1/1
for (ShortPost *post in diff) {
[self updateCacheWith:post]; }
for (ShortPost *post in snapshot) {
if (![cachedPosts containsObject:post]) {
[self downloadPost:post];
}
}
for (Post *post in cachedPosts) {
if (![snapshot containsObject:post]) {
[self deletePost:post];
}
}
item 6
item 1 (cached)
item 4 (cached)
item 5
item 8
item 9
item 2 (cached)
item 0 (cached)
item 7
item 3 (cached)
Обновление
Случай 3:
Порядок выдачи
пересобирается
1/1
NSString *lastModified = [self makeFeedHeadRequest];
if (![lastModified isEqual:cachedLastModified]) { [self dropCache]; [self obtainPostSnapshot]; [self obtainFirstPage]; }
item 0 (cached)
item 1 (updated)
item 2 (cached)
item 3 (updated)
item 4 (updated)
item 5
item 6
item 7
item 8
item 9
Обновление
Случай 4:
Данные элементов
меняются
1/1
for (NSUInteger i = 0; i < cachedPosts.count; i++) {
Post *cachedPost = cachedPosts[i];
ShortPost *post = snapshot[i];
if (![cachedPost isEqual:post]) {
[cachedPost updatePostWithShortPost:post];
}
}
posts: NSArray <Post *>
snapshot: NSArray <NSString *>
offset: NSUInteger
maxCount: NSUInteger
lastModified: NSDate
Feed
PostServiceloadPosts
didLoad
Cache
PostServiceloadPosts
didLoad
Cache
CacheTracker
PostServiceloadPosts
didLoad
Cache
PagingFacade
PostService
ViewController
FeedService CacheUpdater
SnapshotProcessor
PagingCalculator
@protocol PostPagingFacade <NSObject>
- (void)loadNextPageWithBlock:(ErrorBlock)block;
- (void)refreshCategoryWithBlock:(ErrorBlock)block;
- (void)reloadCategoryWithBlock:(ErrorBlock)block;
@end
Клиент должен быть
простым
Техники пагинации
Как подступиться
Загрузка вниз
Обновление ленты
Egor Tolstoy @igrekde
не смог придумать заключительную фразу