緣由
基于面向?qū)ο蟮拈_發(fā)原則中的迪米特法則
:一個軟件實體應(yīng)當盡可能少的與其他實體發(fā)生相互作用
;為了降低列表無數(shù)據(jù)占位圖的使用成本及代碼耦合性,對網(wǎng)上現(xiàn)用的一些解決方案加以優(yōu)化;
核心
針對基于runtime替換reloadData方法的相關(guān),這里就不做多闡述了,本文主要討論以下幾個問題:
1.需要顯示占位圖的情況;
2.tableView初次系統(tǒng)調(diào)用reloadData方法的干擾排除最優(yōu)方案;
3.網(wǎng)絡(luò)因服務(wù)器故障請求失敗的處理;
4.占位圖觸發(fā)再次網(wǎng)絡(luò)請求的策略;
問題1:需要顯示占位圖的情況
現(xiàn)在流行的判斷方案是:
tableView.rows==0;
我需要補充說明的是:
tableView.sections>0&&tableView.rows==0&&tableView.viewForHeaderInSection!=nil;
針對第一種rows==0
的情況就不做多解釋;第二種的話主要就是:當一個列表的數(shù)據(jù)綁定在sectionHeaderView
上面,此時row==0
;然后需求是:點擊sectionHeaderView
,展開section
,刷新數(shù)據(jù);row>=0
;所以如果僅僅考慮rows==0
的情況,在第二種需求的情況占位圖顯示就會異常;
補充:
無網(wǎng)絡(luò)的時候直接加載占位圖;
問題2:tableView初次系統(tǒng)調(diào)用reloadData方法的干擾排除最優(yōu)方案
在網(wǎng)上我看到的解決方案是:
在category給UITableView新增isFirstReload屬性;如果是第一次加載的話設(shè)置tableView.isFirstReload = YES
;然后內(nèi)部的判斷是:
if (!self.firstReload) {
[self checkEmpty];
}
self.firstReload = NO;
針對每次都需要在控制器中調(diào)用tableView.isFirstReload = YES
,我也是做了很多優(yōu)化,比如最開始的時候我會想直接在基類viewDidLoad或者利用Aspects
切入viewWillApear``方法中:遍歷子視圖,如果是[UITableView Class]
或者[UICollectionView Class]
就直接調(diào)用;
- (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的直接子視圖,而是孫視圖....
//可用遞歸優(yōu)化;
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;
}
}
}];
}];
}];
}
如此優(yōu)化,是可以達到效果,但是在視圖啟動的時候遍歷子視圖無非是性能耗損的;最后腦筋急轉(zhuǎn)彎,其實也就是一個很簡單的方法就能解決這個問題:
@property (nonatomic, assign) BOOL isNotFirstReload;
- if (self.isNotFirstReload) {
[self checkEmpty];
}
self.isNotFirstReload = YES;
BOOL屬性第一次加載的時候本來就是NO,也就避免了外部的傳入;
問題3:網(wǎng)絡(luò)因服務(wù)器故障請求失敗的處理
也就是在網(wǎng)絡(luò)請求的時候走failure
的時候;一般情況下,在控制器失敗的回調(diào)中我們不會手動調(diào)用[self.taleView reloadData]
;如果不調(diào)用的話,就不能正確的加載占位圖了;當然你也可以在失敗的回調(diào)中調(diào)用reloadData方法解決這個問題;我這里給出另外一種解決方案:
通過window
的rootViewController
拿到當前的控制器,然后通過遍歷當前控制器的子視圖獲取tableView
,調(diào)用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中是在控制器啟動的時候);耗損也不會影響其他,并且能夠統(tǒng)一處理;所以湊合能用;
補充:
后面突然這個方案存在一個問題:那就是當一個界面存在多個請求的時候,其中任何一個請求失敗會干擾占位圖的加載;暫時沒想到更好的解決辦法;
問題4:占位圖觸發(fā)再次網(wǎng)絡(luò)請求的策略
事件回調(diào):
- block回調(diào)
- delegate回調(diào)
可以直接在每個控制器中接收回調(diào),并完成再次請求;我在這里想的在基類懶加載tableView對象,然后設(shè)置代理接收回調(diào);在回調(diào)里面調(diào)用網(wǎng)絡(luò)請求的統(tǒng)一方法;
- (void)loadData
{
//子類重寫這個方法,并且在這個方法中進行網(wǎng)絡(luò)請求
}
#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
;并在里面執(zhí)行網(wǎng)絡(luò)請求,就可以達到目的;