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 事件中的一种或几种:

  1. RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
  2. // Outputs: A B C D E F G H I
  3. [letters subscribeNext:^(NSString *x) {
  4. NSLog(@"%@", x);
  5. }];
  • -do...:

用来添加副作用,当 Next、Failed、Completed 和 Interrupted 事件中的一种发生时执行 do... 中的 block。

  1. __block unsigned subscriptions = 0;
  2. RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
  3. subscriptions++;
  4. [subscriber sendCompleted];
  5. return nil;
  6. }];
  7. // Does not output anything yet
  8. loggingSignal = [loggingSignal doCompleted:^{
  9. NSLog(@"about to complete subscription %u", subscriptions);
  10. }];
  11. // Outputs:
  12. // about to complete subscription 1
  13. // subscription 1
  14. [loggingSignal subscribeCompleted:^{
  15. NSLog(@"subscription %u", subscriptions);
  16. }];

转换 stream

  • -map:

将一个 stream 中的值转换成其他的值,组成一个新的 stream。

  1. RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
  2. // Contains: AA BB CC DD EE FF GG HH II
  3. RACSequence *mapped = [letters map:^(NSString *value) {
  4. return [value stringByAppendingString:value];
  5. }];
  • -filter:

用来过滤 stream 中的值,传入的 block 返回一个 BOOL,来决定是否将值放入新的 stream 中。

  1. RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
  2. // Contains: 2 4 6 8
  3. RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
  4. return (value.intValue % 2) == 0;
  5. }];

组合 stream

  • -concat:

将一个 stream 的值追加到另一个 stream 的值后面。

  1. RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
  2. RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
  3. // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
  4. RACSequence *concatenated = [letters concat:numbers];
  • -flatten:

如果一个 signal 的 stream 中的值也是一个 stream,-flatten: 会将子 stream 中的值取出,组成一个新的 stream。组合的效果可能是 concat:

  1. RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
  2. RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
  3. RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;
  4. // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
  5. RACSequence *flattened = [sequenceOfSequences flatten];

或者 merge:

  1. RACSubject *letters = [RACSubject subject];
  2. RACSubject *numbers = [RACSubject subject];
  3. RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
  4. [subscriber sendNext:letters];
  5. [subscriber sendNext:numbers];
  6. [subscriber sendCompleted];
  7. return nil;
  8. }];
  9. RACSignal *flattened = [signalOfSignals flatten];
  10. // Outputs: A 1 B C 2
  11. [flattened subscribeNext:^(NSString *x) {
  12. NSLog(@"%@", x);
  13. }];
  14. [letters sendNext:@"A"];
  15. [numbers sendNext:@"1"];
  16. [letters sendNext:@"B"];
  17. [letters sendNext:@"C"];
  18. [numbers sendNext:@"2"];
  • -flattenMap:

当一个 stream 的值也是 stream 时,将子 stream 中的值转换后,组成一个新的 stream。

-flattenMap: = -map: + -flatten:

  1. RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
  2. // Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
  3. RACSequence *extended = [numbers flattenMap:^(NSString *num) {
  4. return @[ num, num ].rac_sequence;
  5. }];
  6. // Contains: 1_ 3_ 5_ 7_ 9_
  7. RACSequence *edited = [numbers flattenMap:^(NSString *num) {
  8. if (num.intValue % 2 == 0) {
  9. return [RACSequence empty];
  10. } else {
  11. NSString *newNum = [num stringByAppendingString:@"_"];
  12. return [RACSequence return:newNum];
  13. }
  14. }];

组合 signal

  • -then:

当一个 signal 执行完成之后,subscriber 后续只会收到新返回的 signal 的值,如同订阅了新的 signal 一样。

  1. RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
  2. // The new signal only contains: 1 2 3 4 5 6 7 8 9
  3. //
  4. // But when subscribed to, it also outputs: A B C D E F G H I
  5. RACSignal *sequenced = [[letters
  6. doNext:^(NSString *letter) {
  7. NSLog(@"%@", letter);
  8. }]
  9. then:^{
  10. return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;
  11. }];
  • +merge:

将多个 signal 合并为一个,一旦任一合并前的 signal 中产生新的值,新的 signal 立即产生同样的值

  1. RACSubject *letters = [RACSubject subject];
  2. RACSubject *numbers = [RACSubject subject];
  3. RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
  4. // Outputs: A 1 B C 2
  5. [merged subscribeNext:^(NSString *x) {
  6. NSLog(@"%@", x);
  7. }];
  8. [letters sendNext:@"A"];
  9. [numbers sendNext:@"1"];
  10. [letters sendNext:@"B"];
  11. [letters sendNext:@"C"];
  12. [numbers sendNext:@"2"];
  • +combineLatest:+combineLatest:reduce:

将多个 signal 合并成一个。与 +merge: 不同的是,这两个方法产生的新的 signal 只有在所有的合并前 signal 至少产生一个新的值时,才将所有的值(以元组的方式)返回。

  1. RACSubject *letters = [RACSubject subject];
  2. RACSubject *numbers = [RACSubject subject];
  3. RACSignal *combined = [RACSignal
  4. combineLatest:@[ letters, numbers ]
  5. reduce:^(NSString *letter, NSString *number) {
  6. return [letter stringByAppendingString:number];
  7. }];
  8. // Outputs: B1 B2 C2 C3
  9. [combined subscribeNext:^(id x) {
  10. NSLog(@"%@", x);
  11. }];
  12. [letters sendNext:@"A"];
  13. [letters sendNext:@"B"];
  14. [numbers sendNext:@"1"];
  15. [numbers sendNext:@"2"];
  16. [letters sendNext:@"C"];
  17. [numbers sendNext:@"3"];
  • -switchToLatest

用来处理由 signal 组成的 signal (signal-of-signals),总是返回最新的外层 signal 中的最新的 signal 的值。

  1. RACSubject *letters = [RACSubject subject];
  2. RACSubject *numbers = [RACSubject subject];
  3. RACSubject *signalOfSignals = [RACSubject subject];
  4. RACSignal *switched = [signalOfSignals switchToLatest];
  5. // Outputs: A B 1 D
  6. [switched subscribeNext:^(NSString *x) {
  7. NSLog(@"%@", x);
  8. }];
  9. [signalOfSignals sendNext:letters];
  10. [letters sendNext:@"A"];
  11. [letters sendNext:@"B"];
  12. [signalOfSignals sendNext:numbers];
  13. [letters sendNext:@"C"];
  14. [numbers sendNext:@"1"];
  15. [signalOfSignals sendNext:letters];
  16. [numbers sendNext:@"2"];
  17. [letters sendNext:@"D"];

其他

  • take:(NSUInteger)n

仅仅值的前 n 次的变化会继续传递下去。

  • distinctUntilChanged

仅当 signal 的值与上一次不同时才会继续传递下去。

  • throttle:(NSTimeInterval)interval

在 interval 的时间内发生的值的变化仅传递最后一个。

统一消息传递机制

ReactiveCocoa 统一了 iOS 中几乎所有的消息传递方式:

  • Blocks

  • Delegates

  • Notifications

  • Errors

  • Target-Action

  • KVO

  • Method Overriding

  1. // Blocks -> Signal
  2. // Groups code that happens on subscriptions, returns a single value when block
  3. // executes. defer is a general pattern to turn a function into a signal.
  4. //
  5. // This can be used to enclose a long-running function into an
  6. // async signal.
  7. [RACSignal defer:^ {
  8. // Test for sending a value and completing
  9. return [RACSignal return:@(arc4random())];
  10. }];
  11. // The same thing as above can be done (more explicitly)
  12. // with createSignal
  13. [RACSignal createSignal:^(id(<RACSubscriber> subscriber) {
  14. // Perform per-subscription side effects.
  15. [subscription sendNext:@(arc4random())];
  16. [subscription sendCompleted];
  17. return nil;
  18. }];
  19. // Blocks -> Signal
  20. // AFNetworking Example:
  21. [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  22. [manager GET:URLString parameters:params
  23. success:^(AFHTTPRequestOperation *op, id response) {
  24. [subscriber sendNext:response];
  25. [subscriber sendCompleted];
  26. }
  27. failure:^(AFHTTPRequestOperation *op, NSError *e) {
  28. [subscriber:sendError:e];
  29. }];
  30. }];
  31. // Using disposables w/ operations to cancel
  32. [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  33. NSOperation *operation = [manager GET:URLString parameters:params
  34. success:^(AFHTTPRequestOperation *op, id response) {
  35. [subscriber sendNext:response];
  36. [subscriber sendCompleted];
  37. } failure:^(AFHTTPRequestOperation *op, NSError *error) {
  38. [subscriber sendError:e];
  39. }];
  40. return [RACDisposable disposableWithBlock:^ {
  41. [operation cancel];
  42. }];
  43. }];
  44. // Core Data : switchToLatest on a search request : automatically cancel old requests.
  45. [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  46. RACDisposable *disposable = [RACDisposable new];
  47. [managedObjectContext performBlock:^ {
  48. if (disposable.disposed) return;
  49. NSError *error;
  50. NSArray *results = [moc performFetch:fetchRequest error:&error];
  51. if (results != nil) {
  52. [subscriber sendNext:results];
  53. [subscriber sendCompleted];
  54. } else {
  55. [subscriber sendError:error];
  56. }
  57. }];
  58. return disposable;
  59. }];
  60. // Delegates : creating a signal version of a delegate.
  61. // Shows the general pattern of wrapping delegate callbacks into
  62. // signals. Also how to perform side-effect actions based on the
  63. // subscriber count. In this example, CL is turned on when the
  64. // first subscriber subscribes and turned off after all subscribers
  65. // have unsubscribed.
  66. //
  67. // Notice we are setting `self` as the CL delegate but the
  68. // delegate methods are not implemented - rather subscribed
  69. // to via rac_signalForSelector. This translates
  70. // delegate callbacks into signal values.
  71. // reduceEach is like map. In this case, we use it to return only
  72. // the values in the tuple that matter (the locations / error object).
  73. // flattenMap takes a value and creates a signal from it.
  74. CLLocationManager *locationManager = ...
  75. locationManager.delegate = self; //
  76. static volatile int32_t subscriberCount = 0;
  77. [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  78. RACSignal *locations = [[self rac_signalForSeletor:(@selector(...didUpdateLocations:)
  79. fromProtocol:@protocol(CLLocationManagerDelegate)]
  80. reduceEach^(id _, NSArray *locations) {
  81. return locations;
  82. }];
  83. RACSignal *error = [[self rac_signalForSeletor:(@selector(...didFailWithError:)
  84. fromProtocol:@protocol(CLLocationManagerDelegate)]
  85. reduceEach^(id _, NSError *error) {
  86. return error;
  87. }]
  88. filter:^BOOL (NSError *error) {
  89. // Documentation says CL will keep trying after kCLErrorLocationUnknown
  90. return error.code != kCLErrorLocationUnknown;
  91. }]
  92. flattenMap:^(NSError *error){
  93. return [RACSignal error:error]; // create a new signal that will send error.
  94. }];
  95. RACDisposable *disposable = [[RACSignal
  96. merge:@[ locations, error ]]
  97. subscribe:subscriber];
  98. // manage side effects if you have multiple subscribers
  99. if (OSAtomicIncrement32(&subscriberCount) == 1) {
  100. [locationManager startUpdatingLocation];
  101. } else {
  102. [subscriber sendNext:locationManager.location];
  103. }
  104. return [RACDisposable disposableWithBlock:^{
  105. [disposable dispose];
  106. if (OSAtomicDecrement32(&subscriberCount) == 0) {
  107. [locationManager stopUpdateLocation];
  108. }
  109. }];
  110. }];
  111. // KVO
  112. RACSignal *isReachable = [RACObserve(reachabilityManager, networkReachabilityStatus)
  113. map:^(NSNumber *networkReachabilityStatus) {
  114. switch (networkReachabilityStatus.intValue) {
  115. case AFNetworkReachabilityStatusReachableViaWWAN:
  116. case AFNetworkReachabilityStatusReachableViaWiFi:
  117. return @YES;
  118. }
  119. return @NO;
  120. }];
  121. // Notifications
  122. RACSignal *isForeground = [RACSignal merge:@[
  123. [[defaultCenter rac_addObserverForName:WillEnterForeground ...]
  124. mapReplace:@YES]
  125. [[defaultCenter rac_addObserverForName:DidEnterBackground ...]
  126. mapReplace:@NO]
  127. ]];
  128. // Listens to the foreground. When isForeground == @YES, sends values from
  129. // the "didBecomeActive" signal. You can use this from a VM
  130. // to *not* bind to UIApplicationDelegate
  131. RACSignal *hasLaunchedActive = [RACSignal
  132. if:isForeground
  133. then:[defaultCenter rac_addObserverForName:DidBecomeActive]
  134. else:[RACSignal empty]];

UIView Categories

ReactiveCocoa 提供了许多 UIView 的 Category 用来快速将 UI 的事件、属性转换成 signal。例如 rac_textSignal 是 UITextField 的 text 属性转换成的信号;rac_buttonClickedSignal 是 UIAlertView 的按钮点击的信号。

我们订阅这些信号就可以摆脱各种 Delegate。

  1. UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil];
  2. [[alertView rac_buttonClickedSignal] subscribeNext:^(NSNumber *indexNumber) {
  3. if ([indexNumber intValue] == 1) {
  4. NSLog(@"NO");
  5. } else {
  6. NSLog(@"YES");
  7. }
  8. }];
  9. [alertView show];

ReactiveCocoa 提供了一些非常方便的宏。

  • RACObserve(self, status):将 status 转换成一个 signal,当其值发生变化时触发事件。作用与 KVO 相同。

  • RAC(self.textField, text):将某个对象的某个属性绑定到一个 signal 的值上。

  • @weakify(self)@strongify(self) 用于避免循环引用。

Ref