利用预加载让分页加载不再繁琐之单个分页讲解

大概是项目里太多的分页加载数据,所以一个简单、快捷、高效分页加载会使你那么的愉悦.

大概就是这么丝滑

images

github链接:JSLoadMoreService

用法讲解

属性预览

NSObject+LoadMoreService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 分页请求数量
*/
static NSInteger const PerPageMaxCount = 20;
@interface NSObject (LoadMoreService)
/**
* 每次请求追加的indexpaths
*/
@property (nonatomic, strong) NSMutableArray *appendingIndexpaths;
/**
* 数据数组
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 原始请求数据
*/
@property (nonatomic, strong) id orginResponseObject;
/**
* 当前页码
*/
@property (nonatomic, assign) NSInteger currentPage;
/**
* 是否请求中
*/
@property (nonatomic, assign) BOOL isRequesting;
/**
* 是否数据加载完
*/
@property (nonatomic, assign) BOOL isNoMoreData;
/**
* 单一请求分页加载数据
*
* @param baseURL 请求地址
* @param para 请求参数
* @param keyOfArray 取数组的key(注:多层请用/分隔)
* @param classNameOfModelArray 序列化model的class_name
* @param isReload (YES:刷新、NO:加载更多)
*
* @return RACSingal
*/
- (RACSignal *)js_singalForSingleRequestWithURL:(NSString *)baseURL
para:(NSMutableDictionary *)para
keyOfArray:(NSString *)keyOfArray
classNameOfModelArray:(NSString *)classNameOfModelArray
isReload:(BOOL)isReload;
@end

UITableView+Preload.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 预加载触发的数量
*/
static NSInteger const PreloadMinCount = 10;
typedef void(^PreloadBlock)(void);
typedef void(^ReloadBlock)(void);
@interface UITableView (Prereload)
/**
* 预加载回调
*/
@property (nonatomic, copy ) PreloadBlock js_preloadBlock;
/**
* tableview数据
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 计算当前index是否达到预加载条件并回调
*
* @param currentIndex row or section
*/
- (void)preloadDataWithCurrentIndex:(NSInteger)currentIndex;
/**
* 上拉刷新
*
* @param js_reloadBlock 刷新回调
*/
- (void)headerReloadBlock:(ReloadBlock)js_reloadBlock;
/**
* 结束上拉刷新
*/
- (void)endReload;

如何调用

建一个viewModel类

这里处理数据的逻辑,所以写了方法

(RACSignal *)siganlForJokeDataIsReload:(BOOL)isReload ```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
下面就是怎样调用分类的方法:
```- (RACSignal *)siganlForJokeDataIsReload:(BOOL)isReload{
RACReplaySubject *subject = [RACReplaySubject subject];
[[self js_singalForSingleRequestWithURL:Test_Page_URL
para:nil
keyOfArray:@"pdlist"
classNameOfModelArray:@"JSGoodListModel"
isReload:isReload] subscribeNext:^(id _Nullable x) {
/**
* x : 分类方法(js_singalForSingleRequestWithURL:...)里 sendNext 传过来的数组
* 你可以对每次传过来的数组的元素"再加工",知道达到你的要求后 再 sendNext
*/
//...
[subject sendNext:x];
} error:^(NSError * _Nullable error) {
[subject sendError:error];
} completed:^{
/**
* 走到这里为,每次分页请求所有逻辑处理完毕
*/
[subject sendCompleted];
}];
return subject;
}

VC调用:

整个方法:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)requestGoodListIsReload:(BOOL)isReload{
kWeakSelf(self)
[[self.viewModel siganlForJokeDataIsReload:isReload] subscribeError:^(NSError * _Nullable error) {
} completed:^{
kStrongSelf(self)
self.listTableView.dataArray = self.viewModel.dataArray;
[self.listTableView reloadData];
[self.listTableView endReload];
}];
}

tableview里调用预加载

绘制cell代理里调用,根据你的需求是row or section

(void)configureCell:(UITableViewCell *)cell
1
2
3
4
5
6
7
8
9
10
forRowAtIndexPath:(NSIndexPath *)indexPath
{
JSGoodListModel *model = self.dataArray[indexPath.row];
cell.textLabel.text = model.title;
cell.detailTextLabel.text = [NSString stringWithFormat:@"¥%@",model.price];
/**
* 根据当期index计算是否回调preloadblock
*/
[self preloadDataWithCurrentIndex:indexPath.row];
}

配置tableview的上拉刷新和预加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (JSListTableView *)listTableView{
if (!_listTableView) {
_listTableView = [[JSListTableView alloc] initWithFrame:self.view.bounds
style:UITableViewStyleGrouped];
[self.view addSubview:_listTableView];
kWeakSelf(self)
/**
* 刷新
*/
[_listTableView headerReloadBlock:^{
kStrongSelf(self)
[self requestGoodListIsReload:YES];
}];
/**
* 预加载
*/
_listTableView.js_preloadBlock = ^{
kStrongSelf(self)
[self requestGoodListIsReload:NO];
};
}
return _listTableView;
}

至此,流程就done了

内部方法实现步骤

NSObject+LoadMoreService.m

先用runtime associate property

(BOOL)isNoMoreData{
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
return [objc_getAssociatedObject(self, &key_isNoMoreData) boolValue];
}
- (void)setIsNoMoreData:(BOOL)isNoMoreData{
objc_setAssociatedObject(self, &key_isNoMoreData, @(isNoMoreData), OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)isRequesting{
return [objc_getAssociatedObject(self, &key_isRequesting) boolValue];
}
- (void)setIsRequesting:(BOOL)isRequesting{
objc_setAssociatedObject(self, &key_isRequesting, @(isRequesting), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)currentPage{
return [objc_getAssociatedObject(self, &key_currentPage) integerValue];
}
- (void)setCurrentPage:(NSInteger)currentPage{
objc_setAssociatedObject(self, &key_currentPage, @(currentPage), OBJC_ASSOCIATION_ASSIGN);
}
- (NSMutableArray *)dataArray{
return objc_getAssociatedObject(self, &key_dataArray);
}
- (void)setDataArray:(NSMutableArray *)dataArray{
objc_setAssociatedObject(self, &key_dataArray, dataArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableArray *)appendingIndexpaths{
return objc_getAssociatedObject(self, &key_appendingIndexpaths);
}
- (void)setAppendingIndexpaths:(NSMutableArray *)appendingIndexpaths{
objc_setAssociatedObject(self, &key_appendingIndexpaths, appendingIndexpaths, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)orginResponseObject{
return objc_getAssociatedObject(self, &key_orginResponseObject);
}
- (void)setOrginResponseObject:(id)orginResponseObject{
objc_setAssociatedObject(self, &key_orginResponseObject, orginResponseObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

分页请求的base Method,
需要你配置的地方都有warning标识着:

(RACSignal *)js_baseSingleRequestWithURL:(NSString *)baseURL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
para:(NSMutableDictionary *)para
isReload:(BOOL)isReload{
RACReplaySubject *subject = [RACReplaySubject subject];
if (![self isSatisfyLoadMoreRequest]&&!isReload) {
return subject;
}
if (!para) {
para = [NSMutableDictionary dictionary];
}
if (isReload) {
self.currentPage = 0;
#warning 此处可以添加统一的HUD
//...
}
self.currentPage++;
#warning 分页的key按需修改
para[@"page"] = @(self.currentPage);
para[@"per_page"] = @(PerPageMaxCount);
self.isRequesting = YES;
[[JSRequestTools js_getURL:baseURL para:para] subscribeNext:^(id _Nullable x) {
self.isRequesting = NO;
if (isReload) {
#warning 消失HUD
//...
}
[subject sendNext:x];
[subject sendCompleted];
} error:^(NSError * _Nullable error) {
self.isRequesting = NO;
if (self.currentPage>0) {
self.currentPage--;
}
[subject sendError:error];
}];
return subject;
}

此方法统一处理一些操作,比如:刷新remove,转model数组,记录是否加载完,记录当前请求的indexpath数组(为了是能调用insertRowsAtIndexPath:或者是insertSections:,而不用reloadData)

(RACSignal *)js_singalForSingleRequestWithURL:(NSString *)baseURL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
para:(NSMutableDictionary *)para
keyOfArray:(NSString *)keyOfArray
classNameOfModelArray:(NSString *)classNameOfModelArray
isReload:(BOOL)isReload{
RACReplaySubject *subject = [RACReplaySubject subject];
[[self js_baseSingleRequestWithURL:baseURL
para:para
isReload:isReload] subscribeNext:^(id _Nullable x) {
NSAssert(classNameOfModelArray, @"请建个对应的model,为了能创建数组模型!");
self.orginResponseObject = x;
if (!self.dataArray) {
self.dataArray = @[].mutableCopy;
}
if (isReload) {
[self.dataArray removeAllObjects];
}
NSArray *separateKeyArray = [keyOfArray componentsSeparatedByString:@"/"];
for (NSString *sepret_key in separateKeyArray) {
x = x[sepret_key];
}
NSArray *dataArray = [NSArray yy_modelArrayWithClass:NSClassFromString(classNameOfModelArray) json:x];
NSInteger from_index = self.dataArray.count;
NSInteger data_count = dataArray.count;
self.appendingIndexpaths = [self getAppendingIndexpathsFromIndex:from_index
appendingCount:data_count
inSection:0
isForRow:YES];
[subject sendNext:dataArray];
if (dataArray.count==0) {
self.isNoMoreData = YES;
} else {
self.isNoMoreData = NO;
[self.dataArray addObjectsFromArray:dataArray];
}
[subject sendCompleted];
} error:^(NSError * _Nullable error) {
[subject sendError:error];
}];
return subject;
}

判断是否满足预加载的条件:

(BOOL)isSatisfyLoadMoreRequest{
1
2
return (!self.isNoMoreData&&!self.isRequesting);
}

获取当前分页的所得indexpaths数组:

(NSMutableArray *)getAppendingIndexpathsFromIndex:(NSInteger)from_index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
appendingCount:(NSInteger)appendingCount
inSection:(NSInteger)inSection
isForRow:(BOOL)isForRow{
NSMutableArray *indexps = [NSMutableArray array];
for (NSInteger i = 0; i < appendingCount; i++) {
if (isForRow) {
NSIndexPath *indexp = [NSIndexPath indexPathForRow:from_index+i inSection:inSection];
[indexps addObject:indexp];
} else {
NSIndexPath *indexp = [NSIndexPath indexPathForRow:0 inSection:from_index+i];
[indexps addObject:indexp];
}
}
return indexps;
}

UITableView+Preload.m

给tableview扩展些属性以及方法

统一给tableview设置头部刷新

(void)headerReloadBlock:(ReloadBlock)js_reloadBlock{
1
2
3
4
MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:js_reloadBlock];
self.mj_header = header;
}

结束刷新

(void)endReload{
1
2
3
[self.mj_header endRefreshing];
}

判断当前index是否可以出发预加载

(void)preloadDataWithCurrentIndex:(NSInteger)currentIndex{
1
2
3
4
5
NSInteger totalCount = self.dataArray.count;
if ([self isSatisfyPreloadDataWithTotalCount:totalCount currentIndex:currentIndex]&&self.js_preloadBlock) {
self.js_preloadBlock();
}
}

是否达到预加载的条件

(BOOL)isSatisfyPreloadDataWithTotalCount:(NSInteger)totalCount currentIndex:(NSInteger)currentIndex{
1
2
return ((currentIndex == totalCount - PreloadMinCount) && (currentIndex >= PreloadMinCount));
}

依赖的三方库有:AFNetworking、ReactiveObjC、YYModel、MJRefresh

其实思路很简单,runtime扩展所需要的属性和方法,然后有机的结合调用,如果你真的看懂了,其实真的很方便,当然如果你有更好的建议都可以github issue我,共同学习共同进步~

坚持原创技术分享,您的支持将鼓励我继续创作!