给UIViewController瘦身之MVP

大胖子

iOS开发的人都会知道,MVC模式下,UIViewController会无比笨重!什么界面显示、代理方法、网络请求、都可以往里面塞。
而MVVM还不是很了解,后续做到在写Demo。

MVP

因此借鉴了做Android的同事的做法:MVP。
如果心急,本文最下面有代码传送门

M:Model
V:UIViewController(view)
P:Presenter

前两个好理解,那Presenter呢?
个人理解,作为分担UIViewController重担的角色,把UIViewController中的除了界面显示的其他逻辑,都丢进Presenter中。一个UIViewController对应一个PresenterPresenterUIViewController中实例化的同时,把UIViewController实例传给Presenter

那么UIViewControllerPresenter如何交互。
这里我实现了两种方式,一种是协议,一种是Block。不过Block实现起来更加轻巧。不像协议,需要声明一堆方法。

这里我具体写了一个例子,在一个登陆界面,有信息显示Label,账户输入框TextField,密码输入框TextField,登录按钮Button
按下按钮之后,在Presenter执行类似请求接口进行登录的逻辑。执行登录动作,可能有三种状态,一种是登录中,一种是登录成功,一种是登录失败。那么这三种状态,通过实现协议方法,将各自的数据回传给View

2.png

下面是文件结构:

1.png

以下是两种实现方式的代码:

使用协议实现数据回传

view:

ViewController.h

#import <UIKit/UIKit.h>
#import "MyPresenter.h"

@interface ViewController : UIViewController

//登录信息显示
@property (weak, nonatomic) IBOutlet UILabel *infoLabel;

//账户输入框
@property (weak, nonatomic) IBOutlet UITextField *accountTextF;

//密码输入框
@property (weak, nonatomic) IBOutlet UITextField *passwordTextF;

@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()<MyPresenterDelegate>{
    MyPresenter *presenter;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //实例化Presenter对象,将页面的传给Presenter
    presenter = [[MyPresenter alloc]initWithViewController:self];
    presenter.myPresenterDelegate = self;
 
}

//登录事件
- (IBAction)MyBtnClick:(UIButton *)sender{
    [presenter loginWithAccount:_accountTextF.text password:_passwordTextF.text];
}

#pragma mark -MyPresenterDelegate
-(void)logining:(NSString *)info{
    _infoLabel.text = info;
}

-(void)loginSuccess:(NSString *)info{
    _infoLabel.text = info;
}

-(void)loginFailure:(NSString *)info{
    _infoLabel.text = info;
}

//触摸空白地方,键盘隐藏
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [_accountTextF resignFirstResponder];
    [_passwordTextF resignFirstResponder];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Presenter

BasePresenter.h

#import <UIKit/UIKit.h>

@interface BasePresenter : NSObject

@property (nonatomic,strong)UIViewController *vc;

-(instancetype)initWithViewController:(UIViewController*)viewC;

@end

BasePresenter.m

#import "BasePresenter.h"

@implementation BasePresenter

-(instancetype)initWithViewController:(UIViewController*)viewC{
    self = [super init];
    if (self) {
        self.vc = viewC;
    }
    return self;
}

@end

MyPresenter.h

#import "BasePresenter.h"
#import "ViewController.h"


@protocol MyPresenterDelegate<NSObject>//执行完,反馈给界面,传值或者使得界面做出具体操作。

- (void)logining:(NSString*)info;
- (void)loginSuccess:(NSString*)info;
- (void)loginFailure:(NSString*)info;

@end

@interface MyPresenter : BasePresenter

@property(nonatomic,weak)id<MyPresenterDelegate>myPresenterDelegate;

-(void)loginWithAccount:(NSString*)accuout password:(NSString*)password;


@end

#import "MyPresenter.h"

@interface MyPresenter(){
    ViewController *viewController;
}

@end


@implementation MyPresenter

-(instancetype)initWithViewController:(UIViewController *)viewC{
    self = [super initWithViewController:viewC];
    if (self) {
        viewController= (ViewController*)self.vc;
    }
    return self;
}

-(void)loginWithAccount:(NSString*)accuout password:(NSString*)password{
    NSLog(@"MyPresenter logining...");
    if ([self.myPresenterDelegate respondsToSelector:@selector(logining:)]) {
        [self.myPresenterDelegate logining:@"MyPresenter logining..."];
    }
    //此处模拟网络请求数据,2秒延迟
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2秒后异步执行这里的代码...
        // 登录成功或失败,返回数据给界面(实际应用中,此处写:访问登录接口)
        if ([accuout isEqualToString:@"vampire"]&&[password isEqualToString:@"123"]) {
            if ([self.myPresenterDelegate respondsToSelector:@selector(loginSuccess:)]) {
                [self.myPresenterDelegate loginSuccess:@"login success"];
            }
        }else{
            if ([self.myPresenterDelegate respondsToSelector:@selector(loginFailure:)]) {
                [self.myPresenterDelegate loginFailure:@"login failure"];
            }
        }
        
    });
    
}
@end

使用Block实现数据回传

View

ViewController.h

#import <UIKit/UIKit.h>
#import "MyPresenter.h"

@interface ViewController : UIViewController

//登录信息显示
@property (weak, nonatomic) IBOutlet UILabel *infoLabel;

//账户输入框
@property (weak, nonatomic) IBOutlet UITextField *accountTextF;

//密码输入框
@property (weak, nonatomic) IBOutlet UITextField *passwordTextF;

@end

ViewController.m

#import "ViewController.h"
#define WEAKSELF typeof(self) __weak weakSelf = self;

@interface ViewController (){
    MyPresenter *presenter;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //实例化Presenter对象,将页面的传给Presenter
    presenter = [[MyPresenter alloc]initWithViewController:self];

    [self loginResult];
}

//登录事件
- (IBAction)MyBtnClick:(UIButton *)sender{
//    [presenter loginWithAccount:_accountTextF.text password:_passwordTextF.text];
    
    //方式二:
    
//    WEAKSELF
//    [presenter login2WithAccount:_accountTextF.text password:_passwordTextF.text block:^(NSString *info) {
//        weakSelf.infoLabel.text = info;
//    }];
}

//Block实现
-(void)loginResult{
    WEAKSELF
    presenter.loginResult = ^(NSString *info){
        weakSelf.infoLabel.text = info;
    };
}


//触摸空白地方,键盘隐藏
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [_accountTextF resignFirstResponder];
    [_passwordTextF resignFirstResponder];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Presenter

BasePresenter文件保持不变。

MyPresenter.h

#import "BasePresenter.h"
#import "ViewController.h"

//因为三种登录状态都是传回一个NSString,所以定义一个通用Block
typedef void (^LoginResult)(NSString *info);



@interface MyPresenter : BasePresenter

@property (nonatomic,strong)LoginResult loginResult;//声明Blokc

-(void)loginWithAccount:(NSString*)accuout password:(NSString*)password;

-(void)login2WithAccount:(NSString*)accuout password:(NSString*)password block:(void(^)(NSString *info))block;

@end

MyPresenter.m

#import "MyPresenter.h"

@interface MyPresenter(){
    ViewController *viewController;
}

@end


@implementation MyPresenter

-(instancetype)initWithViewController:(UIViewController *)viewC{
    self = [super initWithViewController:viewC];
    if (self) {
        viewController= (ViewController*)self.vc;
    }
    return self;
}

-(void)loginWithAccount:(NSString*)accuout password:(NSString*)password{
    
    //Block引用
    self.loginResult(@"MyPresenter logining...");
    
    //此处模拟网络请求数据,2秒延迟
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2秒后异步执行这里的代码...
        // 登录成功或失败,返回数据给界面(实际应用中,此处写:访问登录接口)
        if ([accuout isEqualToString:@"vampire"]&&[password isEqualToString:@"123"]) {
            
            weakSelf.loginResult(@"login success");
            
        }else{
            
            weakSelf.loginResult(@"login failure");
        }
    });
}

//当然Block也可以放到方法上作为参数
-(void)login2WithAccount:(NSString*)accuout password:(NSString*)password block:(void(^)(NSString *info))block{
    
    //Block引用
    block(@"MyPresenter logining...");
    
    //此处模拟网络请求数据,2秒延迟
//    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2秒后异步执行这里的代码...
        // 登录成功或失败,返回数据给界面(实际应用中,此处写:访问登录接口)
        if ([accuout isEqualToString:@"vampire"]&&[password isEqualToString:@"123"]) {
            
            block(@"login success");
            
        }else{
            
            block(@"login failure");
            
        }
    });
}

@end

可以看到以上例子,Block的声明方式是不同的,具体的写法也是不一样的。

小结

通过以上两个例子,可以发现,Presenter使得UIViewController得以解放,很多逻辑可以写到Presenter中。而逻辑的执行结果,需要返回给UIViewController,所以就需要使用协议或者Block。

代码传送门

MVPDemo
MVPBlockDemo

看懂了的朋友,点个💗吧!么么哒~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容