ReactiveCocoa学习


看了好些天了,老是迷迷瞪瞪的,写下来,比较,分析一下。

这是一些参考网站:基本都是从这些文章上摘录来的。。

http://blog.163.com/l1_jun/blog/static/1438638820142610349839/

 http://www.cocoachina.com/applenews/devnews/2014/0115/7702.html

http://blog.segmentfault.com/erliu/1190000000408492

首先:是一些概念。

reactiveCocoa,是用来统一处理响应的一个框架。网上看来的就是:ative app有很大一部分的时间是在等待事件发生,然后响应事件,比如等待网络请求完成,等待用户的操作,等待某些状态值的改变等等,等这些事件发生后,再做进一步处理。 但是这些等待和响应,并没有一个统一的处理方式。Delegate, Notification, Block, KVO, 常常会不知道该用哪个最合适。有时需要chain或者compose某几个事件,就需要多个状态变量,而状态变量一多,复杂度也就上来了。为了解决这些问题,Github的工程师们开发了ReactiveCocoa。

ReactiveCocoa试图解决什么问题
传统iOS开发过程中,状态以及状态之间依赖过多的问题
传统MVC架构的问题:Controller比较复杂,可测试性差
提供统一的消息传递机制


RACSignal对象捕捉当前和未来的值。信号可以被观察者链接,组合和反应。信号实际上不会执行,直到它被订阅。


RACObserve使用了KVO来监听property的变化,只要观察的值被自己或外部改变,block就会被执行。但不是所有的property都可以被RACObserve,该property必须支持KVO,比如NSURLCache的currentDiskUsage就不能被RACObserve。KVO现在还不了解。MARK一下。。回头看。

 [RACObserve(self, self.testLabel.text) subscribeNext:^(id x) {
        NSLog(@"%@",@"testlabel");
    }];

监听self.testlabel.text,如果text有变化,就执行nslog方法。

代码意思就是观察self.testLabel.text....

//    [RACAble(self.testLabel.text) subscribeNext:^(id x) {
//        NSLog(@"%@",@"testLable");
//    }];   作用和下面这个一样,不知道区别。。但是这个有个警告。


 //把testString,self.testLabel.text观察合并起来,其中有一个值发生变化,就调用reduce中的方法。return的值会传到subscribeNext中。也就是说:number *x的值是SSSS。

次方法就是signal的的合并。

    [[RACSignal combineLatest: @[RACObserve(self, testString),RACObserve(self, self.testLabel.text)]
                      reduce:^id(NSString *string , NSString *fieldString){
                          NSLog(@"str: %@   ,field: %@",string,fieldString);
                          return @"SSSS";
                      }]
    subscribeNext:^(NSNumber *x) {
        NSLog(@"X Class: %@",x);
    }];

//下面这个是对上面的一个运用。返回的值会被付值给loginbutton.enabel。

RAC(self.logInButton, enabled) = [RACSignal 
        combineLatest:@[ 
            self.usernameTextField.rac_textSignal, 
            self.passwordTextField.rac_textSignal, 
            RACObserve(LoginManager.sharedManager, loggingIn), 
            RACObserve(self, loggedIn) 
        ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { 
            return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); 
        }]; 



  UIButton *btn1 =[UIButton buttonWithType:UIButtonTypeInfoDark];
    [btn1 addTarget:self action:@selector(btnClick1:) forControlEvents:UIControlEventTouchUpInside];
    [btn1 setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-10, [UIScreen mainScreen].bounds.size.height/2+20, 20, 20)];

 //按钮被点击的时候会调用下面方法,执行在btnClick1:之后。
    btn.rac_command =[[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSLog(@"按钮被点击,signal");
        return [RACSignal empty];  //为毛返回RACSignal empty啊,有什么用。。。要不是返回空,怎么处理?求教。。


    }]; 


// Notification
[[[NSNotificationCenter defaultCenter]
    rac_addObserverForName:UIKeyboardDidChangeFrameNotification
                    object:nil]
    subscribeNext:^(id x) {
        NSLog(@"键盘Frame改变");
    }
];

signal创建完了,如何获取信号,如何处理信号?

//这是网上看的。

冷信号(Cold)和热信号(Hot)
上面提到过这两个概念,冷信号默认什么也不干,比如下面这段代码
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { 
    NSLog(@"triggered"); 
    [subscriber sendNext:@"foobar"]; 
    [subscriber sendCompleted]; 
    return nil; 
}]; 
 
我们创建了一个Signal,但因为没有被subscribe,所以什么也不会发生。加了下面这段代码后,signal就处于Hot的状态了,block里的代码就会被执行。
[signal subscribeCompleted:^{ 
    NSLog(@"subscription %u", subscriptions); 
}];

//创建信号的另一种方式

  RACSignal *abc =[@"A B, C D" componentsSeparatedByString:@","].rac_sequence.signal; //componentsSeparatedByString:@",“按@”,“对字符进行分割

[abc subscribeNext:^(NSString *x) {
        NSLog(@"abc:%@",x);
    }];

UIView Categories
常用的UIView也都相应的category,比如UIAlertView,就不需要再用Delegate了。

    UIAlertView *alertView =[[UIAlertView alloc]initWithTitle:@"" message:@"" delegate:nil cancelButtonTitle:@"A" otherButtonTitles:@"B",@"C", nil];
    [[alertView rac_buttonClickedSignal] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [alertView show];


网上的:再说一下UITableViewCell,RAC给UITableViewCell提供了一个方法:rac_prepareForReuseSignal,它的作用是当Cell即将要被重用时,告诉Cell。想象Cell上有多个button,Cell在初始化时给每个button都addTarget:action:forControlEvents,被重用时需要先移除这些target,下面这段代码就可以很方便地解决这个问题:

[[[self.cancelButton 
    rac_signalForControlEvents:UIControlEventTouchUpInside] 
    takeUntil:self.rac_prepareForReuseSignal] 
    subscribeNext:^(UIButton *x) { 
    // do other things 
}];


//这个不是很理解。。。。。

NSObject+RACLifting.h
有时我们希望满足一定条件时,自动触发某个方法,有了这个category就可以这么办
- (void)test 

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 
        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){ 
            [subscriber sendNext:@"A"]; 
        }); 
        return nil; 
    }]; 
     
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 
        [subscriber sendNext:@"B"]; 
        [subscriber sendNext:@"Another B"]; 
        [subscriber sendCompleted]; 
        return nil; 
    }]; 
     
    [self rac_liftSelector:@selector(doA:withB:) withSignals:signalA, signalB, nil]; 

 
- (void)doA:(NSString *)A withB:(NSString *)B 

    NSLog(@"A:%@ and B:%@", A, B); 

这里的rac_liftSelector:withSignals 就是干这件事的,它的意思是当signalA和signalB都至少sendNext过一次,接下来只要其中任意一个signal有了新的内容,doA:withB这个方法就会自动被触发

都至少sendnext一次什么意思。。signal怎么有新内容。。原谅我的逗比。。


NSObject+RACSelectorSignal.h
这个category有rac_signalForSelector:和rac_signalForSelector:fromProtocol: 这两个方法。先来看前一个,它的意思是当某个selector被调用时,再执行一段指定的代码,相当于hook。比如点击某个按钮后,记个日志。后者表示该selector实现了某个协议,所以可以用它来实现Delegate。

这个试了几个不知道怎么用。。。

  [[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) {
        NSLog(@"viewDidAppear方法执行完毕,这个是挂钩");
    }];

  [[btn rac_signalForSelector:@selector(setFrame:)] subscribeNext:^(id x) {
        NSLog(@"btn设置Frame");
    }];

这种倒是可以。。


 UIButton *btn =[UIButton buttonWithType:UIButtonTypeInfoLight];
    [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [btn setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-10, [UIScreen mainScreen].bounds.size.height/2-10, 20, 20)];
    [self.view addSubview:btn];
    
    [[btn rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"btnClick挂钩");
    }];

这种就逗比了。。。不能和button添加的按钮事件挂钩么。。。


//创建自己的RACSignal。但是怎么用啊。。。

-(RACSignal *)urlResults {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSError *error;
        NSString *result = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"http://www.devtang.com"]
                                                    encoding:NSUTF8StringEncoding
                                                       error:&error];
        NSLog(@"download");
        if (!result) {
            [subscriber sendError:error];
        } else {
            [subscriber sendNext:result];
            [subscriber sendCompleted];
        }
        return [RACDisposable disposableWithBlock:^{  //返回的不是Signal么,这disposable是什么玩意。。。
            NSLog(@"clean up");
        }];
    }];

}


Mapping

-map:方法用来改变流中的值并用结果创建一个新的流:

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;

// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped = [letters map:^(NSString *value) {
    return [value stringByAppendingString:value];
}];


Filtering

filter:方法用一个block来判断(test)每一个值,如果判断通过则把这个值加入到结果的流(resulting stream)中:

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

// Contains: 2 4 6 8
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
    return (value.intValue % 2) == 0;
}];


Concatenating

-concat:方法将一个流中的值加到另一个中:

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];


Combining latest values

+combineLatest:以及+combineLatest:reduce:方法会观察(watch)多个信号的变化,然后在一个变化发生的时候向那些信号发送最新的值。

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
combineLatest:@[ letters, numbers ]
   reduce:^(NSString *letter, NSString *number) {
   return [letter stringByAppendingString:number];
}];

// Outputs: B1 B2 C2 C3
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];
注意:组合的信号(combined signal)只会在所有的输入至少都有一个值的时候才会发送它的第一个值,比如上面代码中@"A"没有被输出因为numbers还没有收到一个值。

RAC在应用中大量使用了block,由于Objective-C语言的内存管理是基于引用计数 的,为了避免循环引用问题,在block中如果要引用self,需要使用@weakify(self)和@strongify(self)来避免强引用。另外,在使用时应该注意block的嵌套层数,不恰当的滥用多层嵌套block可能给程序的可维护性带来灾难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值