转载

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

原文

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

大概就是这么丝滑

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

github链接:JSLoadMoreService

用法讲解

属性预览

NSObject+LoadMoreService.h/**

 *  分页请求数量
 */
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

/**
 *  预加载触发的数量
 */
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 ```
下面就是怎样调用分类的方法:
```- (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调用:

整个方法:

- (void)requestGoodListIsReload:(BOOL)isReload{
    1
    kWeakSelf(self)
    [[self.viewModel siganlForJokeDataIsReload:isReload] subscribeError:^(NSError * _Nullable error) {
        1
    } completed:^{
        kStrongSelf(self)
        self.listTableView.dataArray = self.viewModel.dataArray;
        [self.listTableView reloadData];
        [self.listTableView endReload];
    }];
}

tableview里调用预加载

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

(void)configureCell:(UITableViewCell *)cell
    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的上拉刷新和预加载:

- (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{
    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
                               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];
    }];
    1
    return subject;
}

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

(RACSignal *)js_singalForSingleRequestWithURL:(NSString *)baseURL
                                           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{
    return (!self.isNoMoreData&&!self.isRequesting);
}

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

(NSMutableArray *)getAppendingIndexpathsFromIndex:(NSInteger)from_index
                                     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{
    MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:js_reloadBlock];
    self.mj_header = header;
}

结束刷新

(void)endReload{

  
    [self.mj_header endRefreshing];
}

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

(void)preloadDataWithCurrentIndex:(NSInteger)currentIndex{

    NSInteger totalCount = self.dataArray.count;
    if ([self isSatisfyPreloadDataWithTotalCount:totalCount currentIndex:currentIndex]&&self.js_preloadBlock) {
        self.js_preloadBlock();
    }
}

是否达到预加载的条件

(BOOL)isSatisfyPreloadDataWithTotalCount:(NSInteger)totalCount currentIndex:(NSInteger)currentIndex{

    return  ((currentIndex == totalCount - PreloadMinCount) && (currentIndex >= PreloadMinCount));
}

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

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

正文到此结束
Loading...