RAC的冷信号和热信号
本篇目录
- 何为冷信号,热信号;
- 为何会有
RACSubject
这个类的存在; -
RACSubject
已经其子类的介绍; - 如何将一个冷信号转成热信号。
何为冷信号,热信号?
冷信号:
RAC中的RACSiganl
类中几乎都是冷信号,以+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
方法为例:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
初始化一个RACDynamicSignal
保存一个block,等到有订阅者去订阅,如果没有订阅者的话,一直不会被调用,也就是冷信号被调用的前提是要有订阅者,这就是冷信号;
热信号:
解释完冷信号,那热信号肯定是即使没有订阅者也是会发送消息的,是的rac提供了RACSubject
,专门处理热信号的。
为何会有RACSubject
这个类的存在?
要想解释为什么会存在RACSubject
这个类,首先来一个场景
self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]];
self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
@weakify(self)
RACSignal *fetchData = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
@strongify(self)
NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id responseObject) {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[subscriber sendError:error];
}];
return [RACDisposable disposableWithBlock:^{
if (task.state != NSURLSessionTaskStateCompleted) {
[task cancel];
}
}];
}];
RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
if ([value[@"title"] isKindOfClass:[NSString class]]) {
return [RACSignal return:value[@"title"]];
} else {
return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
}
}];
RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
if ([value[@"desc"] isKindOfClass:[NSString class]]) {
return [RACSignal return:value[@"desc"]];
} else {
return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
}
}];
RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) {
NSError *error = nil;
RenderManager *renderManager = [[RenderManager alloc] init];
NSAttributedString *rendered = [renderManager renderText:value error:&error];
if (error) {
return [RACSignal error:error];
} else {
return [RACSignal return:rendered];
}
}];
RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];
我们再开发中经常会根据服务器返回的数据解析不同的字段进行不同的操作,上面写的情况在开发中很常见,如果用Charles抓取的话,其实发送了3次请求,都知道这个很不好,我们来分析一下为什么会调用3次呢,我们知道在下面三行代码执行前
RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];
fetchData
、title
、desc
、renderedDesc
都是冷信号,当调用上面三行代码的时候fetchData
、title
、desc
、renderedDesc
都变成了热信号了,而title
、desc
、renderedDesc
信号内部都对fetchData
信号进行了订阅,从而导致源信号被订阅了3次,而实际上我们只要发送一次网络请求就可以了,即对源信号只有订阅一次就够了。
那我们如何做到对源信号只订阅一次呢,RACSubject
的存在就是解决这个问题的。
RACSubject
闪亮登场
借用一下美团团队提供的一个例子
RACSubject *subject = [RACSubject subject];
RACSubject *replaySubject = [RACReplaySubject subject];
[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
// Subscriber 1
[subject subscribeNext:^(id x) {
NSLog(@"Subscriber 1 get a next value: %@ from subject", x);
}];
[replaySubject subscribeNext:^(id x) {
NSLog(@"Subscriber 1 get a next value: %@ from replay subject", x);
}];
// Subscriber 2
[subject subscribeNext:^(id x) {
NSLog(@"Subscriber 2 get a next value: %@ from subject", x);
}];
[replaySubject subscribeNext:^(id x) {
NSLog(@"Subscriber 2 get a next value: %@ from replay subject", x);
}];
}];
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subject sendNext:@"send package 1"];
[replaySubject sendNext:@"send package 1"];
}];
[[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{
// Subscriber 3
[subject subscribeNext:^(id x) {
NSLog(@"Subscriber 3 get a next value: %@ from subject", x);
}];
[replaySubject subscribeNext:^(id x) {
NSLog(@"Subscriber 3 get a next value: %@ from replay subject", x);
}];
// Subscriber 4
[subject subscribeNext:^(id x) {
NSLog(@"Subscriber 4 get a next value: %@ from subject", x);
}];
[replaySubject subscribeNext:^(id x) {
NSLog(@"Subscriber 4 get a next value: %@ from replay subject", x);
}];
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subject sendNext:@"send package 2"];
[replaySubject sendNext:@"send package 2"];
}];
输出
2017-07-03 18:16:02.811 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 1 from subject
2017-07-03 18:16:02.812 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 1 from subject
2017-07-03 18:16:02.812 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 1 from replay subject
2017-07-03 18:16:02.813 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 1 from replay subject
2017-07-03 18:16:02.923 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 1 from replay subject
2017-07-03 18:16:02.923 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 1 from replay subject
2017-07-03 18:16:03.911 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 2 from subject
2017-07-03 18:16:03.911 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 2 from subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 2 from subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 2 from subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.913 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 2 from replay subject
解释一下这个结果
先分别创建两个信号对象
RACSubject
和RACReplaySubject
,0.1s分别有两个订阅者分别对其订阅;-
1s后发送消息,
-
对于
subject
有两个订阅者
Subscriber 1
和Subscriber 2
,所以会先给订阅者发送消息从而有
-
```
2017-07-03 18:16:02.811 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 1 from subject
2017-07-03 18:16:02.812 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 1 from subject
```
`subject`内部有个数组会保存订阅者,也就是此时`subject`内部已经有了2个订阅者了
2. 对于 `replaySubject`
也有两个订阅者`Subscriber 1`和`Subscriber 2`,但是她和`subject`不同,它在订阅的时候会首先判断内部之前有没有发送过值,如果有则发送给订阅者
```
2017-07-03 18:16:02.812 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 1 from replay subject
2017-07-03 18:16:02.813 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 1 from replay subject
```
发完以后,`replaySubject`的内部会保存发送的值,也就是`send package 1`,且有两个订阅者。
相信到这里大家没什么问题
-
1.1s后又多了两个订阅者,
Subscriber 3
和Subscriber 4
;对于
subject
,只是保存订阅者;-
而当程序走到下面这行的时候
``` [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 3 get a next value: %@ from replay subject", x); }]; ```
replaySubject
会首先发送之前保存的值也就是send package 1
,从而有了2017-07-03 18:16:02.923 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 1 from replay subject
然后
subject
又多了一个订阅者,保存起来;到这行的时候
```
[replaySubject subscribeNext:^(id x) {
NSLog(@"Subscriber 4 get a next value: %@ from replay subject", x);
}];
```
此时的`replaySubject`有一个值`send package 1`,所以有了
```
2017-07-03 18:16:02.923 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 1 from replay subject
```
- 2s后
subject
被订阅,此时subject
的订阅者有4个从而
```
2017-07-03 18:16:03.911 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 2 from subject
2017-07-03 18:16:03.911 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 2 from subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 2 from subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 2 from subject
```
`replaySubject`也有4个订阅者
```
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 1 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 2 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.912 TestObjectProduct[90499:11197356] Subscriber 3 get a next value: send package 2 from replay subject
2017-07-03 18:16:03.913 TestObjectProduct[90499:11197356] Subscriber 4 get a next value: send package 2 from replay subject
```
总结:
-
RACSubject
这个类会保存所有的订阅者,一旦被订阅,就会保存订阅者,等待有值发出的时候,就会告诉所有的订阅者; -
RACReplaySubject
,是继承RACSubject
的所以和RACSubject
一样会保存订阅者,然后比父类不同的是它还会保存所有发送过的值,供有新订阅者订阅的时候发送它漏过的值。
如何将一个冷信号转成热信号。
热信号一般在开发中充当的角色如下图所示:
所谓的热信号就是使用RACSubject
对象订阅源信号,而其他的订阅者订阅RACSubject
按照上图的理解就有下面的例子
- (void)testSubjectMul {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendCompleted];
return nil;
}];
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了");
}];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了");
}];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了");
}];
[signal subscribe:subject];
}
输出
2017-07-04 22:01:14.443 TestObjectProduct[25044:12106160] 源信号被订阅了
2017-07-04 22:01:14.443 TestObjectProduct[25044:12106160] 订阅者1订阅了
2017-07-04 22:01:14.445 TestObjectProduct[25044:12106160] 订阅者2订阅了
2017-07-04 22:01:14.445 TestObjectProduct[25044:12106160] 订阅者3订阅了
可以看出源信号只被订阅了一次
- 首先创建源信号;
- 再创建一个RACSubject信号;
- 再对
RACSubject
信号进行订阅 - 最后
RACSubject
信号对源信号进行订阅。
当然RACSubject
有提供将冷信号变成热信号的遍历方法
- (RACMulticastConnection *)publish;
- (RACMulticastConnection *)multicast:(RACSubject *)subject ;
- (RACSignal *)replay ;
- (RACSignal *)replayLast ;
- (RACSignal *)replayLazily
我们针对上面的demo分别使用一下
- (RACMulticastConnection *)publish
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
这个方法没有主动connect,一般和- (RACSignal *)autoconnect
搭配使用,每当autoconnect
这个信号订阅的时候触发源信号被订阅;
- (void)testSubjectMul {
RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendCompleted];
return nil;
}] publish] autoconnect];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了");
}];
}
输出
2017-07-04 22:08:05.356 TestObjectProduct[25121:12113364] 源信号被订阅了
2017-07-04 22:08:05.356 TestObjectProduct[25121:12113364] 订阅者1订阅了
这里为什么只会有订阅者1执行了,是因为autoconnect
只允许connect
一次,后面执行的只是保存了订阅者,以便下次执行使用。
- (RACMulticastConnection *)multicast:(RACSubject *)subject
- (void)testSubjectMul {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendCompleted];
return nil;
}];
RACSubject *subject = [RACSubject subject];
RACMulticastConnection *connect = [signal multicast:subject];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了");
}];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了");
}];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了");
}];
[connect connect];
}
输出
017-07-04 22:12:26.846 TestObjectProduct[25180:12117807] 源信号被订阅了
2017-07-04 22:12:26.846 TestObjectProduct[25180:12117807] 订阅者1订阅了
2017-07-04 22:12:26.846 TestObjectProduct[25180:12117807] 订阅者2订阅了
2017-07-04 22:12:26.846 TestObjectProduct[25180:12117807] 订阅者3订阅了
- (RACSignal *)replay
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendCompleted];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了");
}];
}
输出
2017-07-04 22:14:22.805 TestObjectProduct[25224:12120354] 源信号被订阅了
2017-07-04 22:14:22.805 TestObjectProduct[25224:12120354] 订阅者1订阅了
2017-07-04 22:14:22.806 TestObjectProduct[25224:12120354] 订阅者2订阅了
2017-07-04 22:14:22.806 TestObjectProduct[25224:12120354] 订阅者3订阅了
这个一调用replay
的时候就对源信号订阅了,只不过当时没有订阅者,由于内部使用的是RACReplaySubject
对象,此时会保存发送的值,等到订阅者订阅的时候就会发送保存的值了
比如稍微改一下
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendNext:@"假设这是网络"];
[subscriber sendCompleted];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了");
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了");
}];
}
就会输出
2017-07-04 22:17:06.612 TestObjectProduct[25277:12124611] 源信号被订阅了
2017-07-04 22:17:06.613 TestObjectProduct[25277:12124611] 订阅者1订阅了
2017-07-04 22:17:06.613 TestObjectProduct[25277:12124611] 订阅者1订阅了
2017-07-04 22:17:06.613 TestObjectProduct[25277:12124611] 订阅者2订阅了
2017-07-04 22:17:06.613 TestObjectProduct[25277:12124611] 订阅者2订阅了
2017-07-04 22:17:06.614 TestObjectProduct[25277:12124611] 订阅者3订阅了
2017-07-04 22:17:06.614 TestObjectProduct[25277:12124611] 订阅者3订阅了
- (RACSignal *)replayLast
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendNext:@"假设这是另一个网络"];
[subscriber sendCompleted];
return nil;
}] replayLast];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了--%@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者2订阅了--%@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者3订阅了--%@",x);
}];
}
输出
2017-07-04 22:19:52.137 TestObjectProduct[25395:12129467] 源信号被订阅了
2017-07-04 22:19:52.138 TestObjectProduct[25395:12129467] 订阅者1订阅了--假设这是另一个网络
2017-07-04 22:19:52.138 TestObjectProduct[25395:12129467] 订阅者2订阅了--假设这是另一个网络
2017-07-04 22:19:52.138 TestObjectProduct[25395:12129467] 订阅者3订阅了--假设这是另一个网络
这个和replay
类似,唯一的不同就是replay
保存所以发送过的值,而它保存的是最新的发送的值
- (RACSignal *)replayLazily
这个要和replay
对比看,replay
方法是即使没有订阅者,源信号也会被订阅,而replayLazily
只有当返回的信号被订阅的时候源信号才会被订阅,先看replay
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendNext:@"假设这是另一个网络"];
[subscriber sendCompleted];
return nil;
}] replay];
输出
2017-07-04 22:24:02.298 TestObjectProduct[25558:12136590] 源信号被订阅了
再看replayLazily
,先看她没有被订阅
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendNext:@"假设这是另一个网络"];
[subscriber sendCompleted];
return nil;
}] replayLazily];
}
输出
无
再看被订阅了
- (void)testSubjectMul {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"源信号被订阅了");
[subscriber sendNext:@"假设这是网络"];
[subscriber sendNext:@"假设这是另一个网络"];
[subscriber sendCompleted];
return nil;
}] replayLazily];
[signal subscribeNext:^(id x) {
NSLog(@"订阅者1订阅了--%@",x);
}];
}
输出
2017-07-04 22:26:56.527 TestObjectProduct[25601:12139422] 源信号被订阅了
2017-07-04 22:26:56.528 TestObjectProduct[25601:12139422] 订阅者1订阅了--假设这是网络
2017-07-04 22:26:56.528 TestObjectProduct[25601:12139422] 订阅者1订阅了--假设这是另一个网络
说道这里我们可以修改一下开始那个网络请求了
self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]];
self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
@weakify(self)
RACSignal *fetchData = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
@strongify(self)
NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id responseObject) {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[subscriber sendError:error];
}];
return [RACDisposable disposableWithBlock:^{
if (task.state != NSURLSessionTaskStateCompleted) {
[task cancel];
}
}];
}] replayLazily];//只要加一个这个就可以了
RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
if ([value[@"title"] isKindOfClass:[NSString class]]) {
return [RACSignal return:value[@"title"]];
} else {
return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
}
}];
RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {
if ([value[@"desc"] isKindOfClass:[NSString class]]) {
return [RACSignal return:value[@"desc"]];
} else {
return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];
}
}];
RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) {
NSError *error = nil;
RenderManager *renderManager = [[RenderManager alloc] init];
NSAttributedString *rendered = [renderManager renderText:value error:&error];
if (error) {
return [RACSignal error:error];
} else {
return [RACSignal return:rendered];
}
}];
RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];
RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];