ReactiveCocoa入门教程一

欢迎访问的我个人blog
https://ekulelu.github.io/

ReactiveCocoa框架是Github开源的一个应用于iOS和MacOS开发的新框架,使用它能大幅度改变在“苹果体系”里面的编程习惯。所以如果是有一些iOS编程经验的人来看这个框架的使用方法可能倒会觉得不习惯。这里不想从比较两种编程的区别来引入ReactiveCocoa,因为这样会让人觉得更混乱。希望读者能把iOS的编程套路忘记后来看这篇文章,以免造成不习惯或混乱,毕竟ReactiveCocoa的编程习惯更加接近我们所追求的编程套路。

安装

先讲ReactiveCocoa的安装,顺道给出github地址,注意这个仓库里面有多个分支:
https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v2.5
ReactiveCocoa分为Objective-C和Swift两个不同的语言版本,OC版本的只到V2.5。所以安装的时候不要选错版本。
虽然上面的网址有给出安装的方法,但是不能用。运行script/bootstrap会提示xctool找不到。还是乖乖用cocoapod安装吧。Cocoapod的使用这里不介绍,
在Podfile里面输入

$ pod 'ReactiveCocoa', '2.5'

然后install即可。这样就完成了安装。

入门例子

本篇文章的代码可以在这里下载
https://github.com/Ekulelu/ReactiveCocoaLogin

先通过一个最简单的登录例子来认识ReactiveCocoa,这里只有一个用户名、密码输入框和一个登录按钮。有两个规则:1、只有用户名和密码的长度都大于5的时候,登录按钮才可用。2、登录成功的条件为账户和密码都是aahuang。

先从人的思考方式来考虑第一个规则。这个规则可以这样处理:每当用户名框和密码框有输入的时候,都告诉一下按钮,用户名和密码的长度是否都大于5,其他的事情登录按钮都可以不用去理。
分解一下这个处理方法需要的要素:1、两个事件:用户名框输入事件和密码框输入事件。2、一个信息:用户名和密码的长度都大于5。3、当事件产生的时候,需要把这个信息传递给登录按钮。画图就是这样:

这个图很简单,需要的东西也少。在ReactiveCocoa框架下,我们可以用很少的代码将上面的图描述出来。关键代码如下:

//loginBtn是一个登录按钮
//accountTV是用户名输入框,passwordTV是密码输入框
RAC(self.loginBtn, enabled) = 
      [RACSignal combineLatest:@[self.accountTV.rac_textSignal,
                              self.passwordTV.rac_textSignal]
             reduce:^id{
                 return @(self.accountTV.text.length > 5 &&  
                         self.passwordTV.text.length > 5);
  }];

下面来解析一下上面代码:
rac_textSignal代表文字输入的信号,每当有文字输入事件产生的时候,就会产生“动静”。这个方法是ReactiveCocoa给UITextView和UITextField添加的分类。
有了这两个信号之后,使用combineLatest方法将两个信号连接组合起来,创建一个新的信号,这个新信号当两个旧的信号任意一个有“动静”的时候都会产生“动静”。
这里吐槽一下ReactiveCocoa的命名,它的signal更像我们所说的信号源,而不是信号(值),他的信号里面所包含的值就是我们日常所认为的信号。所以上面所说的“动静”,你可以理解为是这个信号产生了新的值。
在reduce的block里面,可以对信号值做转换,这里直接返回了登录按钮可用条件

self.accountTV.text.length > 5 && self.passwordTV.text.length > 5

然后这个返回值通过RAC(…)这个宏直接绑定到loginBtn的enable属性上面。就这一段的代码就可以完成了规则1的要求,是不是很简单。

这里补充一下combineLasest的规则:
1、必须当combine的所有信号都发出了一个值,reduce才会调用。
2、此后,任何combine的信号只要有值发出,reduce就会调用一次,但这些信号的值都是各个信号最后一次发出的值。

除了combine方法,RAC还有其他合并信号的方法,可以参考这篇博客:http://www.jianshu.com/p/ebf08debb325

规则2的要求涉及到登录功能,这就要响应登录按钮的按下。代码如下:

[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]
        subscribeNext:^(id x) {
        if ([self.accountTV.text isEqualToString:@"aahuang"]
            && [self.passwordTV.text isEqualToString:@"aahuang"]) {
            NSLog(@"login!");
        } else {
            NSLog(@"Invaild account or password ");
        }
}];

下面分析代码:

[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]

这一句从按钮的TouchUpInside事件中产生了一个信号,也就是说只要有按钮被按下,这个信号就会产生值,但如何获取到这个值呢?这里有一个订阅的概念,所谓的订阅和订报纸一样,只要信号有了新值,就会发送给订阅者。(这里说明一下,如果一个信号没有订阅者,这称为冷信号,那么它是不会发送值的。)还有,这里虽然说订阅者,但是RAC把它封装好了,我们需要告诉RAC的是我们需要对这个值做什么事情。所以创建订阅者并请阅信号这件事情变为了给RAC一个block就行了,block的参数就是这个值。这个过程通过调用RACSignal的subscribeNext方法来实现。注意到这里block的参数是id类型,其实可以自己改的。

理一下思路:我们对按钮按下事件做了监听(创建了信号),每当有按钮被按下,就会有新值传给订阅者(调用我们写的block)。但现在按钮按下事件传来的值对我们并没有什么用。所以我们在block里面检查用户名和密码的正确性就行了。
这样满足规则1、2的登录框就完成了。

改进的入门例子

再提一个需求:当用户名或者密码不合法的时候,将输入框的背景颜色变为红色。
这个需求应该很简单,只要将输入框的背景色和输入的信号绑定,然后在每次按钮按下的时候判断输入框的内容是否合法,返回相应的颜色就行了。但是留意到判断输入框是否合法这件事情对登录按钮也有影响,最好把它抽出来复用。但前面我们是用combineLatest:reduce:来将按下事件产生的信号值转换为输入是否合法的值的。对于单个的信号,需要使用map方法来对信号值进行转换。代码如下:

RACSignal *accountValidSignal =
   [self.accountTV.rac_textSignal map:^id(id value) {
       return @(self.accountTV.text.length > 5);//转化为是否合法布尔值
}];

RACSignal *passwordValidSignal =
   [self.passwordTV.rac_textSignal map:^id(id value) {
     return @(self.passwordTV.text.length > 5); //转化为是否合法布尔值
}];

RAC(self.accountTV, backgroundColor) =
   [accountValidSignal map:^id(NSNumber* accountValid) {
  return accountValid.boolValue ? [UIColor whiteColor] : [UIColor redColor]; //转化为UIColor
}];

RAC(self.passwordTV, backgroundColor) =
   [passwordValidSignal map:^id(NSNumber* passwordValid) {
        return passwordValid.boolValue ? [UIColor whiteColor] : [UIColor redColor]; //转化为UIColor
}];

RAC(self.loginBtn, enabled) =
   [RACSignal combineLatest:@[accountValidSignal, passwordValidSignal]
  reduce:^id(NSNumber* accountValid, NSNumber* passwordValid){
        return @(accountValid.boolValue && passwordValid.boolValue);
}];

map方法,用来将信号的值进行转换,在block里面返回一个值进行了。上面的例子里面,将文字输入的信号产生的值转换成了输入内容是否合法的布尔变量。另外在文字背景色的代码上,将输入是否合法的信号值转换为了UIColor返回。
另外需要留意的是reduce的block里面我们添加了参数,一般是你将几个信号进行combine,就有几个参数。
留意到之前写的登录的判断是很简单的,但实际中一般都是异步调用的。我们把登录验证封装为一个方法,里面用延时模拟网络调用:

- (void)signInWithAccount:(NSString *)account password:(NSString *)password complete:(void(^)(Boolean success))completeBlock {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
                                (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
   BOOL success = [account isEqualToString:@"aahuang"]
                 && [password isEqualToString:@"aahuang"];
   completeBlock(success);
 });
}

这个方法可以在按钮按下信号的subscribeNext的block直接调用,但还有另外一种做法,将这个方法包装为一个信号,代码如下:

- (RACSignal*)signInSignal{
    return [RACSignal createSignal:^RACDisposable 
                     *(id<RACSubscriber> subscriber) {
        [self signInWithAccount:self.accountTV.text
              password:self.passwordTV.text
              complete:^(Boolean success){
                 [subscriber sendNext:@(success)];
                 [subscriber sendCompleted];
         }];
        return nil;
}];
}

这里使用了RACSignal 的createSignal方法创建一个信号,这个方法需要一个block,这个block的返回值是一个RACDisposable对象,这个对象允许你在一个订阅被取消时执行一些清理工作,但这里不需要,所以返回nil。这个block会在这个信号被订阅的时候调用,如果这个信号被订阅两次,那么就会调用两次。如何防止这种情况,可以参考下面这篇博客,里面用到一个类叫RACMulticastConnection。
http://blog.csdn.net/sjcode/article/details/48027089

这里还有一个RACSubscriber类,这个类就是订阅者,调用它的sendNext方法,就能执行到订阅之后传入在subscribeNext的block。
另外还有一个sendCompleted方法,这个方法执行后,这个订阅者就再也接收不到信号了,会调用订阅时候的传递给complete的block(这里订阅的时候没有选用带complete的方法,同样还有sendError方法)。
然后我们更改按钮按下信号那段代码如下:

[[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]
      flattenMap:^id(id value) {
        return [self signInSignal];
    }] subscribeNext:^(NSNumber* isLogin) {
        if (isLogin.boolValue) {
            NSLog(@"login!");
        } else {
            NSLog(@"Invaild account or password ");
        }
}];

这里同样是要将信号进行转换,将按钮按下信号转换为登陆的信号,注意,这里使用的是flattenMap方法。这和map方法的区别在于:map方法返回的值返回的是信号的值,它会将该值进行包装成信号,但是flattenMap方法不会将返回的结果进行包装,而是将返回值当做信号。因为这里return [self signInSignal];返回的就是信号了,所以用flattenMap方法。
最后一点细节,登陆按钮在登陆的时候应该是不可用的。其实这个就叫做“副作用”,换句话说就是在一个next事件发生时执行的逻辑,而该逻辑并不改变事件本身。这个通过对信号使用doNext方法实现,修改登陆按钮信号代码如下:

[[[[self.loginBtn  rac_signalForControlEvents:UIControlEventTouchUpInside]
    doNext:^(id x) {
    self.loginBtn.enabled = NO;
    }]
    flattenMap:^id(id value) {
    return [self signInSignal];
    }] 
    subscribeNext:^(NSNumber* isLogin) {
    self.loginBtn.enabled = YES;
    if (isLogin.boolValue) {
        NSLog(@"login!");
    } else {
        NSLog(@"Invaild account or password ");
    }
}];

至此,这个简单的登录框就算完成了,和界面交互的代码量比用iOS框架要少不少,而且思路清晰。下篇文章可以和大家谈谈注册框。

发布了43 篇原创文章 · 获赞 2 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览