1.前言
項目使用MJRefresh作為下拉刷新控件山孔。在手動觸發(fā)下拉刷新時候遇到了一個bug钾军,看了一下MJRefresh的源碼,發(fā)現(xiàn)MJRefresh的實現(xiàn)有點瑕疵展鸡,總結(jié)在此姨丈。
2.問題描述
如果我們這樣使用MJRefresh畅卓,最后MJRefresh Header將會保持下拉刷新的狀態(tài),而不能恢復(fù)到Idle的狀態(tài)构挤。
MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
@strongify(self);
[[self.viewModel reloadData] subscribeNext:^(id x) {
@strongify(self);
[self endRefreshing];
} error:^(NSError *error) {
@strongify(self);
[self endRefreshing];
}];
}];
self.tableView.mj_header = header;
...
[self.tableView.mj_header endRefreshing];
[self.tableView.mj_header beginRefreshing];
以上代碼中調(diào)用beginRefreshing
是為了觸發(fā)下拉刷新髓介。調(diào)用endRefreshing
是不必要的,但是這條語句會導(dǎo)致MJRefresh表現(xiàn)不正確(即不能恢復(fù)到Idle狀態(tài))筋现,作為組件應(yīng)該更加健壯一些唐础,說明MJRefresh實現(xiàn)上有些瑕疵箱歧。下面具體分析一下。
3.問題原因
問題核心原因是:
在MJRefreshHeader類setState方法中“更改UI為refreshing狀態(tài)”的操作是異步的一膨。也就是說呀邢,設(shè)置Refreshing狀態(tài)時,設(shè)置內(nèi)部狀態(tài)和設(shè)置UI狀態(tài)被分離開了豹绪,如果在中間插入了設(shè)置內(nèi)部狀態(tài)(比如Idle)的操作可能會導(dǎo)致內(nèi)部狀態(tài)和UI狀態(tài)不一致的問題价淌。另外,MJRefreshendRefreshing
方法中“設(shè)置狀態(tài)為Idle”操作是異步的瞒津。
出現(xiàn)問題的原因就是兩次異步蝉衣,由于執(zhí)行順序的原因,導(dǎo)致內(nèi)部狀態(tài)和UI狀態(tài)不一致巷蚪。
源碼如下:
- (void)beginRefreshing
{
...
self.state = MJRefreshStateRefreshing;
...
}
---
- (void)endRefreshing
{
dispatch_async(dispatch_get_main_queue(), ^{
self.state = MJRefreshStateIdle;
});
}
- (void)setState:(MJRefreshState)state
{
// 根據(jù)狀態(tài)做事情
if (state == MJRefreshStateIdle) {
...
// 恢復(fù)inset和offset
[UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
self.scrollView.mj_insetT += self.insetTDelta;
// 自動調(diào)整透明度
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
} else if (state == MJRefreshStateRefreshing) {
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
// 增加滾動區(qū)域top
self.scrollView.mj_insetT = top;
// 設(shè)置滾動位置
[self.scrollView setContentOffset:CGPointMake(0, -top) animated:NO];
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
});
}
}
按照我們在問題描述中的調(diào)用方式病毡,最后執(zhí)行順序如下:
- dispatch
set state idle
operation - set state refreshing
- dispatch
set ui refreshing
operation - set state idle
- set ui refreshing
至此,內(nèi)部狀態(tài)為idle屁柏,UI狀態(tài)為refreshing啦膜。
內(nèi)部狀態(tài)為Idle狀態(tài),之后的endRefreshing
將不會生效(發(fā)現(xiàn)newState與oldState一致就直接返回了)淌喻,UI無法恢復(fù)為Idle狀態(tài)僧家。
4.問題解決
最好的解決辦法是把setState中“更改UI為refreshing狀態(tài)”的操作變成同步的。避免設(shè)置內(nèi)部狀態(tài)和設(shè)置UI狀態(tài)的分離裸删,因為兩者分離之后八拱,如果中間執(zhí)行了“設(shè)置狀態(tài)為Idle”,那么將導(dǎo)致最終內(nèi)部狀態(tài)為Idle烁落、UI狀態(tài)為Refreshing的問題乘粒,也就是標(biāo)題所說的內(nèi)部狀態(tài)和UI狀態(tài)不一致的問題。