Controller太胖、如何减肥?

前言:需要构建如下图的UI呈现出来给用户、逻辑业务(1.点击头部需要判断用户等级进行不同的操作2.点击cell可以携带数据进入详情页面3.注销用户时需要请求后台验证信息、以及判断余额是否为0、是否有优惠卷、最后请求后台注销)。

一、网络请求、解析数据提取出来。采用block直接回调model

1.新建PersonalCenterService类,专门处理网络请求以及model回调。model解析使用第三方YYModel。数据结构为两个部分,头部返回一个字典、第二部分为一个数组。备注:数据是自己构建的、此过程只是模拟网络请求过程。

#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];
}];
    
复制代码

二、采用继承、封装、类目的方式单元化

1.cell子类化

自定义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子类化

HeadViev我们可以封装成一个UIView子类,当作tableViewHeadView处理就好了。点击事件大家可以用target-action或者代理以及block等处理,这里我用了block、简洁明了。

- (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];
   
复制代码

三、从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];
}];

复制代码

四、定义协议、抽取业务逻辑到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];
}

复制代码

五、总结

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、代码简单、明了就好。最后附上结构图。

转载于:https://juejin.im/post/5d004eafe51d4510bf1d665f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值