Reactive Cocoa: A 6 Step Guide

Datetime:2016-08-23 01:37:57          Topic: Cocoa           Share
  1. Introduction: What is Reactive Cocoa? What does functional and reactive mean, in terms of programming? Why is it useful, and generally a good practice?
  2. Getting started: How do I set up my project to start developing with RAC?
  3. Basics: Classes and methods used in RAC. General data flow representation.
  4. A small task: A short example of code and output behaviour
  5. Bigger picture: What else is RAC capable of?
  6. Summary:  Where can I find more information, documentations? What did I learn from the sample application?

1. Introduction

Most people, who have some experience in the field of software development have heard, or read about Functional and Reactive programming at least at one point in their lives. If you are one of them, you’ve come to the right place. If not, it’s all the same. Whether you are a senior developer looking for ways to improve the performance of your apps, an adventurous freelancer seeking new coding styles, or a novice still expanding his knowledge on mobile: Reactive Cocoa will certainly come in handy, as it incorporates both Functional and Reactive technologies i nto a neat and simple framework.

Worry not, there is tons of support for Swift out there. However, this article uses the Objective-C implementation of RAC, because we know there are still a bunch of applications that could benefit from it!

So why is RAC so amazing?

First of all, it’s Functional : It makes use of functions that take other functions as arguments.

Secondly, it’s Reactive : It’s focused on the flow of data and change,.

Functional and Reactive programming slightly counteract each other in certain ways, but RAC takes the best of both worlds, providing immense practical value, when implemented. This article focuses on initially using the framework, with examples and explanations to understand it better.

2. Getting Started

Setting up the environment is easy as pie: just add the ReactiveCocoa framework to your project! Using CocoaPods is strongly recommended. The actual usage of the framework will be explained later in the samples.

Start a new project, add a UITextField outlet to your root viewcontroller, then add

#import <ReactiveCocoa/ReactiveCocoa.h>

to the header file.

An important thing to mention is that the Objective-C implementation of RAC relies heavily on blocks. If you are not experienced with blocks, you can always visit this website. (There is a good reason I’m not writing down its name, you’ll see ).

3. Basics of Reactive Cocoa

Events

In mobile platform development, a huge portion of our code is a reaction to all kinds of events, be it button taps, recognised gestures, NSNotifications, Key-Value Observing, etc. The bottom line is, something happened . These happenings are represented by the Event type, which is the simple most important thing in the whole picture. When an observable event occurs, these objects are generated and sent to Observers over Signals (more about these a bit later).

Events can represent either a new value, or the termination of a process. Enumerated as follows:

  1. Next: The event’s source produced a new value, ready to be observed and handled.
  2. Failed: An error occurred before the signal could finish.
  3. Completed: The signal went through with no problems, and value generation is now stopped.
  4. Interrupted: The signal was neither successful, nor unsuccessful. In other words, it was cancelled.

Signals

Objects of the Signal type are series of events, and can be observed. Signals represent event streams in progress, e.g. user input. As data are received, events are sent on the signal, and all observers receive said events at the same time. In order to see a signal’s events, the user must observe it. Signals are push-based, so observing them has no side effect, and there is no way to influence their behaviour from the receiving end.

The lifetime of a signal includes any number of Next events, and exactly one terminating event (Failed, Completed, or Interrupted).

Manipulating a signal’s value can be done by applying a primitive to their Next events, these are filter, map, and reduce. Multiple signals can be manipulated together with the zip primitive.

Using a pipe with the Signal.pipe() command produces a signal that can be controlled manually, which is useful when a non-RAC class works together with an RAC class.

4. Example

Let’s take a look at this piece of code:

[self.textField.rac_textSignal subscribeNext:^(id x) {
  NSLog(@ "%@" , x);
}];

Let’s assume we have a UITextField, and we’d like to log its input to the standard output. Normally, we would have to add our viewcontroller as the target to the textfield, define a function to call, and implement this behaviour separately. With RAC, every standard UIControl element has a signal for its standard events, so all we have to do is subscribe to the appropriate signal, then tell our program what we want to do with the information. After the subscription has been done, and we start typing into the textfield and our output will look like this:

T Th Thi This This This i This is This is This is a This is aw This is awe This is awes This is aweso This is awesom This is awesome This is awesome!

As you can see, we are observing every single value change in the text field. This is nice and all, but there is a lot more we can do with event flows.

This is where the filter and map primitives come in.

First, we need to check if the textfield’s value is actually numeric, then convert it into an NSInteger.

We start off by checking the value:

RACSignal *numericTextSignal = [self.textField.rac_textSignal
    filter:^BOOL(id value) {
     NSString *text = value;
     NSCharacterSet* notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
     if ([text rangeOfCharacterFromSet:notDigits].location == NSNotFound && text.length > 0 )
     {
       // newString consists only of the digits 0 through 9, and has at least one digit
       return YES;
     }
     return NO;
   }];

The primitive filter takes an argument of any kind, and returns a boolean value. What it actually does is filtering (how surprising) the incoming values, only letting through the ones we told it to. Note that it doesn’t change the original value observed through the signal, it’s just a gate to keep out everyone without a valid passport. Currently, every purely numeric string that’s at least one digit long will go through, others go down the drain.

Notice anything else? That’s right, the return value of (almost) every signal is another signal,. This allows data flows to be forked, which is probably one of the most useful things about signals.

Onto the next step! We have a signal that provides us with numeric strings from our textfield, now we need to convert it to an NSInteger. Let’s add a small twist, and take the number’s remainder divided by 4.

[[numericTextSignal map:^id(NSString* text) {
     return @([text integerValue]);
   }] map:^id(NSNumber* value) {
     return @([value integerValue] % 4 );
   }];

Using the map primitive, we’re able to transform any value to any value, however we might desire. (The logic is similar to how programmers convert caffeine into code.) As you can see, these primitives can be chained (almost) infinitely. An important thing to remember is that signal chains can only take objects as their arguments, and can only return objects, that’s why we used the @() operator, to box our integers into objects.

Because we know that the numericTextSignal signal provides NSString objects, it’s safe to change the argument type too, there’s no need to manually cast it.

We already know that signals can be forked, what about merging them? Synchronizing data is crucial in mobile applications, but fear not, RAC has a great solution: the combineLatest:reduce primitive.

First off, let’s do some forking:

RACSignal *integerSignal = [numericTextSignal map:^id(NSString* text) {
     NSLog(@ "Checking %@" ,text);
     return @([text integerValue]);
   }];
  RACSignal *div3Signal = [integerSignal map:^id(NSNumber* value) {
     return @([value integerValue] % 3 == 0 );
   }];
  RACSignal *div4Signal = [integerSignal map:^id(NSNumber* value) {
     return @([value integerValue] % 4 == 0 );
   }];
RACSignal *div5Signal = [integerSignal map:^id(NSNumber* value) {
     return @([value integerValue] % 5 == 0 );
   }];

Now we have 3 new signals, each returning a boxed boolean value of whether the received number is divisible by 3, 4, and 5, respectively.

All we need is the combining:

RACSignal *divisibleByAll =
   [RACSignal combineLatest:@[div3Signal,div4Signal,div5Signal]
            reduce:^id(NSNumber *div3, NSNumber *div4, NSNumber *div5){
              return @([div3 boolValue] && [div4 boolValue] && [div5 boolValue]);
   }];
[divisibleByAll subscribeNext:^(NSNumber *divisibleByAll) {
     if ([divisibleByAll boolValue]){
       NSLog(@ "Divisible by 3,4,5!" );
     } else {
       NSLog(@ "Not divisible by all" );
     }
   }];

If you start typing numbers into the textfield, you will start seeing the results. Try 60!

The primitive combineLatest:reduce takes all given signals and their outputs, and returns a new signal, similar to map. The key difference here is that we can observe multiple signals at once. Whenever one of the observed signals produces a new value, their combination is instantly evaluated. This is extremely useful when working in environments that act upon a large number of conditions, for example, games. Exciting, huh?

5. Creating your own signal

So far we’ve only used signals provided by native elements of the platform. In most cases, people use 3rd party libraries, and/or their already existing code to speed up development. So how do we connect our other classes with the RAC data flow? A good example would be a login service with a success/failure completion block. We make an asynchronous request, wait for the response, and then what?

Well, the answer is surprisingly easy: Let’s create our own signal that provides the server’s response as its value.

-(RACSignal *)signInSignal {
   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     [self.signInService
      signInWithUsername:self.usernameTextField.text
      password:self.passwordTextField.text
      complete:^(BOOL success) {
        [subscriber sendNext:@(success)];
        [subscriber sendCompleted];
      }];
     return nil;
   }];
}

This method creates a signal that tries to sign in with our currently entered credentials.

The block that describes the signal created  is a single argument, when there is a subscriber to this signal, the code within the block will execute. It is passed a subscriber instance that conforms to the RACSubscriber protocol, this allows us to emit events towards them. We can send any number of Next events, followed by a terminating event, in this case, Completed.

The block’s return type is RACDisposable, which is useful when there is additional clean-up work in case of a cancellation, but it’s not needed now, so we return nil. Now let’s make use of this signal!

[[[self.signInButton
   rac_signalForControlEvents:UIControlEventTouchUpInside]
   flattenMap:^id(id x) {
    return [self signInSignal];
   }]
   subscribeNext:^(id x) {
    NSLog(@ "Sign in result: %@" , x);
   }];

Upon pressing the signInButton, the flattenMap primitive transforms the touch event into our previously written signal, then logs the result. It works very similar to map, and there is a good reason why it’s used here. If we use map, the forwarded signal would be a “signal that contains a signal”, and by flattening it, we can access the innermost signal. They can try to hide, but we’ll always find them!

6. Summary

Naturally, going into deep details of RAC would take tons of time, so I’m not going to do that (thank me later ). Hopefully this article was enough to demonstrate some of the strengths of this framework, and give you a fresh view of handling large rivers of data. These examples are fairly simple, but using this logic can lead to great success even in more complex situations.

If you are interested in learning more about this fantastic addition to our greyish default tools, make sure to visit the official GitHub website . You’ll find tons of documentation, platform and language support, and more! Have fun experimenting, don’t be afraid to shape your pipelines! Or riverbeds… whatever floats your boat.





About List