This is the second part of the Introduction to FRP series. Here’s the first part.

As mentioned on the first part of this series, we are going to focus on ReactiveCocoa 4 for showing examples on how to implement FRP concepts. I’ll assume you already installed it on your project. If you haven’t you may want to read this.

Let’s use the example of the register button that will be enabled when both the username and the password are valid, as long as the register button hasn’t been pressed already and we are registering the user via API.

If you recall, this is what we’ve said:

Now, this is what FRP programming will normally do:
1. Bind the register button’s enabled property to when both the username and the password are valid, as long as the register button hasn’t been pressed already and we aren’t registering the user via API.

So let start with the bind.

Bindings

ReactiveCocoa has a really cool, smart and practical way of binding things via the use of the <~ operator. Let’s see an example.

Let’s assume we have a UserViewModel that looks more less like this:

class UserViewModel {

	var name = MutableProperty<String?>(nil)
}

Then, this test would pass without issues:

class ReactiveTests: XCTestCase {
    
    func testSimpleBind() {
        
        let newName = "Mariano Abdala"
        let userViewModel = UserViewModel()
        let nameBindedProperty = MutableProperty<String?>(nil)
        
        nameBindedProperty <~ userViewModel.name

        userViewModel.name.value = newName

        XCTAssertEqual(userViewModel.name.value, newName)
        XCTAssertEqual(nameBindedProperty.value, newName)
    }
}

We will, of course, want to bind a ViewModel to a UIView’s property. In this case, the user’s name would probably end up being bounded to a userNameLabel’s text.

In the past you’d do something like1:

RAC(self.userNameLabel, text) = userViewModel.name;

But that won’t work in RAC 4. In RAC 4 you can only bind, using the <~ operator, to a MutableProperty<T>. But worry not, because Colin Eberhardt came up with an easy way to turn almost any property (including UIKit’s) into a RAC 4 MutableProperty<T>. You’ll most likely want to add that file2 to any project that uses RAC 4.

Let’s see how this looks like with a UILabel:

class ReactiveTests: XCTestCase {
    
    func testLabelBind() {
        
        let newName = "Mariano Abdala"
        let userViewModel = UserViewModel()
        let userNameLabel = UILabel()
        
        userNameLabel.rac_text <~ userViewModel.name

        userViewModel.name.value = newName

        XCTAssertEqual(userViewModel.name.value, newName)
        XCTAssertEqual(userNameLabel.text, newName)
    }
}

This may not look like much, but once your View Controllers start looking like just a bunch of simple bindings to your models, that’s when this starts to pay out. And isn’t that what one of the View Controller’s main responsibility is, to bind the View to the Model’s data?

Let’s then…

  1. Bind the register button’s enabled property to when both the username and the password are valid, as long as the register button hasn’t been pressed already and we aren’t registering the user via API.

This is going to take a few steps, we’ll need to cover hot and cold signals, signal aggregation, transformations and chaining (!), so hang tight.

Signal transformations

The easiest way to validate that the username, or the password or any other text, are valid is via a transformation. We will transform the user name text into a Bool that will indicate whether the username is valid or not.

To do that we need a signal. Luckily, all MutableProperty<T> have one. The prefered method to transform one thing into another is map.

We won’t be covering all other transformations, but what’s important is that RAC 4 Signal’s map doesn’t return a new value, but a new Signal. Which allows us to do both, biding and chaining.

class ReactiveTests: XCTestCase {
    
    func testCompoundBind() {
        
        let registerButton = UIButton()
        let usernameTextField = UITextField()
        
        registerButton.rac_enabled <~ usernameTextField.rac_text.signal.map { (text) -> Bool in
            
            return text.characters.count >= 8
        }

        XCTAssertFalse(registerButton.enabled)

        usernameTextField.text = "mariano@zerously.com"
        usernameTextField.sendActionsForControlEvents(.EditingChanged)
        
        XCTAssertTrue(registerButton.enabled)
    }
}

As you can see here, what we are doing is mapping the text of the username to whether it’s valid or not. This is what we meant by a stream being a river with all it’s water: we can fully define whether the register button is enabled or not, across time and across values.

There’s a couple new concepts in this test though.

usernameTextField.sendActionsForControlEvents(.EditingChanged) is there so that the proper event’s will be triggered when setting the text by hand. You won’t have to worry about this with a live UITextField on a UIViewController.

For the sake of clariry, let’s wrap that into an extension and assume it’s present in future code.

extension UITextField {
    
    func inputText(text: String) {
        
        self.text = text
        self.sendActionsForControlEvents(.EditingChanged)
    }
}

And then there’s the rac_enabled property that isn’t present on the Util.swift file we mentioned before.

To add new UIKit properties simply add to that file something like this:

extension UIControl {
    public var rac_enabled: MutableProperty<Bool> {
        return lazyMutableProperty(self, key: &AssociationKey.enabled, setter: { self.enabled = $0 }, getter: { self.enabled })
    }
}

But there’s one last a catch here. This test will fail on the first assert. Which brings us to hot and cold signals.

Hot and cold signals

So, why did that test fail on the first assert?

Since we were using a hot signal, the binding will only be set once the value of the textfield changes for the first time. Remember that:

If you get the current value (which was set in the past) alongside with whichever new values are set in the future binded into your property then you are using a cold signal.
If you only start getting the new values (whichever are set in the future) binded into your property then you are using a hot signal.

In this particular case, if we want the enabled to be “computed” false, we need to consider the preexisting username text value, which is nil.

How do we get a cold signal? Using the property’s SignalProducer instead of the Signal. It’s that easy.

class ReactiveTests: XCTestCase {

    func testCompoundBind() {
        
        let registerButton = UIButton()
        let userNameTextField = UITextField()
        
        registerButton.rac_enabled <~ userNameTextField.rac_text.producer.map { (text) -> Bool in
            
            return text.characters.count >= 8
        }

        XCTAssertFalse(registerButton.enabled)
        userNameTextField.inputText("mariano@zerously.com")
        XCTAssertTrue(registerButton.enabled)
    }
}

All tests passing, now we need to validate the password as well, how do we do that?

Signal Aggregation

Have you noticed the name of the test? Exactly, “compound”, what we need to do is combine both signals, the username’s and the password’s.

And here we go, we have the first part of the goal resolved.

class ReactiveTests: XCTestCase {

    func testCompoundBind() {
        
        let registerButton = UIButton()
        let usernameTextField = UITextField()
        let passwordTextField = UITextField()
        
        registerButton.rac_enabled <~
            combineLatest(usernameTextField.rac_text.producer, passwordTextField.rac_text.producer)
            .map { (username, password) -> Bool in
            
	            return username.characters.count >= 8 &&
	                    password.characters.count >= 8
            }

        XCTAssertFalse(registerButton.enabled)
        usernameTextField.inputText("mariano@zerously.com")
        XCTAssertFalse(registerButton.enabled)
        passwordTextField.inputText("pa55worD")
        XCTAssertTrue(registerButton.enabled)
    }
}

I’m sure you used apps with way more creative algorithms. ;-)

There are other kinds of signal aggregation and operations, but we aren’t going to cover that here.

Chaining

While we were dealing with our goal of enabling the register button when both the username and the password are valid, we were unadvertedly using chaining already. combineLatest returns a Signal (or SignalProducer) to which we can perform other transformations (like mapping) and that’s exactly what chaining is all about.

But, this is awful, shall we move this into a View Model?

struct RegisterViewModel {
    
    let username = MutableProperty<String?>(nil)
    let password = MutableProperty<String?>(nil)
    let registerEnabledSignalProducer: SignalProducer<Bool, NoError>
    
    init() {

        self.registerEnabledSignalProducer = combineLatest(self.username.producer, self.password.producer)
            .map { (username, password) -> Bool in

                return username?.characters.count >= 8 &&
                        password?.characters.count >= 8
            }
    }
}

class ReactiveTests: XCTestCase {
    
    func testModelBind() {
        
        let registerViewModel = RegisterViewModel()
        let registerButton = UIButton()
        let usernameTextField = UITextField()
        let passwordTextField = UITextField()
        
        registerViewModel.username <~ usernameTextField.rac_text
        registerViewModel.password <~ passwordTextField.rac_text
        registerButton.rac_enabled <~ registerViewModel.registerEnabledSignalProducer

        XCTAssertFalse(registerButton.enabled)
        usernameTextField.inputText("mariano@zerously.com")
        XCTAssertFalse(registerButton.enabled)
        passwordTextField.inputText("pa55worD")
        XCTAssertTrue(registerButton.enabled)
    }
}

That’s better! See how all our code starts looking like simple bindings?

Next

Part 3: Applied networking with ReactiveCocoa 4.
Part 4: Using ReactiveCocoa 4 on an app.

  1. Notice that this is Objc. RAC 4 is mostly built around Swift.

  2. If you do that, I’d suggest that you change the rac_text type to MutableProperty<String?>. Since that’s the type of UILabel’s text property, so should be the type of the rac_text MutableProperty.