Yalantis: iOS, Android And Web App Development Company

Reactive Programming on Objective-C

Languages constantly change and evolve with time due to new technology appearing, modern requirements or just a wish to bring new air into coding experience. Functional reactive lifestyle of a programming framework Reactive Cocoa breaks the imperative borders of Objective-C and has a lot to offer to the conventional paradigm. As tasty as it sounds it certainly attracts attention of our iOS developers.

ReactiveCocoa brings declarative style into Objective-C. What do we mean by that? Conventional imperative style, which is used by such languages as: C, C++, Objective-C, Java, etc. can be described like this: you’re writing directives for computer program which should be executed in a certain way. In other words, you’re saying “how to do” something. Whereas declarative programming lets you describe control flow as a sequence of “what to do” actions without prescribing how to do them.

 reactive programming in objective C

Imperative vs Functional

Imperative approach to programming implies detailed description of the each step the computer should take to accomplish the goal. In fact, the native to a computer machine code is written in the imperative style, which is, therefore, a characteristic of most of the programming languages and they generally follow the same paradigm.

In contrast, a functional approach involves composing the problem as a set of functions to be executed. You define the input to each function, and what each function returns. These two approaches to programming are very different.

Here are the main inconsistencies:

1. State Changes

For pure functional programming state changes do not exist as there are no side effects. Side effect implies state modification in addition to returning value because of some external interaction. RT (referential transparency) of sub-expression is often referred to "no side-effects" and is especially applicable to pure functions. RT does not let the function implementation access external to the function mutable state because every sub-expression is a function call by definition. To clarify the point, pure functions have the following attributes:

  • the only observable output is the return value
  • the only output dependency is the arguments
  • arguments are fully determined before any output is generated

Notwithstanding the fact that the functional approach minimizes side-effects, it can’t avoid them completely as it is the internal part of any coding.

In contrast, functions in the imperative programming lack referential transparency, which may be the only attribute to differentiate a declarative expression from an imperative one. Side effects are widely used to implement state and I/O. Commands in the source language may change the state, which leads to the same language expression resulting in different values.

What about Reactive cocoa? This functional framework on Objective-C, which is conceptually an imperative language, does not include explicitly pure functions. Trying to avoid states mutability it doesn’t restrict side effects, however.

2. First-class citizens

Functional programming has objects and functions as its first-class citizens. What does it mean? It means that functions can be passed as a parameter, assigned to a variable, returned from the function. Why is it useful? It makes is easy to manipulate execution blocks, compose and combine functions in various ways without any difficulties such as function pointers (char *(*(**foo[][8])())[]; - have fun!).

Languages that use the imperative approach have their own particularities regarding first-class expressions. What about Objective - C? It has blocks as an implementation of closures. Higher-order functions (HOFs) can be simulated by passing blocks as parameters. In this case, a block is a closure and a higher order function can be built of a certain set of blocks.

However, the process of manipulation with HOF in functional languages is way faster and requires shorter lines of code.

3. Primary flow control

Loops in the imperative style are represented as function calls and recursions in the functional. Iteration in functional languages is usually accomplished via recursion. Why? For the sake of complexity, maybe. For us, Objective-C developers with our imperatively composed brains, loops seem much more programmer-friendly. Recursions can cause difficulties, like excessive memory consumption.

But! We can write a function without using loops or recursions. For each of infinite possible specialized actions that could be applied to each element of a collection, functional programming employs reusable iteration functions, such as “map”, “fold”, “filter”. These functions are useful for refactoring code. They reduce repetition and don’t require writing a separate function. (read further, we have more on this!)

4. Order of the execution

Declarative expressions, express only the logical relationships of the sub-expression function arguments and not mutable state relationship, as due to the lack of side effects a state transition of each function call is independent of the others.

The functional order of imperative expressions execution depends on the mutable state. That’s why the order of execution matters and is implicitly defined by the source code organization. To this matter, we can point out the difference between the evaluation strategies of the both approaches.

Lazy evaluation or call-by-need in functional programming languages is a strategy that delays the evaluation of an expression until its value is needed and avoids repeated evaluations. In other words, expressions are only evaluated when evaluating a dependent expression. The order of operations becomes indeterminate.

In contrast, eager evaluation in the imperative language means, that the expression is evaluated as soon as it is bound to a variable. It implies dictating the order of execution, making it easier to determine when sub-expressions (including functions) within the expression will be evaluated, because these sub-expressions may have side-effects that will affect the evaluation of other expressions.

5. Code size

Functional programming requires less code than imperative programming. That means fewer points of failure, less code to test, and a more productive programming cycle. As system constantly evolves and grows, this is important.

Read also: JSON to Core Data

Major components of ReactiveCocoa

Functional programming operates with concepts known as futures & promises. What’s so cool about them? In imperative programming you have to operate with already existing values which leads to the necessity of synchronizing asynchronous code and other difficulties. In contrast to that futures & promises enable you to work with the values that have not formed yet (asynchronous code is written in a synchronous way).

reactive cocoa

Signal

Futures & promises are represented as signals in the reactive programming. RACSignal is a base component of Reactive Cocoa and represents a stream of events that will be delivered in future. You subscribe to the signal and access the events that occur over time. Signal is a push-driven stream and can represent button presses, asynchronous network operations, timers, other UI events, or anything else that changes over time. They can bind the results of asynchronous operations and effectively combine multiple event sources.

Sequence

Another type of stream is a sequence. Unlike signal, sequence is a pull-driven stream. It is a kind of collection which has a similar purpose to that of NSArray. RACSequence allows certain operations to be executed when you need them, not consistently, like with NSArray collection. Values in a sequence are evaluated only when they are required by default. Using only a part of a sequence potentially improves the performance. RACSequence allows Cocoa collections to be manipulated in a uniform and declarative way. RAC adds a -rac_sequence method to most of Cocoa's collection classes, allowing them to be used as RACSequences instead.

Command

RACCommand creates and subscribes to a signal in response to some action. It is mostly applied to UI interactions. UIKit categories provided by ReactiveCocoa for most of UIKit controls give us a graceful way of UI-events handling. Let’s imagine that we have to login a user in response to a button tap. In this case, a command can represent network request. When it starts executing the button changes its state to disabled and vice versa. What else? We can pass enabled signal to a command (Reachability is a good example). Therefore, if the server isn’t reachable (which is our “enabled signal”), command will be disabled and each command associated control will reflect this state.

Read also: Web-based hosting service for software development projects

Basic operations examples

Here are some diagrams on how the basic operations with RACSignals work:

Merge

+ (RACSignal *)merge:(id<NSFastEnumeration>)signals;

merge

The result streams has both streams of events merged together.  So as you can see "+ merge" is useful whenever you don’t care about the particular source of events but would like to handle them in one place. In our example we are setting stateLabel.text using 3 different signals: execution, completion, error.

RACCommand *loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
	// let's login!
}];

RACSignal *executionSignal = [loginCommand.executionSignals map:^id(id value) {
	return @"Connecting..";
}];

RACSignal *completionSignal = [loginCommand.executionSignals flattenMap:^RACStream *(RACSignal *next) {
	return [[[next materialize] filter:^BOOL(RACEvent *event) {
		return event.eventType == RACEventTypeCompleted;
	}] map:^id(id value) {
		return @"Done";
	}];
}];

RACSignal *errorSignal = [loginCommand.errors map:^id(id value) {
	return @"Sorry :(";
}];

RAC(self.stateLabel, text) = [RACSignal merge:@[executionSignal, completionSignal, errorSignal]];

CombineLatest

+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;

The result stream contains the latest values of the passed the streams. If one of the streams has no value yet, the result will be nil.

combine-1

When we can use this one? Let’s take our previous example and add more logic to it. It’s useful to enable login button only when a user has entered the correct email & password, right? We can declare this rules in the following way:

ACSignal *enabledSignal = [RACSignal combineLatest:@[self.emailField.rac_textSignal, self.passwordField.rac_textSignal]
 reduce:^id (NSString *email, NSString *password) {
	return @([email isValidEmail] && password.length > 3);
}];
*Now let’s modify our login command a little bit & connect it to the actual loginButton

RACCommand *loginCommand = [[RACCommand alloc] initWithEnabled:enabledSignal signalBlock:^RACSignal *(id input) {
	// let's login!
}];

[self.loginButton setRac_command:loginCommand];

FlattenMap

- (RACSignal *)flattenMap:(RACStream * (^)(id value))block;

You create new streams for each value in the source stream using the given (f) function. The result stream returns new signals based on the values formed in the source streams. That’s why it can be asynchronous.

flattenMap

Let’s imagine, that your login request consists of the two separate parts: fetch Facebook data (id, etc) & pass it to the Backend. One of the requirements is to be able to cancel login. Therefore the client code has to handle the state of login operation to be able to cancel it. It gives a lot of boilerplate code, especially if you can login from a few places.

How ReactiveCocoa helps you? This could be the real implementation of the login routine:

- (RACSignal *)authorizeUsingFacebook {
	return [[[FBSession rac_openedSession] flattenMap:^RACStream *(FBSession *session) {
		return [session rac_fetchProfile];
	}] flattenMap:^RACStream *(NSDictionary *profile) {
		return [self authorizeUsingFacebookProfile:profile];
	}];
}

Legend:

+ [FBSession rac_openedSession] - signal, whose result is opened FBSession instance. It could lead to Facebook login, if needed.

- [FBSession rac_fetchProfile] - signal, which fetches profile with session, passed as self.

The greatest advantage of this approach is that for a user the whole flow is opaque, represented by a single signal and could be canceled at any “stage”, whether it’s a Facebook login or a Backend call.

Filter

- (RACSignal *)filter:(BOOL (^)(id value))block;

The result stream contains the values of the stream “a”, filtered according to the given function.

filter-1

RACSequence *sequence = @[@"Some", @"example", @"of", @"sequence"].rac_sequence;
RACSequence *filteredSequence = [sequence filter:^BOOL(id value) {
	return [value hasPrefix:@"seq"];
}];


Map

- (RACSignal *)map:(id (^)(id value))block;

Unlike FlattenMap, Map is a synchronous operation. The value of the property “a” goes through given f (x + 1) function and returns the mapped input value.

map

Let’s imagine, that you’re displaying a model’s title by applying some attributes to it. Map comes into play, when “Applying some attributes” described as a separate function:

RAC(self.titleLabel, text) = [RACObserve(model, title) map:^id(NSString *modelTitle) {
	NSDictionary *attributes = @{/*your custom font, paragraph style, etc*/};
	return [[NSAttributedString alloc] initWithString:modelTitle attributes:attributes];
}];

What it actually does here: bind self.titleLabel.text to the changes of the model.title by applying custom attributes to it.

Zip

+ (RACSignal *)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock;

The events of the result stream are generated when each of the streams has formed the equal number of events.  It contains the values one from each of the 3 streams combined.

 zip-1

 

Zip could be described for some use-cases as a dispatch_group_notify . For example, you’ve got 3 separate signals and need to combine their responses in a single point:

NSArray *signals = @[retrieveFacebookContactsSignal, retrieveAddressBookContactsSignal];
return [RACSignal zip:signals reduce:^id (NSArray *facebookContacts, NSArray *addressBookContacts){
	NSArray *mergedContacts = // let's merge them somehow ^_^
	return mergedContacts;
}];

Throttle

- (RACSignal *)throttle:(NSTimeInterval)interval;

With a timer set to the definite time interval the first value of the stream “a” is passed to the result stream only when the time interval is over. In case a new value is produced within the defined time interval, it throttles the first value not letting it be passed to the result stream. The second value appears in the result stream instead.

 throttle

Amazing use-case: we need to perform search request when a user changes the searchField. A common task, huh? However, it’s not effective to construct & send network request on each text change, since the textField can generate a lot of such events per second and we’ll end up using network inefficiently. The way out here is to add a delay, after which we actually perform network request. A usual way to achieve this, is to add NSTimer. With ReactiveCocoa it is much easier!

[[[seachField rac_textSignal] throttle:0.3] subscribeNext:^(NSString *text) {
	// perform network request
}];

*An important note here is that all the “previous” textField changes before the last one will be discarded.

Delay

- (RACSignal *)delay:(NSTimeInterval)interval;

The value produced in “a” stream is delayed and passed to the result stream after a certain time interval.

 delay

As counterpart to -[RACSignal throttle:] delay will only delay sending of “next” & “completed” events.

[[textField.rac_textSignal delay:0.3] subscribeNext:^(NSString *text) {

}];

What we like about Reactive Cocoa

  • Introduces Cocoa Bindings to iOS
  • The ability to compose operations on future data. Here is a bit on futures & promises theory from Scala.
  • The ability to represent asynchronous operations in a synchronous way. Reactive Cocoa simplifies asynchronous software, networking code for example.
  • Convenient decomposition. Code that deals with user events and changing app state may become very complex and spaghetti-like. Reactive Cocoa makes patterns of dependant operations particularly easy. When we represent operations like network response handling, user events, etc., as a stream of events that are combined in some ways, we can achieve high-modularity and loose coupling which leads to a more reusable code.
  • Behaviors and relationships between properties are defined declaratively.
  • Minimizing shared state solves problems with synchronization - if you combine multiple signals there is one single place to handle all the results (whether it is the next value, a signal completion or an error)

With RAC framework you can compose and transform sequences of values in a better high-level way. RAC makes it easier for us to reason about, chain and compose in any uniform all that waiting for an asynchronous operation to complete, network to respond, dependent value to change and then react to that. Hard to deal with at the first sight, Reactive Cocoa is contagious!

Tech

How I Learned to Write Custom Signals in Reactive Cocoa

Tech

Declarative Navigation Bar Appearance Configuration

Design

How We Created Guillotine Menu Animation for iOS