ReactiveCocoa 是一个符合 FRP(Functional Reactive Programming)范式的,操作和转换数据流的框架。
它可以解决三个问题:
开发过程中关于界面的状态信息太多,不易维护。
统一各种消息传递的方式。
绑定功能可以配合 MVVM 使用。
基本概念
简而言之,Singal 以 stream 的方式将 event 发送给 Observer。
Event
Event 代表值发生变化,或者有用户操作,分为 4 种:
Next:signal 产生了新的值。
Failed:signal 执行发生了错误,不再产生新的值。
Completed:signal 执行结束,不再产生新的值。
Interrupted:signal 被中止,不再产生新的值。
Singal
Signal 类似于一个 event 源头,event 通过 signal 传递。用户只能通过 subscribe 来开始有序地、被动地获取 signal 里面的值。一一个 signal 的生命周期通常是一个或多个 Next event,然后以 Failed、Completed 和 Interrupted 中的一种 event 结束,不受 subscriber 的影响。
用户可以使用基本操作符对 Signal 进行一些操作,如过滤(filter)、映射(map)等。
Observer
通过 subscribe 一个 signal 来接受 event 的对象。
RACCommand
通常用来执行一个 UI 事件发生后要处理的任务。
RACSubject
一个 signal 的子类,是一个可以手动发送 Next、Failed 等事件的 signal。通常用来将 non-RAC 的代码 bridge 成 RAC 的代码。
基本操作
添加副作用
-subscribe...:
用来订阅一个 signal 的 Next、Failed、Completed 和 Interrupted 事件中的一种或几种:
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;// Outputs: A B C D E F G H I[letters subscribeNext:^(NSString *x) {NSLog(@"%@", x);}];
-do...:
用来添加副作用,当 Next、Failed、Completed 和 Interrupted 事件中的一种发生时执行 do... 中的 block。
__block unsigned subscriptions = 0;RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {subscriptions++;[subscriber sendCompleted];return nil;}];// Does not output anything yetloggingSignal = [loggingSignal doCompleted:^{NSLog(@"about to complete subscription %u", subscriptions);}];// Outputs:// about to complete subscription 1// subscription 1[loggingSignal subscribeCompleted:^{NSLog(@"subscription %u", subscriptions);}];
转换 stream
-map:
将一个 stream 中的值转换成其他的值,组成一个新的 stream。
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;// Contains: AA BB CC DD EE FF GG HH IIRACSequence *mapped = [letters map:^(NSString *value) {return [value stringByAppendingString:value];}];
-filter:
用来过滤 stream 中的值,传入的 block 返回一个 BOOL,来决定是否将值放入新的 stream 中。
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;// Contains: 2 4 6 8RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {return (value.intValue % 2) == 0;}];
组合 stream
-concat:
将一个 stream 的值追加到另一个 stream 的值后面。
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9RACSequence *concatenated = [letters concat:numbers];
-flatten:
如果一个 signal 的 stream 中的值也是一个 stream,-flatten: 会将子 stream 中的值取出,组成一个新的 stream。组合的效果可能是 concat:
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9RACSequence *flattened = [sequenceOfSequences flatten];
或者 merge:
RACSubject *letters = [RACSubject subject];RACSubject *numbers = [RACSubject subject];RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {[subscriber sendNext:letters];[subscriber sendNext:numbers];[subscriber sendCompleted];return nil;}];RACSignal *flattened = [signalOfSignals flatten];// Outputs: A 1 B C 2[flattened subscribeNext:^(NSString *x) {NSLog(@"%@", x);}];[letters sendNext:@"A"];[numbers sendNext:@"1"];[letters sendNext:@"B"];[letters sendNext:@"C"];[numbers sendNext:@"2"];
-flattenMap:
当一个 stream 的值也是 stream 时,将子 stream 中的值转换后,组成一个新的 stream。
-flattenMap: = -map: + -flatten:
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9RACSequence *extended = [numbers flattenMap:^(NSString *num) {return @[ num, num ].rac_sequence;}];// Contains: 1_ 3_ 5_ 7_ 9_RACSequence *edited = [numbers flattenMap:^(NSString *num) {if (num.intValue % 2 == 0) {return [RACSequence empty];} else {NSString *newNum = [num stringByAppendingString:@"_"];return [RACSequence return:newNum];}}];
组合 signal
-then:
当一个 signal 执行完成之后,subscriber 后续只会收到新返回的 signal 的值,如同订阅了新的 signal 一样。
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;// The new signal only contains: 1 2 3 4 5 6 7 8 9//// But when subscribed to, it also outputs: A B C D E F G H IRACSignal *sequenced = [[lettersdoNext:^(NSString *letter) {NSLog(@"%@", letter);}]then:^{return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;}];
+merge:
将多个 signal 合并为一个,一旦任一合并前的 signal 中产生新的值,新的 signal 立即产生同样的值。
RACSubject *letters = [RACSubject subject];RACSubject *numbers = [RACSubject subject];RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];// Outputs: A 1 B C 2[merged subscribeNext:^(NSString *x) {NSLog(@"%@", x);}];[letters sendNext:@"A"];[numbers sendNext:@"1"];[letters sendNext:@"B"];[letters sendNext:@"C"];[numbers sendNext:@"2"];
+combineLatest:和+combineLatest:reduce:
将多个 signal 合并成一个。与 +merge: 不同的是,这两个方法产生的新的 signal 只有在所有的合并前 signal 至少产生一个新的值时,才将所有的值(以元组的方式)返回。
RACSubject *letters = [RACSubject subject];RACSubject *numbers = [RACSubject subject];RACSignal *combined = [RACSignalcombineLatest:@[ letters, numbers ]reduce:^(NSString *letter, NSString *number) {return [letter stringByAppendingString:number];}];// Outputs: B1 B2 C2 C3[combined subscribeNext:^(id x) {NSLog(@"%@", x);}];[letters sendNext:@"A"];[letters sendNext:@"B"];[numbers sendNext:@"1"];[numbers sendNext:@"2"];[letters sendNext:@"C"];[numbers sendNext:@"3"];
-switchToLatest
用来处理由 signal 组成的 signal (signal-of-signals),总是返回最新的外层 signal 中的最新的 signal 的值。
RACSubject *letters = [RACSubject subject];RACSubject *numbers = [RACSubject subject];RACSubject *signalOfSignals = [RACSubject subject];RACSignal *switched = [signalOfSignals switchToLatest];// Outputs: A B 1 D[switched subscribeNext:^(NSString *x) {NSLog(@"%@", x);}];[signalOfSignals sendNext:letters];[letters sendNext:@"A"];[letters sendNext:@"B"];[signalOfSignals sendNext:numbers];[letters sendNext:@"C"];[numbers sendNext:@"1"];[signalOfSignals sendNext:letters];[numbers sendNext:@"2"];[letters sendNext:@"D"];
其他
take:(NSUInteger)n
仅仅值的前 n 次的变化会继续传递下去。
distinctUntilChanged
仅当 signal 的值与上一次不同时才会继续传递下去。
throttle:(NSTimeInterval)interval
在 interval 的时间内发生的值的变化仅传递最后一个。
统一消息传递机制
ReactiveCocoa 统一了 iOS 中几乎所有的消息传递方式:
Blocks
Delegates
Notifications
Errors
Target-Action
KVO
Method Overriding
// Blocks -> Signal// Groups code that happens on subscriptions, returns a single value when block// executes. defer is a general pattern to turn a function into a signal.//// This can be used to enclose a long-running function into an// async signal.[RACSignal defer:^ {// Test for sending a value and completingreturn [RACSignal return:@(arc4random())];}];// The same thing as above can be done (more explicitly)// with createSignal[RACSignal createSignal:^(id(<RACSubscriber> subscriber) {// Perform per-subscription side effects.[subscription sendNext:@(arc4random())];[subscription sendCompleted];return nil;}];// Blocks -> Signal// AFNetworking Example:[RACSignal createSignal:^(id<RACSubscriber> subscriber) {[manager GET:URLString parameters:paramssuccess:^(AFHTTPRequestOperation *op, id response) {[subscriber sendNext:response];[subscriber sendCompleted];}failure:^(AFHTTPRequestOperation *op, NSError *e) {[subscriber:sendError:e];}];}];// Using disposables w/ operations to cancel[RACSignal createSignal:^(id<RACSubscriber> subscriber) {NSOperation *operation = [manager GET:URLString parameters:paramssuccess:^(AFHTTPRequestOperation *op, id response) {[subscriber sendNext:response];[subscriber sendCompleted];} failure:^(AFHTTPRequestOperation *op, NSError *error) {[subscriber sendError:e];}];return [RACDisposable disposableWithBlock:^ {[operation cancel];}];}];// Core Data : switchToLatest on a search request : automatically cancel old requests.[RACSignal createSignal:^(id<RACSubscriber> subscriber) {RACDisposable *disposable = [RACDisposable new];[managedObjectContext performBlock:^ {if (disposable.disposed) return;NSError *error;NSArray *results = [moc performFetch:fetchRequest error:&error];if (results != nil) {[subscriber sendNext:results];[subscriber sendCompleted];} else {[subscriber sendError:error];}}];return disposable;}];// Delegates : creating a signal version of a delegate.// Shows the general pattern of wrapping delegate callbacks into// signals. Also how to perform side-effect actions based on the// subscriber count. In this example, CL is turned on when the// first subscriber subscribes and turned off after all subscribers// have unsubscribed.//// Notice we are setting `self` as the CL delegate but the// delegate methods are not implemented - rather subscribed// to via rac_signalForSelector. This translates// delegate callbacks into signal values.// reduceEach is like map. In this case, we use it to return only// the values in the tuple that matter (the locations / error object).// flattenMap takes a value and creates a signal from it.CLLocationManager *locationManager = ...locationManager.delegate = self; //static volatile int32_t subscriberCount = 0;[RACSignal createSignal:^(id<RACSubscriber> subscriber) {RACSignal *locations = [[self rac_signalForSeletor:(@selector(...didUpdateLocations:)fromProtocol:@protocol(CLLocationManagerDelegate)]reduceEach^(id _, NSArray *locations) {return locations;}];RACSignal *error = [[self rac_signalForSeletor:(@selector(...didFailWithError:)fromProtocol:@protocol(CLLocationManagerDelegate)]reduceEach^(id _, NSError *error) {return error;}]filter:^BOOL (NSError *error) {// Documentation says CL will keep trying after kCLErrorLocationUnknownreturn error.code != kCLErrorLocationUnknown;}]flattenMap:^(NSError *error){return [RACSignal error:error]; // create a new signal that will send error.}];RACDisposable *disposable = [[RACSignalmerge:@[ locations, error ]]subscribe:subscriber];// manage side effects if you have multiple subscribersif (OSAtomicIncrement32(&subscriberCount) == 1) {[locationManager startUpdatingLocation];} else {[subscriber sendNext:locationManager.location];}return [RACDisposable disposableWithBlock:^{[disposable dispose];if (OSAtomicDecrement32(&subscriberCount) == 0) {[locationManager stopUpdateLocation];}}];}];// KVORACSignal *isReachable = [RACObserve(reachabilityManager, networkReachabilityStatus)map:^(NSNumber *networkReachabilityStatus) {switch (networkReachabilityStatus.intValue) {case AFNetworkReachabilityStatusReachableViaWWAN:case AFNetworkReachabilityStatusReachableViaWiFi:return @YES;}return @NO;}];// NotificationsRACSignal *isForeground = [RACSignal merge:@[[[defaultCenter rac_addObserverForName:WillEnterForeground ...]mapReplace:@YES][[defaultCenter rac_addObserverForName:DidEnterBackground ...]mapReplace:@NO]]];// Listens to the foreground. When isForeground == @YES, sends values from// the "didBecomeActive" signal. You can use this from a VM// to *not* bind to UIApplicationDelegateRACSignal *hasLaunchedActive = [RACSignalif:isForegroundthen:[defaultCenter rac_addObserverForName:DidBecomeActive]else:[RACSignal empty]];
UIView Categories
ReactiveCocoa 提供了许多 UIView 的 Category 用来快速将 UI 的事件、属性转换成 signal。例如 rac_textSignal 是 UITextField 的 text 属性转换成的信号;rac_buttonClickedSignal 是 UIAlertView 的按钮点击的信号。
我们订阅这些信号就可以摆脱各种 Delegate。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil];[[alertView rac_buttonClickedSignal] subscribeNext:^(NSNumber *indexNumber) {if ([indexNumber intValue] == 1) {NSLog(@"NO");} else {NSLog(@"YES");}}];[alertView show];
宏
ReactiveCocoa 提供了一些非常方便的宏。
RACObserve(self, status):将 status 转换成一个 signal,当其值发生变化时触发事件。作用与 KVO 相同。RAC(self.textField, text):将某个对象的某个属性绑定到一个 signal 的值上。@weakify(self)和@strongify(self)用于避免循环引用。
