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-在实际开发的使用
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