一些常用的RACSignal
如果你没有听说和使用过ReactiveCocoa
框架,请阅读sunnyxx写的入门教程。
本文将罗列一些常用的RACSignal方法,并会不断更新。
RAC()和RACObserve()
RAC(<#TARGET, ...#>)
宏用来将一个对象的属性和信号量绑定,RACObserve(<#TARGET#>, <#KEYPATH#>)
宏则用来生成一个对象的绑定属性的信号量,
这样描述很抽象,上一个例子解释
@property (nonatomic, strong) NSString* testText;
@property (nonatomic, strong) NSString* testText_2;
RAC(self,testText) = RACObserve(self, testText_2);
这个例子中,testText_2属性有任何变动,都会通知给testText变成一样的内容,testText变化不会影响testText_2。RAC宏在等号左边,右边RACObserve宏返回一个传值信号量。
这个用法十分宽泛,可以省去自己添加KVO或添加分散且复杂的绑定代码,并且逻辑更直观。
RACSingal
接着我们讲讲RACSignal
这个信号量类,他的基类是RACStream
,从名字看出,"信号量类"继承自"流类",所以RACSignal
支持一些高级,如:flattenMap
,flatten
,map
等等,您看不会用也没关系,本篇文章我们用不到这些函数。
这些函数的用法请参考使用ReactiveCocoa实现iOS平台响应式编程博文,此文中使用的RACSequence
类和RACSingal
同样继承自RACStream
,是RAC
框架的数组类,RACSequence
和NSArray
转换也十分简单:
NSArray* array = @[@(1),@(2),@(3),@(4),@(5)];
RACSequence* sequence = [array rac_sequence]; //NSArray -> RACSequence
NSArray* array2 = [sequence array]; //RACSequence -> NSArray
既然RACSignal
和RACSequence
都是"流"类,那肯定有共同的特性。RACSequence
队列,是很形象的流,例如人流车流都是队列。RACSingal
则抽象一点,所谓信号,也是一种流,我们用代码来描述。
RACSignal* getRandomSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@(arc4random())];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
}];
}];
[getRandomSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[getRandomSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
2015-12-29 10:28:20.611 Fahu[967:365178] 707130803
2015-12-29 10:28:20.613 Fahu[967:365178] 743304158
我们的getRandomSignal
信号,是一个用来获取随机数的信号量,用类方法createSignal
初始化,subscriber
则是这个信号量的订阅者,当有人订阅这个信号量时,[subscriber sendNext:@(arc4random())];
信号量返回一个随机数给订阅者,注意这个createSignal
的Block,只有在有人订阅时才会执行。
订阅的操作就是信号量调用subscribeNext
方法,一次订阅会返回一个值,再订阅一次,返回了一个新的随机数,如上方LOG。
代码并不难懂,出于讲解需要,写了这么一个例子,并不是很合理。应为直接调用arc4random
就可以获取随机数,没必要写这么麻烦把它包装成信号量。但随着一项任务的复杂程度增高,信号量写法便会展示出更清晰的逻辑。
举个例子来源于ReactiveCocoa的开源项目,天气类软件Tropos。代码中包含了一些高级函数,请根据注释来理解
先普及一下CLLocationManager
的回调,这个位置管理器类,开始定位以后,会调用locationManager:didUpdateLocations:
和locationManager:didFailWithError:
两个回调,前者用来返回定位数据NSArray
,后者用来返回错误。
//返回一个用来更新地理位置的信号量
- (RACSignal *)updateCurrentLocation
{
//已知[self didUpdateLocations]是locationManager:didUpdateLocations:回调的信号量,这个回调返回定位的GPS信息。[self didUpdateLocations]返回的GPS位置数组信号量,进行map操作,map操作简单的说就是遍历+处理,[self didUpdateLocations],每每返回一个位置数组,这一步的map函数都将其变为数组的最后一个对象输出,然后再用filter函数过滤,把请求时间超时的数据去掉,isStale是一个私有属性,这里就不粘出来了。
RACSignal *currentLocationUpdated = [[[self didUpdateLocations] map:^id(NSArray *locations) {
return locations.lastObject; //返回最后一个对象
}] filter:^BOOL(CLLocation *location) {
return !location.isStale; //过滤超时对象
}];
//map函数返回id类型,用来替换。filter函数返回 BOOL值,决定要不要过滤掉,返回NO就过滤。
//locationManager:didFailWithError:的信号量
RACSignal *locationUpdateFailed = [[[self didFailWithError] map:^id(NSError *error) {
return [RACSignal error:error];
}] switchToLatest];
//最后返回的信号量,是上面两股信号量的merge(混合),take:1是不管这两个信号那一个先来,只返回一个,initially是信号量开始时候调用的block,finally则是信号量结束了调用的block。
return [[[[RACSignal merge:@[currentLocationUpdated, locationUpdateFailed]] take:1] initially:^{
[self.locationManager startUpdatingLocation];
}] finally:^{
[self.locationManager stopUpdatingLocation];
}];
}
不知道您感觉如何,相对于传统的delegate
方法使用CLLocationManager
,RAC
信号量是不是让整个获取逻辑更加清晰明了,用一个函数完成了之前最少两段控制代码和两个回调的工作。
rac_signalForSelector
再让我们看看上段代码中的didUpdateLocations
信号量怎么来的
- (RACSignal *)didUpdateLocations
{
return [[self rac_signalForSelector:@selector(locationManager:didUpdateLocations:) fromProtocol:@protocol(CLLocationManagerDelegate)] reduceEach:^id(CLLocationManager *manager, NSArray *locations) {
return locations;
}];
}
self
是CLLocationManagerDelegate
,是CLLocationManager
的委托,通过rac_signalForSelector
生成了@selector(locationManager:didUpdateLocations:)
的信号量,这个信号量在被订阅后,会在locationManager:didUpdateLocations:
回调调用的时候,向订阅者发出信号。并且使用reduceEach
函数把回调中多余的信息删除了,只返回了位置信息。
rac_signalForSelector
这个方法,既可以返回普通Selector的信号量,也可以像上例中返回代理方法的信号量,十分好用。
RACSignal类簇
RACSignal
的设计模式,就是标准的类簇模式,不同的Signal都是继承于RACSignal的子类,但是都封装起来不用开发者关注,和NSArray、UIButton的设计模式相同。
例如下面这个简单的返回定位状态授权的信号量,使用的[RACSignal return:@(authorized)]
生成的便是RACReturnSignal
类信号量。
- (RACSignal *)authorized
{
BOOL authorized = [self authorizationStatusEqualTo:kCLAuthorizationStatusAuthorizedWhenInUse] || [self authorizationStatusEqualTo:kCLAuthorizationStatusAuthorizedAlways];
return [RACSignal return:@(authorized)];
}
+ (RACSignal *)return:(id)value {
return [RACReturnSignal return:value];
}