Controller太胖、如何減肥?

前言:須要構建以下圖的UI呈現出來給用戶、邏輯業務(1.點擊頭部須要判斷用戶等級進行不一樣的操做2.點擊cell能夠攜帶數據進入詳情頁面3.註銷用戶時須要請求後臺驗證信息、以及判斷餘額是否爲0、是否有優惠卷、最後請求後臺註銷)。

1、網絡請求、解析數據提取出來。採用block直接回調model

1.新建PersonalCenterService類,專門處理網絡請求以及model回調。model解析使用第三方YYModel。數據結構爲兩個部分,頭部返回一個字典、第二部分爲一個數組。備註:數據是本身構建的、此過程只是模擬網絡請求過程。json

#import "PersonalCenterService.h"
#import "PersonalCenterModel.h"

@implementation PersonalCenterService

+ (void)getPersonalCenterData:(void(^)(PersonalCenterModel *,NSArray *))successBlock
{
    //模擬網絡請求-後臺返回數據
    NSDictionary *dictionary = @{
                                 @"head":@{
                                         @"name":@"怪咖君",
                                         @"autograph":@"人生得意須盡歡,莫使金樽空對月",
                                         @"grade":@"青銅",
                                         @"money":@"100",
                                         @"blind":@"2",
                                         @"cardCoupon":@"2",
                                         },
                                 @"rowInfomation":@[
                                         @{@"title":@"支付"},
                                         @{@"title":@"收藏"},
                                         @{@"title":@"卡劵"},
                                         @{@"title":@"用戶反饋"},
                                         @{@"title":@"系統設置"},
                                         ]
                          };
    
    //數據解析
    NSDictionary *head = [dictionary objectForKey:@"head"];
    //頭部model
    PersonalCenterModel*model =  [PersonalCenterModel  yy_modelWithDictionary:head];
    
    //section->model利用YYModel解析裝入數組、便於操做
    NSArray *rowInfomation = [dictionary objectForKey:@"rowInfomation"];
    NSArray *dataArray = [NSArray yy_modelArrayWithClass:[personalSecontionModel class] json:rowInfomation];
    
    successBlock(model,dataArray);

}

@end

複製代碼

2.controller中、直接回調block、刷新數據數組

[PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
   //接收數據
   self.headModel = headModel;
   self.sectionArray = sectionDataArray;
   //刷新數據
    [self.tableView reloadData];
}];
    
複製代碼

2、採用繼承、封裝、類目的方式單元化

1.cell子類化bash

自定義TableViewCell、子視圖佈局全放到cell中,cellforRow簡化代碼、不作任何建立子視圖以及添加target事件。網絡

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *identifier = @"TableViewCell";
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) {
        cell = [[TableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
    }
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.model = self.dataArray[indexPath.section];
    return cell;
}
複製代碼

這樣看起來仍是有點多、咱們自定義BaseCell、cell建立代碼放到BaseCell中。cell相關屬性也放到子cell中。以後就變成以下代碼、簡潔明瞭。數據結構

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewCell *cell = [TableViewCell cellWithTableView:tableView];
    cell.model = self.dataArray[indexPath.section];
    return cell;
}

複製代碼

BaseCell你們能夠封裝以下架構

/**
 *  建立單元格
 */
+ (instancetype)cellWithTableView:(UITableView *)tableView
{
    NSString *className = NSStringFromClass([self class]);
    NSString *identifier = [NSString stringWithFormat:@"%@",className];
    
    BaseTableViewCell *baseCell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if(baseCell == nil){
        baseCell = [[self alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }
    return baseCell;
}
複製代碼

2.HeadView子類化app

HeadViev咱們能夠封裝成一個UIView子類,看成tableViewHeadView處理就行了。點擊事件你們能夠用target-action或者代理以及block等處理,這裏我用了block、簡潔明瞭。ide

- (UIView*)headView
{
    PersonalCenterView *centerView = [[PersonalCenterView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100) clickBlock:^{
    //業務操做-根據model判斷等級 進行不一樣的的處理
    if ([self.headModel.grade isEqualToString:@"青銅"])
    {
      [ASRequestHUD showErrorWithStatus:@"您還不具有修改資格,努力升級吧"];
    }else if ([self.headModel.grade isEqualToString:@"黃金"])
    {
        //邏輯處理
        
    }else
    {
        //鑽石
        
    }
  }];
    centerView.model = self.headModel;
    return centerView;
}
複製代碼

3.數據展現放到view中工具

數據展現、複寫model的set方法就好(你們經常使用的方法,apple的MVC架構中model和View是不能通訊的)佈局

//headView
- (void)setModel:(PersonalCenterModel *)model
{
    self.nameLabel.text = model.name;
    self.autographLabel.text = model.autograph;
}

//cell
- (void)setModel:(personalSecontionModel *)model
{
    self.titleLabel.text = model.title;
}
複製代碼

4.封裝工具類

對於字符串判空、還有提示信息能夠封裝起來。一句話代碼調用就好。以下是註銷模擬過程。ASRequestHUD以及字符串判空、獲取當前控制器等所有封裝成工具類。備註:這裏用註銷過程示例、也是爲了體現Controller處理邏輯業務會佔用太多代碼

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"驗證信息" message:nil preferredStyle:UIAlertControllerStyleAlert];
   
// 添加文本框
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
    textField.placeholder = @"請輸入姓名";
}];
   
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
    textField.placeholder = @"請輸入身份證號碼";
}];
  
UIAlertAction * firstAction = [UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){
    UITextField *name= alertController.textFields.firstObject;
    UITextField *idCard= alertController.textFields.lastObject;
    if ([NSString isEmptString:name.text]&&[NSString isEmptString:idCard.text]) {
        [ASRequestHUD showErrorWithStatus:@"信息輸入不完整~"];
            return ;
        }
        //網絡請求1-假設請求成功 處理業務
        //網絡請求2-假設請求成功 處理業務
    if ([NSString isEmptString:model.money]) {
        [ASRequestHUD showErrorWithStatus:@"有餘額未體現,請提現後註銷"];
        return;
    }else if ([NSString isEmptString:model.blind])
    {
        [ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您還有%@張卡未解除,請前往解除綁定",model.blind]];
        return;
    }else if ([NSString isEmptString:model.cardCoupon])
    {
        [ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您還有%@張優惠卷未使用,請前往刪除或使用",model.cardCoupon]];
        return;
    }else
    {
        //網絡請求3-註銷-假設請求成功
        //假設須要刷新數據
        [self.tableView reloadUI];
    }];
 
    UIAlertAction *secondAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:firstAction];
    [alertController addAction:secondAction];
   
複製代碼

3、從Controller中提取UITableView的UITableViewDataSource和UITableViewDelegate

以上經過提取網絡請求等一系列方法、Controller是瘦身一些。但因爲UITableView協議內容太多、仍然顯得臃腫。如今須要將tableView協議剝離出來,控制器就變得乾淨起來。實現協議最核心的仍是數據源問題,因此咱們只傳入數據源、問題就解決了。

1.定義TableViewData類

#import "TableViewDataSource.h"
#import "TableViewCell.h"

@implementation TableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.dataArray.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewCell *cell = [TableViewCell cellWithTableView:tableView];
    cell.model = self.dataArray[indexPath.section];
    return cell;
}

@end
複製代碼

2.定義TableViewDelegate類

@interface TableViewDelegate()

@property(nonatomic ,strong)PresenterPesonalCenter *present;

@end

@implementation TableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //邏輯業務處理-進入詳情頁面
    personalSecontionModel *model = self.dataArray[indexPath.section];
    PersonalCenterDeatilController *controller = [PersonalCenterDeatilController new];
    controller.title = model.title;
    [[self  jsd_getCurrentViewController].navigationController pushViewController:controller animated:YES];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 50;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 10;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    return 0;
}

- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    return nil;
}

- (UIView*)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    return nil;
}

@end
複製代碼

3.Controller中只要以下代碼就好

//初始化協議
tableDataSource = [[TableViewDataSource alloc]init];
tableDelegate = [[TableViewDelegate alloc]init];
    
//請求數據
[PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
    self.headModel = headModel;
    tableDataSource.dataArray = sectionDataArray;
    tableDelegate.dataArray = sectionDataArray;
    [self.tableView reloadData];
}];

複製代碼

4、定義協議、抽取業務邏輯到Prsenter類、controller以及view只負責UI佈局以及展現。

通過以上三個步驟以後,Controller已經達到了瘦身的效果。但若是實際開發中業務邏輯太多、Controller仍然仍是很胖的。這種狀況咱們須要把Controller中的業務邏輯提取出到Prsenter類中、Controller專門負責UI展現、佈局,再也不關心業務操做。這裏採用的思想是在MVC基礎上擴展的MVP,咱們定義專門協議類,讓Prsenter類去實現,Controller只要跟Presenter交流就好

1.協議部分

#import <Foundation/Foundation.h>
#import "PersonalCenterModel.h"

NS_ASSUME_NONNULL_BEGIN

@protocol PersonalCenterProtocol <NSObject>

@optional

/***
 定義協議、全部邏輯業務都讓Present類去處理、controller和view只負責UI佈局和數據顯示
 **/

//註銷-prsenter實現
- (void)cancellation:(PersonalCenterModel*)model;

//刷新UI->tableView----->讓controller去實現(比較方便-爲了清晰、明瞭也能夠單獨給controller定義協議類)
- (void)reloadUI;

//點擊頭部-進入編輯頁面-prsenter實現
- (void)clickHead:(PersonalCenterModel*)model;

//cell點擊事件-進入詳情頁面並攜帶參數-prsenter實現
- (void)enterDetailPage:(personalSecontionModel*)model;

@end

NS_ASSUME_NONNULL_END
複製代碼

2.Prsenter處理

實現協議方法、處理業務邏輯。Prsenter就像箇中間人,從model獲取數據,通知view更新數據。model和view互相獨立。

#import "PresenterPesonalCenter.h"
#import "PersonalCenterViewController.h"
#import "PersonalCenterDeatilController.h"

@implementation PresenterPesonalCenter

//實現協議方法 註銷帳號&&點擊頭部進入想象
- (void)cancellation:(PersonalCenterModel*)model
{
    PersonalCenterViewController *controller = (PersonalCenterViewController*)[self jsd_getCurrentViewController];

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"驗證信息" message:nil preferredStyle:UIAlertControllerStyleAlert];
   
    // 添加文本框
    [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
        textField.placeholder = @"請輸入姓名";
    }];
   
    [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
        textField.placeholder = @"請輸入身份證號碼";
    }];
  
    UIAlertAction * firstAction = [UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){
        UITextField *name= alertController.textFields.firstObject;
        UITextField *idCard= alertController.textFields.lastObject;
        if ([NSString isEmptString:name.text]&&[NSString isEmptString:idCard.text]) {
            [ASRequestHUD showErrorWithStatus:@"信息輸入不完整~"];
            return ;
        }
        //網絡請求1-假設請求成功 處理業務
        //網絡請求2-假設請求成功 處理業務
        if ([NSString isEmptString:model.money]) {
            [ASRequestHUD showErrorWithStatus:@"有餘額未體現,請提現後註銷"];
            return;
        }else if ([NSString isEmptString:model.blind])
        {
            [ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您還有%@張卡未解除,請前往解除綁定",model.blind]];
            return;
        }else if ([NSString isEmptString:model.cardCoupon])
        {
            [ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您還有%@張優惠卷未使用,請前往刪除或使用",model.cardCoupon]];
            return;
        }else
        {
            //網絡請求3-註銷-假設請求成功
            //假設須要刷新數據
            if (controller &&[controller respondsToSelector:@selector(reloadUI)]) {
                [controller reloadUI];
            }
        }
        
    }];
 
    UIAlertAction *secondAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:firstAction];
    [alertController addAction:secondAction];
   
    [controller presentViewController:alertController animated:YES completion:nil];
}

- (void)clickHead:(PersonalCenterModel*)model
{
    if ([model.grade isEqualToString:@"青銅"])
    {
        [ASRequestHUD showErrorWithStatus:@"您還不具有修改資格,努力升級吧"];
    }else if ([model.grade isEqualToString:@"黃金"])
    {
        //邏輯處理
        
    }else
    {
        //鑽石
        
    }
}

- (void)enterDetailPage:(personalSecontionModel *)model
{
    //進入詳情頁面
    PersonalCenterDeatilController *controller = [PersonalCenterDeatilController new];
    controller.title = model.title;
    [[self  jsd_getCurrentViewController].navigationController pushViewController:controller animated:YES];
}

複製代碼

3.controller處理

點擊事件中、指定讓presenter去實現就好

//頭部點擊事件
if (weakSelf.presenter&&[weakSelf.presenter respondsToSelector:@selector(clickHead:)]) {
    [weakSelf.presenter clickHead:weakSelf.headModel];
}

//註銷點擊
if (self.presenter&&[self.presenter respondsToSelector:@selector(cancellation:)]) {
    [self.presenter cancellation:self.headModel];
}

cell中點擊進入詳情
self.present = [[PresenterPesonalCenter alloc]init];
personalSecontionModel *model = self.dataArray[indexPath.section];
    
if (self.present&&[self.present respondsToSelector:@selector(enterDetailPage:)]) {
    [self.present enterDetailPage:model];
}

複製代碼

5、總結

1.經過以上處理、控制器溫柔一笑,自言自語道「我終於瘦下來了」,我要去照照鏡子了。

#import "PersonalCenterViewController.h"
#import "PersonalCenterView.h"
#import "PersonalCenterService.h"

#import "TableViewDataSource.h"
#import "TableViewDelegate.h"

#import "PresenterPesonalCenter.h"

@interface PersonalCenterViewController ()
{
    TableViewDataSource *tableDataSource;
    TableViewDelegate *tableDelegate;
}

@property(strong ,nonatomic)PresenterPesonalCenter *presenter;
@property(strong ,nonatomic)UITableView *tableView;
@property(strong ,nonatomic)PersonalCenterModel *headModel;
@property(strong,nonatomic)UIButton *footButton;

@end

@implementation PersonalCenterViewController

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"我的中心";
    
    //初始化協議
    tableDataSource = [[TableViewDataSource alloc]init];
    tableDelegate = [[TableViewDelegate alloc]init];
    
    //請求數據
    [PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
        self.headModel = headModel;
        tableDataSource.dataArray = sectionDataArray;
        tableDelegate.dataArray = sectionDataArray;
        [self.tableView reloadData];
    }];
    
    //tableView
    [self.view addSubview:self.tableView];
    
    self.presenter = [[PresenterPesonalCenter alloc]init];
    
}

- (UITableView*)tableView
{
    if (!_tableView) {
        _tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, KNavBarHeight, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-KNavBarHeight) style:(UITableViewStyleGrouped)];
        _tableView.dataSource = tableDataSource;
        _tableView.delegate = tableDelegate;
        _tableView.tableHeaderView = [self headView];
        _tableView.tableFooterView = [self footView];
    }
    return _tableView;
}

- (UIView*)headView
{
    WeakSelf;
    PersonalCenterView *centerView = [[PersonalCenterView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100) clickBlock:^{
        //操做
        if (weakSelf.presenter&&[weakSelf.presenter respondsToSelector:@selector(clickHead:)]) {
            [weakSelf.presenter clickHead:weakSelf.headModel];
        }
    }];
    centerView.model = self.headModel;
    return centerView;
}

- (UIView*)footView
{
    UIButton*(^creatButton)(NSString*,CGRect) = ^(NSString *title,CGRect frame)
    {
        UIButton *button = [[UIButton alloc] initWithFrame:frame];
        button.backgroundColor = K_WhiteColor;
        [button setTitle:title forState:UIControlStateNormal];
        [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [button addTarget:self action:@selector(cancellation) forControlEvents:UIControlEventTouchUpInside];
        return button;
    };
    UIView *footView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100)];
    footView.backgroundColor = [UIColor groupTableViewBackgroundColor];
    
    UIButton *footbutton = creatButton(@"註銷",CGRectMake(0, 0, kScreenWidth, 50));
    footbutton.center = footView.center;
    
    [footView addSubview:footbutton];
    
    return footView;
}

//註銷方法
- (void)cancellation
{
    if (self.presenter&&[self.presenter respondsToSelector:@selector(cancellation:)]) {
        [self.presenter cancellation:self.headModel];
    }
}

#pragma mark-實現協議方法
- (void)reloadUI {
    //假設需求須要清空數據-更新UI
    tableDataSource.dataArray = @[];
    [self.tableView reloadData];
}

- (void)dealloc
{
    NSLog(@"控制器釋放了!!!!!!!!");
}

@end

複製代碼

2.Prsenter回之一笑,真是好女不過百也。方天下之英雄,爲使君與吾也。Controller發現再也不是本身一我的的天下了。

3.MVVM經過雙向綁定,也可使Controller達到瘦身效果。學習成本較前兩位高,也存在着缺點。我的認爲須要根據實際業務場景、不特別複雜的頁面,能用MVC就用MVC、代碼簡單、明瞭就好。最後附上結構圖。

相關文章
相關標籤/搜索