(完)④、iOS-RAC-在实际开发的使用-以登录注册为例子

该文详细介绍了ReactiveCocoa(RAC)在iOS登录注册场景中的应用,包括双向绑定、信号属性绑定及事件响应监听。通过ViewController和LoginViewModel的交互,展示了RAC如何实现UI与ViewModel的同步,按钮状态控制,以及登录过程中的状态更新。文中提供了一个完整的Demo,演示了RAC在实际项目中的具体使用方法。
摘要由CSDN通过智能技术生成

iOS RAC系列
①、iOS-RAC的开发用法-底层分析以及总结
②、iOS-RAC-核心类分析-RACPassthroughSubscriber订阅者-RACScheduler调度者-RACDisposable销毁者-RACObseve监听者-RACSubject
③、iOS-RAC-底层分析-RAC的宏-RACCommand
(完)④、iOS-RAC-在实际开发的使用-以登录注册为例子

Demo
Demo-iOS-RAC-高阶函数-带注释
iOS-RAC-实际开发案例-登录注册
资料
iOS-RAC底层源码分析-思维导图-MindNote

iOS-RAC-在实际开发的使用-以登录注册为例子

这里最主要是以登录注册为例子。来演示RAC是如何在实际开发的使用 demo下载链接

1、效果图

简单的登录注册页面
请添加图片描述

2、类

Viewcontroller.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController


@end
Viewcontroller.m
#import "ViewController.h"
#import <ReactiveObjC.h>
#import "LoginViewModel.h"
#import <SVProgressHUD.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *statuslabel;
@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
@property (weak, nonatomic) IBOutlet UITextField *accountTF;
@property (weak, nonatomic) IBOutlet UITextField *passwordTF;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;

@property (nonatomic, strong) LoginViewModel *loginVM;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    [self bindViewModelAndView];

}
- (void)bindViewModelAndView{
    
    // 这样就能进行双向绑定
    @weakify(self);
    // 正向传值 VM  <--- UI 传递
    /**
     把UI的值 传递到 VM里面去
     */
    RAC(self.loginVM,account) = self.accountTF.rac_textSignal;
    RAC(self.loginVM,password) = self.passwordTF.rac_textSignal;
    
    // 反向传值  VM ---> UI 传值
    // 防止内存循环
    [RACObserve(self.loginVM, iconUrl)subscribeNext:^(id  _Nullable x) {
        @strongify(self);
        self.iconImageView.image = [UIImage imageNamed:x];
    }];
    
    // vm ---> signal
    [self.loginVM.loginEnableSignal subscribeNext:^(NSNumber *x) {
        @strongify(self);
        UIColor *color = (x.intValue == 0) ? [UIColor lightGrayColor] :  [UIColor blueColor];
        [self.loginButton setBackgroundColor:color];
    }];
    
    
    // 按钮的点击能动性 --- account
    RAC(self.loginButton,enabled) = self.loginVM.loginEnableSignal;
    // 响应的发生 --- 响应的接受 --- vm --- UI
    RAC(self.statuslabel,text) = self.loginVM.statusSubject;
    
    // 响应的监听
    [[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(__kindof UIControl * _Nullable x) {
        // ----> VM ----> 迁移
        // 涉及 ----> 网络 ---> 状态 --- 响应 ---- 命令 ---- vm ---- 请求网络
        NSLog(@"按钮来了");
        [self.loginVM.loginCommand execute:@"登录"];
    }];
    

    

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"loginVM is %@",self.loginVM);

}
#pragma mark - LAZY
- (LoginViewModel *)loginVM
{
    if (!_loginVM){
        _loginVM = [[LoginViewModel alloc]init];
    }
    return _loginVM;
    
}

@end


LoginViewModel.h
#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
#import <SVProgressHUD.h>

NS_ASSUME_NONNULL_BEGIN


@interface LoginViewModel : NSObject

@property (nonatomic, copy) NSString *iconUrl;
@property (nonatomic, copy) NSString *account;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, strong) RACSignal  *loginEnableSignal; // 用来处理登录按钮的状态
@property (nonatomic, strong) RACCommand *loginCommand; // 登录按钮操作 用来发送指令 是登录 还是注册
@property (nonatomic, strong) RACSubject *statusSubject; // 状态的监听
@property (nonatomic) BOOL islogining; // 是不是正在登录


@end

NS_ASSUME_NONNULL_END
@end

NS_ASSUME_NONNULL_END
LoginViewModel.m
#import "LoginViewModel.h"
#import "UserModel.h"

@implementation LoginViewModel
- (instancetype)init{
    if (self = [super init]) {
        
        // 通过kvo监听
        // skip 1 是调用1次
        
        // 123 --- host ---
        // 进行一个map 拼接
        // distinctUntilChanged 直到改变的时候 调用 distinctUntilChanged 这个函数
        // 回到vc里面进行逻辑处理
        RAC(self,iconUrl) = [[[RACObserve(self, account) skip:1] map:^id _Nullable(id  _Nullable value)
        {
            return [NSString stringWithFormat:@"www:%@",value];
        }] distinctUntilChanged];
        
        // 业务逻辑层处理
        // 按钮点击能够 ---- account + passwrod --- 函数 组合 + 聚合
        self.loginEnableSignal = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, password)] reduce:^id (NSString *account,NSString *password) {
            return @(account.length>0 && password.length >0);
        }];
        

//        //登录状态
        self.islogining = NO;
        self.statusSubject = [RACSubject subject];
        //按钮事件
        [self setupLoginCommand];
        
    }
    return self;
}


- (void)setupLoginCommand{
    @weakify(self);
    // 初始化 command
    self.loginCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        // 网络信号 ---- 成功 ---- 失败 ---- 状态
        // 我们可以根据 input输入 是登录 还是 注册
        NSLog(@"input === %@",input);
        @strongify(self);
        return [self loginRequest];
        
    }];
    
    // 业务逻辑层进行监控
    // 成功
    [[self.loginCommand.executionSignals switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@"请求 switchToLatest ===\n  user is \n%@",x);
        @strongify(self);
        self.islogining = NO;
        [self.statusSubject sendNext:@"登录成功"];

    }];
    // 失败
    [self.loginCommand.errors subscribeNext:^(NSError * _Nullable x) {
        NSLog(@"请求 error === %@",x);
        @strongify(self);
        // 一旦请求失败 标记登录中的状态 改为 NO
        self.islogining = NO;
        [self.statusSubject sendNext:@"登录失败"];

    }];
    // 状态
    [[self.loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"请求 executing = %@",x);
        @strongify(self);
        if ([x boolValue])
        {
            [self statusLableAnimation];
        }
    }];
}

#pragma mark - 网络请求部分
//- (RACSignal *)loginRequest:(id)model
- (RACSignal *)loginRequest{
     // 抽取网络
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:2];
            if ([self.account isEqualToString:@"123"] && [self.password isEqualToString:@"123"]) {
                UserModel *u = [[UserModel alloc]init];
                u.userName = @"雨夜";
                u.password = @"123456";
                u.sex = @"男";
                u.region = @"广东深圳";
                [subscriber sendNext:u]; // 序列化  --- [model class]  --- @[model model]
//                [subscriber sendNext:@"登录成功"]; // 序列化  --- [model class]  --- @[model model]
                [subscriber sendCompleted];
            }
            else
            {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:10086 userInfo:@{@"YYError":@"fail"}];
                [subscriber sendError:error];
            }
        });
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"请求销毁了");
        }];
    }];
}

// 登录成功
- (void)statusLableAnimation{
    __block int num = 0;
    // logining = YES 表示正在登录
    self.islogining = YES;

    RACSignal *timerSignal = [[[RACSignal interval:0.5 onScheduler:[RACScheduler mainThreadScheduler]] map:^id _Nullable(NSDate * _Nullable value) {
        NSLog(@"登录时间:%@",value);
        NSString *statusStr = @"登录中,请稍后";
        num += 1;
        int count = num % 3;
        switch (count) {
            case 0:
                statusStr = @"登录中,请稍后.";
                break;
            case 1:
                statusStr = @"登录中,请稍后..";
                break;
            case 2:
                statusStr = @"登录中,请稍后...";
                break;
            default:
                break;
        }

        return statusStr;
    }] takeUntilBlock:^BOOL(id  _Nullable x) {

        if (num >= 20 || !self.islogining) {
            return YES;
        }
        return NO;
    }];

    [timerSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"subscribeNext == %@",x);
        if (self.islogining) {
            [self.statusSubject sendNext:x];
        }
    }];
    
    
}

- (NSString *)description{
    return [NSString stringWithFormat:@"%@-%@",self.account,self.password];
}


UserModel.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface UserModel : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, copy) NSString *region;

@end

NS_ASSUME_NONNULL_END

UserModel.m
#import "UserModel.h"

@implementation UserModel

- (NSString *)description{
    return [NSString stringWithFormat:@" username is %@\n passwrod is %@\n sex is %@\n region is %@",self.userName,self.password,self.sex,self.region];
}
@end

3、分析代码的作用

1.RAC的双向绑定 正向传值反向传值 以输入账号改变头像的情况为例
1.1.正向传值 就是通过UI 将值 传递给 ViewModel
1.2.反向传值 就是通过ViewModel 将值 传递给 UI 如果开发不是使用RAC,传值的情况可能是使用kvo、代理、block
ViewController
    // 这样就能进行双向绑定
    @weakify(self);
    // 正向传值 VM  <--- UI 传递
    /**
     把UI的值 传递到 VM里面去
     */
    RAC(self.loginVM,account) = self.accountTF.rac_textSignal;
    RAC(self.loginVM,password) = self.passwordTF.rac_textSignal;
    
    // 反向传值  VM ---> UI 传值
    // 防止内存循环
    [RACObserve(self.loginVM, iconUrl)subscribeNext:^(id  _Nullable x) {
        @strongify(self);
        self.iconImageView.image = [UIImage imageNamed:x];
    }];
LoginViewModel
- (instancetype)init{
    if (self = [super init]) {
        
        // 通过kvo监听
        // skip 1 是调用1次
        // 123 --- host ---
        // 进行一个map 拼接
        // distinctUntilChanged 直到改变的时候 调用 distinctUntilChanged 这个函数
        // 回到vc里面进行逻辑处理
        RAC(self,iconUrl) = [[[RACObserve(self, account) skip:1] map:^id _Nullable(id  _Nullable value)
        {
            return [NSString stringWithFormat:@"www:%@",value];
        }] distinctUntilChanged];
    }
    return self;
}

2.RAC的直接绑定 signal信号属性 以按钮的状态为例 根据账号密码是否输入改变颜色的状态
ViewController
	@weakify(self);


 // vm ---> signal
    [self.loginVM.loginEnableSignal subscribeNext:^(NSNumber *x) {
        @strongify(self);
        UIColor *color = (x.intValue == 0) ? [UIColor lightGrayColor] :  [UIColor blueColor];
        [self.loginButton setBackgroundColor:color];
    }];
LoginViewModel
- (instancetype)init{
    if (self = [super init]) {

        // 业务逻辑层处理
        // 按钮点击能够 ---- account + passwrod --- 函数 组合 + 聚合
        self.loginEnableSignal = [RACSignal combineLatest:@[RACObserve(self, account),RACObserve(self, password)] reduce:^id (NSString *account,NSString *password) {
            return @(account.length>0 && password.length >0);
        }];

    }
    return self;
}


3.RAC的事件响应的监听 以按钮的点击 发送虚拟的请求为例
ViewController
  // 响应的监听
    [[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(__kindof UIControl * _Nullable x) {
        // ----> VM ----> 迁移
        // 涉及 ----> 网络 ---> 状态 --- 响应 ---- 命令 ---- vm ---- 请求网络
        NSLog(@"按钮来了");
        [self.loginVM.loginCommand execute:@"登录"];
    }];
LoginViewModel
- (instancetype)init{
    if (self = [super init]) {
//        //登录状态
        self.islogining = NO;
        self.statusSubject = [RACSubject subject];
        //按钮事件
        [self setupLoginCommand];
        
    }
    return self;
}



- (void)setupLoginCommand{
    @weakify(self);
    // 初始化 command
    self.loginCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        // 网络信号 ---- 成功 ---- 失败 ---- 状态
        // 我们可以根据 input输入 是登录 还是 注册
        NSLog(@"input === %@",input);
        @strongify(self);
        return [self loginRequest];
        
    }];
    
    // 业务逻辑层进行监控
    // 成功
    [[self.loginCommand.executionSignals switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@"请求 switchToLatest ===\n  user is \n%@",x);
        @strongify(self);
        self.islogining = NO;
        [self.statusSubject sendNext:@"登录成功"];

    }];
    // 失败
    [self.loginCommand.errors subscribeNext:^(NSError * _Nullable x) {
        NSLog(@"请求 error === %@",x);
        @strongify(self);
        // 一旦请求失败 标记登录中的状态 改为 NO
        self.islogining = NO;
        [self.statusSubject sendNext:@"登录失败"];

    }];
    // 状态
    [[self.loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"请求 executing = %@",x);
        @strongify(self);
        if ([x boolValue])
        {
            [self statusLableAnimation];
        }
    }];
}

#pragma mark - 网络请求部分
//- (RACSignal *)loginRequest:(id)model
- (RACSignal *)loginRequest{
     // 抽取网络
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:2];
            if ([self.account isEqualToString:@"123"] && [self.password isEqualToString:@"123"]) {
                UserModel *u = [[UserModel alloc]init];
                u.userName = @"雨夜";
                u.password = @"123456";
                u.sex = @"男";
                u.region = @"广东深圳";
                [subscriber sendNext:u]; // 序列化  --- [model class]  --- @[model model]
//                [subscriber sendNext:@"登录成功"]; // 序列化  --- [model class]  --- @[model model]
                [subscriber sendCompleted];
            }
            else
            {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:10086 userInfo:@{@"YYError":@"fail"}];
                [subscriber sendError:error];
            }
        });
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"请求销毁了");
        }];
    }];
}

// 状态动画的改变 
- (void)statusLableAnimation{
    __block int num = 0;
    // logining = YES 表示正在登录
    self.islogining = YES;
	// 每0.5秒发生过一次信号
    RACSignal *timerSignal = [[[RACSignal interval:0.5 onScheduler:[RACScheduler mainThreadScheduler]] map:^id _Nullable(NSDate * _Nullable value) {
        NSLog(@"登录时间:%@",value);
        NSString *statusStr = @"登录中,请稍后";
        num += 1;
        int count = num % 3;
        switch (count) {
            case 0:
                statusStr = @"登录中,请稍后.";
                break;
            case 1:
                statusStr = @"登录中,请稍后..";
                break;
            case 2:
                statusStr = @"登录中,请稍后...";
                break;
            default:
                break;
        }

        return statusStr;
    }] takeUntilBlock:^BOOL(id  _Nullable x) {
	
	// 超过20秒 或者 状态发生改变的时候 终止定时器
        if (num >= 20 || !self.islogining) {
            return YES;
        }
        return NO;
    }];

    [timerSignal subscribeNext:^(id  _Nullable x) {
        NSLog(@"subscribeNext == %@",x);
        if (self.islogining) {
            [self.statusSubject sendNext:x];
        }
    }];
    
    
}


UserModel.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface UserModel : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, copy) NSString *region;

@end

NS_ASSUME_NONNULL_END

UserModel.m
#import "UserModel.h"

@implementation UserModel

- (NSString *)description{
    return [NSString stringWithFormat:@" username is %@\n passwrod is %@\n sex is %@\n region is %@",self.userName,self.password,self.sex,self.region];
}
@end


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇夜iOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值