ReactiveCocoa 学习 一

ReactiveCocoa 框架,在刚听说过这个框架时,我便在github上搜索了一下,star的数量确实让我震惊了一下。查询资料了解后,感觉自己入行没多久,感觉这个框架高深莫测,不敢涉猎。
这个周末的闲暇之余,我决定学习一下这个框架并写个登录demo。

开始了。
首先来导入框架,我使用的是Cocopods来导入框架。毕竟这个框架手动导入起来实在是太麻烦😭

通过stroyboard搭建了一个登录界面,并声明了三个变量:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userTF;
@property (weak, nonatomic) IBOutlet UITextField *passTF;
@property (weak, nonatomic) IBOutlet UIButton *loginBTN;
@property (nonatomic, strong) LoginService *service; //这是自己写的模拟后台
@end

导入框架后,在view controller中引入ReactiveCocoa的头文件

#import <ReactiveCocoa/ReactiveCocoa.h> 

在viewdidload中写上代码:

RACSignal *userTFSingal = self.userTF.rac_textSignal;
RACSignal *passTFSingal = self.passTF.rac_textSignal;

[userTFSingal subscribeNext:^(id x) {
        NSLog(@"打印出来的用户名文本:%@",x);
    }];

运行之后会发现 userTF中值发生了改变就会打印一次userTF中的text;

也可以通过fliter来设定subscribeNext响应条件。

[[userTFSingal filter:^BOOL(NSString *text) {
        
        return text.length > 3;
    }] subscribeNext:^(id x) {
        NSLog(@"用户名超过三位的文本:%@",x);
    }];

map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。在上面的代码中,map以NSString为输入,取字符串的长度,返回一个NSNumber。

    [[[userTFSingal map:^id(NSString *text) {
        return @(text.length);
    }]
    filter:^BOOL(NSNumber *length) {
        return [length intValue]>5;
    }]
    subscribeNext:^(id x) {
        NSLog(@"用户名文本长度:%@",x);
    }];

上面说明了ReactiveCocoa的UITextField部分使用。下面开始实现登录逻辑吧!

设置有效文本长度


- (BOOL)isValidUsername:(NSString *)username {
    return username.length > 3;
}

- (BOOL)isValidPassword:(NSString *)password {
    return password.length > 3;
}

创建有效状态信号

    RACSignal *validUsernameSignal = [self.userTF.rac_textSignal 
map:^id(NSString *text) {
        return @([self isValidUsername:text]);
    }];
    RACSignal *validPasswordSignal = [self.passTF.rac_textSignal
     map:^id(NSString *text) {
         return @([self isValidPassword:text]);
     }];

根据文本是否有效改变文本框颜色

    [[validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor grayColor];
    }]
     subscribeNext:^(UIColor *color) {
        self.passTF.backgroundColor = color;
    }];

相对于上面这个方法更推荐使用下面这个方法:


RAC(self.userTF, backgroundColor) = [validUsernameSignal map:^id(NSNumber *userValid) {
        return [userValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

    
    RAC(self.passTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

聚合信号
combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。


RACSignal *loginSignal = [RACSignal combineLatest:@[validPasswordSignal,validUsernameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
        return @([usernameValid boolValue] && [passwordValid boolValue]);
    }];

这里是模拟后台

LoginService接口


typedef void (^RWSignInResponse)(BOOL);

@interface LoginService : NSObject

- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;

@end

实现


- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock {
    
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"];
        completeBlock(success);
    });
}

创建登录信号

- (RACSignal *)loginInSignal {
    
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [self.service signInWithUsername:self.userTF.text password:self.passTF.text complete:^(BOOL success) {
            [subscriber sendNext:@(success)];
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}

UIButton

设置按钮状态

[loginSignal subscribeNext:^(NSNumber *login) {
        self.loginBTN.enabled = [login boolValue];
    }];

UIButton部分使用

[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"登录按钮被点击了");
    }];

输出登录结果

    [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(id x) {
        NSLog(@"登录结果:%@",x);
    }];

这里你会发现打印的登录结果 不是bool类型
当点击按钮时,rac_signalForControlEvents发送了一个next事件(事件的data是UIButton)。map操作创建并返回了登录信号,这意味着后续步骤都会收到一个RACSignal。这就是你在subscribeNext:这步看到的。

上面问题的解决方法,有时候叫做信号中的信号,换句话说就是一个外部信号里面还有一个内部信号。你可以在外部信号的subscribeNext:block里订阅内部信号。不过这样嵌套太混乱啦,还好ReactiveCocoa已经解决了这个问题。

信号中的信号
解决的方法很简单,只需要把map操作改成flattenMap就可以了:

        [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^id(id value) {
            return [self loginInSignal];
        }] subscribeNext:^(NSNumber *loginIn) {
            NSLog(@"登录结果:%@",loginIn);
            BOOL success = [loginIn boolValue];
            if (success) {
                [self performSegueWithIdentifier:@"Kitten" sender:self];
            }
        }];

为了防止多次点击Button,使用doNext添加附加操作。

    [[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
        self.loginBTN.enabled = NO;
    }] flattenMap:^RACStream *(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(NSNumber *loginIn) {
        NSLog(@"登录结果:%@",loginIn);
                    BOOL success = [loginIn boolValue];
                    if (success) {
                        [self performSegueWithIdentifier:@"Kitten" sender:self];
                    }

    }];

好了 登录demo完成了。
参考文章:
最快让你上手ReactiveCocoa之基础篇
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ==>此文的初始工程
以及译文

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

推荐阅读更多精彩内容