ReactiveCocoa|ReactiveCocoa 学习笔记(三)

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

ReactiveCocoa|ReactiveCocoa 学习笔记(三)
文章图片
image 内存管理
ReactiveCocoa的内存管理非常之复杂,但结果却是在使用处理信号时,不需要对该信号进行引用.
在使用ReactiveCocoa的时候因为没有赋值给任何一个变量和属性,所以它的引用计数不会增加。这是因为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 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) returnself.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就不会执行了。

ReactiveCocoa|ReactiveCocoa 学习笔记(三)
文章图片
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) returnself.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

    推荐阅读