ReactiveCocoa 学习笔记(三)

2017-11-27  本文已影响53人  丘山Ivan

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

上一篇下一篇

猜你喜欢

热点阅读