ReactiveCocoa Tutorial – The Definitive Introduction

Part One

*ReactiveCocoa combines a couple of programming styles:

Functional Programming which makes use of higher order functions, i.e. functions which take other functions as their arguments.

Reactive Programming which focuses of data-flows and change propagation

For this reason, you might hear ReactiveCocoa described as a Functional Reactive Programming (or FRP) framework.


*ReactiveCocoa signals (represented by RACSignal) send a stream of events to their subscribers. There are three types of events to know: next, error and completed. A signal may send any number of next events before it terminates after an error, or it completes.


*RACSignal has a number of methods you can use to subscribe to these different event types. Each method takes one or more blocks, with the logic in your block executing when an event occurs. In this case, you can see that the subscribeNext: method was used to supply a block that excites on each next event. 


*The ReactiveCocoa framework uses categories to add signals to many of the standard UIKit controls so you can add subscriptions to their events, which is where the rac_textSignal property on the text field came from.


*

    RACSignal *usernameSourceSignal = self.usernameTextField.rac_textSignal;

    RACSignal *filteredUsername = [usernameSourceSignal filter:^BOOL(id value) {

        NSString *text = value;

        return text.length >3;

    }];

    [filteredUsername subscribeNext:^(id x) {

        NSLog(@"%@", x);

    }];

What you've created here is a very simple pipeline. It is the very essence of Reactive Programming, where you express your application's functionality in terms of data flows.

In the above diagram you can see that the rac_textSingal is the initial source of events. The data flows through a filter that only allows events to pass if they contain a string with a length that is greater than 3. The final step in the pipeline is subscribeNext: where your block logs the event value.

At this point it's worth noting that the output of the filter operation is also an RACSignal. 


*The RAC macro allows you to assign the output of a signal to the property of an object. It takes two arguments, the first is the object that contains the property to set and the second is the property name. Each time the signal emits a next event, the value that passes is assigned to the given property. 


*

 RACSignal *signUpActiveSignal =

    [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]

                      reduce:^id(NSNumber *usernameValid,NSNumber *passwordValid) {

                          return @([usernameValid boolValue] && [passwordValidboolValue]);

                      }];

The above code uses the combineLatest:reduce: method to combine the latest values emitted by validUsernameSignal and validPasswordSignal into a shiny new signal. Each time either of the two source signals emits a new value, the reduce block executes, and the value it returns is sent as the next value of the combined signal.


*The result of these changes is the application no longer has private properties that indicate the current valid state of the two text fields. This is one of the key differences you'll find when you adopt a reactive style -- you don't need to use instance variables to track transient state.


*

-(RACSignal *)signInSignal {

    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

        [self.signInServicesignInWithUsername:self.usernameTextField.text

                                      password:self.passwordTextField.text

                                      complete:^(BOOL success) {

                                          [subscriber sendNext:@(success)];

                                          [subscriber sendCompleted];

                                      }];

        return nil;

    }];

}

The above code uses the createSignal: method on RACSignal for singal creation. The block that describes this signal is a single argument, and is passed to this method. When this signal has a subscriber, the code within this block executes.

The block is passed a single subscriber instance that adopt the RACSubscriber protocol, which has methods you invoke in order to emit events; you may also send any number of next events, terminated with either an error or complete event. In this case, it sends a single next event to indicate whether the sign-in was a success, followed by a complete event.

The return type for this block is an RACDisposable object, and it allows you to perform any clean-up work that might be required when a subscription is cancelled or trashed. This signal does not have any clean-up requirements, hence nil is returned. 


*Side-effect: logic you want to execute within a pipeline when a next event occurs, but it does not actually change the nature of the event itself.


Part Two

*ReactiveCocoa maintains and retains its own global set of signals. If it has one or more subscribers, then the signal is active. If all subscribers are removed, the signal can be de-allocated.

After a completed or error event, a subscription removes itself automatically. Manual removal may be accomplished via RACDisposable.

The subscription methods on RACSignal all return an instance of RACDisposable that allows you to manually remove the subscription via the dispose method.

If you create a pipeline but do not subscribe to it, the pipeline never executes, this includes any side-effects such as doNext: blocks.


*Note: If you're interested in finding out what @weakify and @strongify actually do, within Xcode select Product -> Perform Action -> Preprocess "RWSearchForViewController". This will preprocess the view controller, expand all the macros and allow you to see the final output. 

Take care when using instance variables within blocks. These will also result in the block capturing a strong reference to self. You can turn on a compiler warning to alert you if your code results in this problem. Search for retain within the project's build settings to find the options.


*

    [[[[[[[self requestAccessToTwitterSignal]

          then:^RACSignal *{

              return self.searchText.rac_textSignal;

          }]

         filter:^BOOL(NSString *text) {

             return [self isValidSearchText:text];

         }]

        throttle:0.5]

       flattenMap:^RACStream *(NSString *text) {

           return [self signalForSearchWithText:text];

       }]

      deliverOn:[RACSchedulermainThreadScheduler]]

     subscribeNext:^(NSDictionary *jsonSearchResult) {

         NSArray *statuses = jsonSearchResult[@"statuses"];

         NSArray *tweets = [statuses linq_select:^id(id tweet) {

             return [RWTweet tweetWithStatus:tweet];

         }];

         [self.resultsViewControllerdisplayTweets:tweets];

     } error:^(NSError *error) {

         NSLog(@"An error occurred: %@", error);

     }];

The then method waits until a completed event is emitted, then subscribes to the signal returned by its block parameter. This effectively passes control from one signal to the next.

The then method passes error events through. Therefore the final subscribeNext:error block still receives errors emitted by the initial access-requesting step.

The application pipeline starts with the requestAccessToTwitterSignal then switches to the rac_textSignal. Meanwhile, next events pass through a filter and finally onto the subscription block. You can also see any error events emitted by the first step are consumed by the same subscrieNext:error: block.


 As soon as a signal emits an error, it falls straight-through to the error-handling block. It is an exceptional flow.


*The operations shown above execute on the thread where the signal originally emitted its events.


*The deliverOn: pipeline step marshals the next event onto the main thread so that the subscribeNext: block can be safely executed.


*The throttle operation will only send a next event if another next event isn't received within the given time period.







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值