ReactiveCocoa项目实战例子

图片发自简书App

看完文档后,似乎方法都知道怎么回事儿,但是应用到项目上就无从下手,这篇文章就是来说一说项目实战的例子。本文就综合网上的文章和我平时遇到的问题来一一梳理一下,有一部分会是从其他地方引用而来,我会在文章下方说明出处。

实战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

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

推荐阅读更多精彩内容