美文网首页
ReactiveCocoa 学习笔记(三)

ReactiveCocoa 学习笔记(三)

作者: 丘山Ivan | 来源:发表于2017-11-27 11:10 被阅读53次

    ReactiveCocoa格式

    在写ReactiveCocoa代码的时候,比较推荐的是 每一个操作放到一个新行上上。就是物理行和逻辑行对应起来。此外,尽量简化每个block中的代码量。超过多少行就封装成一个方法调用。在所有代码中都可以使用这种编码格式,这样有利于提高我们的代码可读性。


    image

    内存管理

    ReactiveCocoa的内存管理非常之复杂,但结果却是在使用处理信号时,不需要对该信号进行引用.

    在使用ReactiveCocoa的时候因为没有赋值给任何一个变量和属性,所以它的引用计数不会增加。这是因为ReactiveCocoa的设计目标之一就是允许这样的编程风格,管道可以匿名的形式。

    为了支持这种设计模式,ReactiveCocoa 保留了自己的全局信号。如果全局信号被一个或多个对象订阅,则该信号是激活状态。如果都被删除了,则信号就会被就会被回收。

    那如何取消对信号的订阅呢?一个时间complete或error后,订阅将会自动删除。手动移除可以通过RACDisposable.所有订阅方法RACSignal都返回一个实例RACDisposable,允许您通过dispose方法手动删除订阅.

    RACSignal *backgroundColorSignal =
        [self.searchText.rac_textSignal
          map:^id(id value) {
              return [self isValidSearchText:self.searchText.text] ? [UIColor whiteColor]:[UIColor yellowColor];
          }];
        RACDisposable *subscription =
        [backgroundColorSignal
         subscribeNext:^(UIColor *color) {
             self.searchText.backgroundColor = color;
         }];
        [subscription dispose];
    

    即使ReactiveCocoa的设计模式很巧妙的使我们不用太担心内存管理的问题,但是因为ReactiveCocoa中大量的使用了block,所以这里就或许会存在循环引用的问题。而苹果建议我们使用block的时候建议使用弱引用self。

    所以我们需要修改上面的代码,使用ReactiveCocoa中的一些小技巧(RACEXTScope类中)

     @weakify(self)
        [[self.searchText.rac_textSignal
          map:^id(id value) {
              return [self isValidSearchText:self.searchText.text] ? [UIColor whiteColor]:[UIColor yellowColor];
          }]subscribeNext:^(UIColor *color) {
              @strongify(self)
             self.searchText.backgroundColor = color;
         }];
    

    这就是self衍生的信号的解决办法。一个信号的生命周期与期调用范围联系在一起,这很容易造成循环引用 。
    常见的就是在使用RACObserve()的时候,用到了self作为关键路径,又在订阅代码块中捕获了self。
    打破这种循环引用最简单的方式是捕获 self 的弱引用(weak references)。

    合并信号

    then: 将会触发对原本的信号触发一次订阅,当原本的信号完成时,产生一个新的信号.
    使用doNext:给一个信号注入自定义操作,然后当自定义操作完成时返回一个信号,十分方便.

    - (RACSignal *)requestSignal {
      
      // 1 - 定义错误
      NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain
    code:RWTwitterInstantErrorAccessDenied userInfo:nil];
      
      // 2 - 创建信号
      @weakify(self)
      return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 3 - 请求网络
        @strongify(self)
        [self.viewModel request_POSTNetWork options:nil
          completion:^(id response, NSError *error) {
              // 4 - 处理网络返回数据
              if (error || response["code"].intergerValue!=0 ) {
                //失败
                [subscriber sendError:response["msg"]];
              } else {
                // 成功   
                [subscriber sendNext:response];
                [subscriber sendCompleted];
              }
            }];
        return nil;
      }];
    }
    
    [[[[self requestSignal]
        then:^ RACSignal * {
            @strongify(self)
            return  self.searchText.rac_textSignal;
        }] 
        filter:^ BOOL(NSString * text){ 
            @strongify(self)
             return [text.length> 2]; 
        }] 
        subscribeNext:^(id x){
            NSLog(@"%@",x);
        }error:^(NSError *error){
             NSLog(@"error %@",error);
    }];
        
    

    [self requestSignal]是一个异步网络请求的signal。
    then: 就触发触发 [self requestSignal] 这个信号,然后又把self.searchText.rac_textSignal 这个信号返回到下一步。然后filter:去进行判断text是否符合要求。如果符合,就会继续向下执行subscribeNext:,而subscribeNext:以下的信号是来自[self requestSignal],当[self requestSignal]中触发了sendError(),则就只会执行error:这个block,而不会去执行subscribeNext:block.反之,x就会是[self requestSignal] 这个信号传输出来的sendNext()responseerror:block就不会执行了。

    image
    再举个例子说明 -then:-doNext:
    RACSignal *SignalA = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
    RACSignal *SignalB = 
    [[SignalA doNext:^(NSString *letter) {
        NSLog(@"%@", letter);
    }]
    then:^{
        return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;
    }];
    //订阅SignalB
    [SignalB subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    

    当不订阅SignalB 的时候, SignalA和SignalB都是冷信号,SignalA 中包含了A B C D E F G H I 。只有当订阅了之后才会是热信号。
    订阅之后doNext:block打印的结果是 SignalA信号的东西。然后then:又给SignalB返回一个新的信号。所以B被订阅后会打印的是1 2 3 4 5 6 7 8 9.

    subscribeNext:error:的位置向步骤添加一个断点,你会发现信号所处的线程是异步线程,当我们要更新UI的时候,我们就需要线程间的通讯回到主线程上刷新UI。在ReactiveCocoa有一个更简单的解决方案来解决这个问题。在要刷新的UI的 block 前面加上deliverOn:[RACScheduler mainThreadScheduler]].

    [[[[self requestSignal]
        then:^ RACSignal * {
            @strongify(self)
            return  self.searchText.rac_textSignal;
        }] 
        filter:^ BOOL(NSString * text){ 
            @strongify(self)
             return [text.length> 2]; 
        }]
        flattenMap:^RACStream *(NSString *text) {
        @strongify(self)
        return [self signalForSearchWithText:text];
         }] 
        deliverOn:[RACScheduler mainThreadScheduler]]
        subscribeNext:^(id x){
            NSLog(@"%@",x);
        }error:^(NSError *error){
             NSLog(@"error %@",error);
    }];
    

    TiP:RACScheduler这个类,在不同优先级的线程上传递的选项有很多种,或者在流水线中添加延迟。

    -(RACSignal *)signalForLoadingImage:(NSString *)imageUrl { 
        //创建异步线程
        RACScheduler *scheduler = [RACScheduler 
            schedulerWithPriority:RACSchedulerPriorityBackground]; 
    
        return [[RACSignal createSignal:^RACDisposable *(id subscriber) { 
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 
            UIImage *image = [UIImage imageWithData:data]; 
            [subscriber sendNext:image]; 
            [subscriber sendCompleted]; 
            return nil; 
        }] subscribeOn:scheduler]; 
    }
    
    

    上面方法是我们常用的加载图像的方法,希望不要再主线程上执行。subscribeOn:确保信号在给定的调度程序上执行.

    参考:
    ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2

    相关文章

      网友评论

          本文标题:ReactiveCocoa 学习笔记(三)

          本文链接:https://www.haomeiwen.com/subject/agmfbxtx.html