ReactiveCocoa格式
在写ReactiveCocoa代码的时候,比较推荐的是 每一个操作放到一个新行上上。就是物理行和逻辑行对应起来。此外,尽量简化每个block中的代码量。超过多少行就封装成一个方法调用。在所有代码中都可以使用这种编码格式,这样有利于提高我们的代码可读性。
内存管理
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)
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()
的response
。error:
block就不会执行了。
再举个例子说明
-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