緣由
基于面向對象的開發原則中的
迪米特法則
:
一個軟體實體應當盡可能少的與其他實體發生互相作用
;為了降低清單無資料占位圖的使用成本及代碼耦合性,對網上現用的一些解決方案加以優化;
核心
針對基于runtime替換reloadData方法的相關,這裡就不做多闡述了,本文主要讨論以下幾個問題:
-
1.需要顯示占位圖的情況;
-
2.tableView初次系統調用reloadData方法的幹擾排除最優方案;
-
3.網絡因伺服器故障請求失敗的處理;
-
4.占位圖觸發再次網絡請求的政策;
問題1:需要顯示占位圖的情況
現在流行的判斷方案是:
-
tableView.rows==0;
我需要補充說明的是:
-
tableView.sections>0&&tableView.rows==0&&tableView.viewForHeaderInSection!=nil;
針對第一種
rows==0
的情況就不做多解釋;第二種的話主要就是:當一個清單的資料綁定在
sectionHeaderView
上面,此時
row==0
;然後需求是:點選
sectionHeaderView
,展開
section
,重新整理資料;
row>=0
;是以如果僅僅考慮
rows==0
的情況,在第二種需求的情況占位圖顯示就會異常;
補充:
無網絡的時候直接加載占位圖;
問題2:tableView初次系統調用reloadData方法的幹擾排除最優方案
在網上我看到的解決方案是:
在category給UITableView新增isFirstReload屬性;如果是第一次加載的話設定
tableView.isFirstReload = YES
;然後内部的判斷是:
if (!self.firstReload) { [self checkEmpty]; } self.firstReload = NO;
針對每次都需要在控制器中調用
tableView.isFirstReload = YES
,我也是做了很多優化,比如最開始的時候我會想直接在基類viewDidLoad或者利用
Aspects
切入viewWillApear
`方法中:周遊子視圖,如果是
[UITableView Class]
或者
[UICollectionView Class]`就直接調用;
(void)aspectViewWillAppearWithViewController:(UIViewController )viewController { NSArray subViews = viewController.view.subviews; [subViews enumerateObjectsUsingBlock:^(UIView view, NSUInteger idx, BOOL _Nonnull stop) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)view;
if (!tableView.isNotFirstReload) {
tableView.isNotFirstReload = NO;
}
}
//如果tableView非self.view的直接子視圖,而是孫視圖....
//可用遞歸優化;
NSArray *secondLevelSubviews = view.subviews;
[secondLevelSubviews enumerateObjectsUsingBlock:^(UIView *secondView, NSUInteger idx, BOOL * _Nonnull stop) {
if ([secondView isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)secondView;
if (!tableView.isNotFirstReload) {
tableView.isNotFirstReload = NO;
}
}
NSArray *thirdLevelSubviews = secondView.subviews;
[thirdLevelSubviews enumerateObjectsUsingBlock:^(UIView *thirdView, NSUInteger idx, BOOL * _Nonnull stop) {
if ([thirdView isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)thirdView;
if (!tableView.isNotFirstReload) {
tableView.isNotFirstReload = NO;
}
}
}];
}];
}]; } 如此優化,是可以達到效果,但是在視圖啟動的時候周遊子視圖無非是性能耗損的;最後腦筋急轉彎,其實也就是一個很簡單的方法就能解決這個問題:
@property (nonatomic, assign) BOOL isNotFirstReload;
- if (self.isNotFirstReload) { [self checkEmpty]; } self.isNotFirstReload = YES;
BOOL屬性第一次加載的時候本來就是NO,也就避免了外部的傳入;
~~問題3:網絡因伺服器故障請求失敗的處理~~
~~也就是在網絡請求的時候走
failure
的時候;一般情況下,在控制器失敗的回調中我們不會手動調用
[self.taleView reloadData]
;如果不調用的話,就不能正确的加載占位圖了;當然你也可以在失敗的回調中調用reloadData方法解決這個問題;我這裡給出另外一種解決方案:~~
~~通過
window
的
rootViewController
拿到目前的控制器,然後通過周遊目前控制器的子視圖擷取
tableView
,調用
reloadData
方法,主要代碼如下:~~
(instancetype)shareInstance { static RequestFailureHandler *shareInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
shareInstance = [[RequestFailureHandler alloc] init];
}); return shareInstance; }
(void)handleRequestFailure { //根控制器是UINavigationController if ([self.rootVC isKindOfClass:[UINavigationController class]]) {
[[RequestFailureHandler shareInstance] handleWithNavgationController:(UINavigationController *)self.rootVC];
}else {
//沒有UINavigationController的情況下
[[RequestFailureHandler shareInstance] findTargetViewWithController:self.rootVC];
} }
(void)handleWithNavgationController:(UINavigationController )nav { UIViewController vc = nav.visibleViewController; if (vc.childViewControllers.count>0) {
if ([vc.childViewControllers.firstObject isKindOfClass:[UIPageViewController class]]) {
UIPageViewController *pageVc = (UIPageViewController *)vc.childViewControllers.firstObject;
UIViewController *pageChild = pageVc.viewControllers.firstObject;
[[RequestFailureHandler shareInstance] findTargetViewWithController:pageChild];
}
}else{
[[RequestFailureHandler shareInstance] findTargetViewWithController:vc];
} }
(void)findTargetViewWithController:(UIViewController )viewController { NSArray subViews = viewController.view.subviews; [subViews enumerateObjectsUsingBlock:^(UIView view, NSUInteger idx, BOOL _Nonnull stop) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)view;
[tableView reloadData];
}
}]; }
pragma mark - Getters & Setters
(UIViewController *)rootVC { if (!_rootVC) {
_rootVC = [[[UIApplication sharedApplication] delegate] window].rootViewController;
} return _rootVC; }
~~針對這個做法,子視圖的周遊,性能是會耗損的;但是考慮到這個主要是請求失敗的回掉中(不像在問題2中是在控制器啟動的時候);耗損也不會影響其他,并且能夠統一處理;是以湊合能用;~~
補充:
後面突然這個方案存在一個問題:那就是當一個界面存在多個請求的時候,其中任何一個請求失敗會幹擾占位圖的加載;暫時沒想到更好的解決辦法;
問題4:占位圖觸發再次網絡請求的政策
事件回調:
- block回調
- delegate回調
可以直接在每個控制器中接收回調,并完成再次請求;我在這裡想的在基類懶加載tableView對象,然後設定代理接收回調;在回調裡面調用網絡請求的統一方法;
(void)loadData { //子類重寫這個方法,并且在這個方法中進行網絡請求 }
pragma mark - ReRequesDataDelegate
(void)reRequesData { [self loadData]; }
(UITableView *)tableView { if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
_tableView.tableFooterView = [UIView new];
_tableView.dataSource = self;
_tableView.delegate = self;
_tableView.reRequestDelegate = self;
} return _tableView; }
這樣的話,子類隻需要重寫
loadData
;并在裡面執行網絡請求,就可以達到目的;