iOS开发iOS开发程序员

ReactiveCocoa项目实战例子

2016-02-19  本文已影响1493人  楼上那位
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;

后续的再补充吧...

参考:
http://www.jianshu.com/p/e10e5ca413b7

上一篇下一篇

猜你喜欢

热点阅读