ReactiveCocoa项目实战例子
看完文档后,似乎方法都知道怎么回事儿,但是应用到项目上就无从下手,这篇文章就是来说一说项目实战的例子。本文就综合网上的文章和我平时遇到的问题来一一梳理一下,有一部分会是从其他地方引用而来,我会在文章下方说明出处。
实战1:网络下载图片完成后 按钮才可以点击
-(void)btnAvliableWhenImgOK{
// 观察img 是否修改,如果修改就会触发
RACSignal * imagAvaibaleSignal = [RACObserve(self, self.imageView.image) map:^id(id value) {
return value ? @YES : @NO;
}];
[imagAvaibaleSignal subscribeNext:^(id x) {
NSLog(@"xx =%@",x);
}];
self.shareBtn.rac_command = [[RACCommand alloc] initWithEnabled:imagAvaibaleSignal signalBlock:^RACSignal *(id input) {
// do share logic
NSLog(@"input =%@",input);
return [RACSignal empty];// 必须返回一个信号,不能返回nil
}];
// 一个command 需要execute 才能触发执行 但是和btn绑定的command不需要
// [self.shareBtn.rac_command execute:@"100"];
/*
2016-02-19 11:14:25.359 JFReactive[26455:2201124] xx =0
2016-02-19 11:14:37.216 JFReactive[26455:2201124] xx =1
2016-02-19 11:16:21.597 JFReactive[26455:2201124] input =<UIButton: 0x7f9d2bd6fc60; frame = (93 330; 151 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7f9d2bd6bcc0>> */
}
我们使用RACObserver()
观察self.imgView.img,然后使用map操作如果有值则返回yes,否则返回no,接下来我们使用RACCommand
使用imgAvailableSigna作为参数初始化一个RACCommand
并赋值给shareBtn.rac_command.
在运行上述代码是img为nil 所以shareBtn enable = NO
在另外一个方法中下载图片 使得 self.imgView.img = img, shareBtn的enable = yes
实现2: 使用rac_signalForSelector 实现协议方法
当selector 执行完后会发送next事件
[[self rac_signalForSelector:@selector(scrollViewDidEndDecelerating:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {
// do something
}];
[[self rac_signalForSelector:@selector(scrollViewDidScroll:) fromProtocol:@protocol(UIScrollViewDelegate)] subscribeNext:^(RACTuple *tuple) {
// do something
}];
实战3: 网络请求失败后再发起一次请求
一般情况下,我们会遇到网络请求失败,但是失败的原因有很多,总之我们还想再试一次,怎么办呢?按照传统的逻辑要定义一个标签,如果成功则返回该标签的值为yes,否则返回no,再次发起请求。好麻烦是不是,如果是RAC,就简单了很多
-(void)retry{
__block int flag = 0;
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (flag == 4){
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}else{
flag ++;
NSLog(@"flag= %d",flag);
[subscriber sendError:[NSError errorWithDomain:@"myerror " code:100 userInfo:nil]];
}
return nil;
}];
[[signal retry:5]subscribeNext:^(id x) {
NSLog(@"xxxx =%@",x);
}];
}
是不是很简单,retry(count) 可以指定任意数字,直到我们获取正确的结果或者到达指定的count次数
实战4:发送请求发现lostToken了
例如在请求我的投资数据(reqInvestAPI)发现token过期了,传统的做法是在发送请求之前先去请求token(reqTokenAPI),等token回来后再发reqInvestAPI,噢,LadyGaga,你好吗?
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// suppose first time send request, access token is expired or invalid
// and next time it is correct.
// the block will be triggered twice.
static BOOL isFirstTime = 0;
NSString *url = @"http://httpbin.org/ip";
if (!isFirstTime) {
url = @"http://nonexists.com/error";
isFirstTime = 1;
}
NSLog(@"url:%@", url);
[[AFHTTPRequestOperationManager manager] GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
NSLog(@"subscriber sendcompleted");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"subscriber send error");
[subscriber sendError:error];
}];
return nil;
}];
self.labelForName.text = @"sending request...";
//Subscribes to the returned signal when an error occurs.
[[requestSignal catch:^RACSignal *(NSError *error) {// requestSignal 发送error 触发 catch{} catch 中返回的signal 发送next 在subcribeNext接收后,再追加一次requestSignal
self.labelForName.text = @"oops, invalid access token";
NSLog(@"catch ....");
// 模拟获取token的请求,然后concat requestSignal,再次发送之前的请求
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
double delayInSeconds = 1.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:@YES];
NSLog(@"subscriber sendNext...");
[subscriber sendCompleted];
});
return nil;
}]concat:requestSignal];
}] subscribeNext:^(id x) {
NSLog(@"next =%@",x);
if ([x isKindOfClass:[NSDictionary class]]) {
self.labelForName.text = [NSString stringWithFormat:@"result:%@", x[@"origin"]];
}
} completed:^{
NSLog(@"completed");
}];
我们先创建了一个requestSignal,在这个signal中我们会先发送一次失败的请求,然后被catch,catch方法中返回一个新的信号会被重新订阅,在该信号中模拟网络请求获取token,然后再改请求token的signal中concat之前的信号(相当于在此发送之前的请求)
实战5:根据搜索框的文字进行实时搜索
我们在用tmall 和 京东的app,会发现搜索框的结果会根据输入的文字动态更新,这个放到我们的实际需求中会发现,只要有用户输入的文字进行改变我们就去发送请求,当用户前后两次输入间隔很短,我们发送了两次请求,之前的请求还没返回下一次的已经发送了,这势必会造成服务器的压力,另外第一次的请求也要抛弃掉。如果放到RAC
该如何处理呢?
[[[[[[self.textField.rac_textSignal throttle:1]distinctUntilChanged]ignore:@""] map:^id(id value) {
NSLog(@"value =%@",value);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// network request
[subscriber sendNext:value];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// cancel request
}];
}];
}]switchToLatest] subscribeNext:^(id x) {// 如果不switchToLastest 则返回一个signal
NSLog(@"x = %@",x);
}];
我们在map中根据输入框的值 模拟发送网络请求。
throttle的参数是一个NSTimerInternal,指定一个时间间隔。
查看该方法的文档知道,在interval间隔内如果是已经接收到下一个next 事件,就会抛弃前一个事件。
在这里我们设置间隔为1s,distinctchanged方法是检测前后两次事件的值是否改变,如果改变才会触发接下来的事件
实战6: 多个信号组合
RACSignal * singal = [RACSignal
combineLatest:@[ RACObserve(self, self.model.age), RACObserve(self, self.model.name) ]
reduce:^(NSString *password, NSString *passwordConfirm) {
return @([passwordConfirm isEqualToString:password]);
}];
[singal subscribeNext:^(id x) {
NSLog(@"xx =%@",x) ;
}];
/* // 如果是直接RAC()的话 自动进行了subscribeNext:
RAC(self, self.textField.enabled) =[RACSignal
combineLatest:@[ RACObserve(self, self.model.age), RACObserve(self, self.model.name) ]
reduce:^(NSString *password, NSString *passwordConfirm) {
return @([passwordConfirm isEqualToString:password]);
}];
*/
实战7:使用Command 模拟登录
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
//模拟login signal
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1000"];
[subscriber sendCompleted];
return nil;
}];
}];
// -executionSignals returns a signal that includes the signals returned from
// the above block, one for each time the command is executed.
[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
// Log a message whenever we log in successfully.
[loginSignal subscribeNext:^(id x) {
NSLog(@"xxx %@",x);
}];
[loginSignal subscribeCompleted:^{
NSLog(@"Logged in successfully!");
}];
}];
// [self.loginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
// NSLog(@"xx =%@",x);
// }];
// Executes the login command when the button is pressed. 按钮点击触发
self.shareBtn.rac_command = self.loginCommand;
后续的再补充吧...