天天看點

【iOS學習】UITableView占位圖的低耦合性設計pragma mark - Getters & Setterspragma mark - ReRequesDataDelegate

緣由

基于面向對象的開發原則中的

迪米特法則

:

一個軟體實體應當盡可能少的與其他實體發生互相作用

;為了降低清單無資料占位圖的使用成本及代碼耦合性,對網上現用的一些解決方案加以優化;

【iOS學習】UITableView占位圖的低耦合性設計pragma mark - Getters & Setterspragma mark - ReRequesDataDelegate

核心

針對基于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

;并在裡面執行網絡請求,就可以達到目的;