Reactive Swift - The Boxing Technique

Reactive Programming Series

Subscribe to my newsletter and never miss my upcoming articles

Hello my brilliant readers, Leo here.

Today we'll explore one of the oldest reactive techniques that you can easily implement in pure Swift. But why would you do that if you have Combine or RxSwift. Well, maybe you don't want to add all the complexity of Combine or RxSwift to your code base but still want to have a reactive code. Reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. This is has a special fit for mobile developers because all that we do is to wait the user to do something and react to that.

Let's code!

Problem

You want that your properties of your objects could have custom closure-based reactions.

The technique we'll go through today is called Boxing. But what is it? Let's examine the code above:

struct User {
    let name: String
    let age: Int
}

class UserController {
    var users: [User] = [] // 1

    func getUsersFromNetwork() { // 2
        // get from network stuff
        users = [User(name: "Leo", age: 30),User(name: "Ana", age: 26)]
    }
}

Imagine that you have this very common structure of Controller. You have a func (2) that get users from network and add to a local variable the result of it. Generally the UserController would use some Closure based completion, that we can inject in the method what we want to do after it completes processing, something like this:

    func getUsersFromNetwork(completion: @escaping (Result<[User],Error>)->()) { // 2
        // mock a completion handler
        let users = [User(name: "Leo", age: 30),User(name: "Ana", age: 26)]
        self.users = users 
        completion(.success(users))
    }

And use it like this:

class UserViewController {

    let userController = UserController()

    init() {
        userController.getUsersFromNetwork {
            switch $0 {
            case .success(let users):
                print(users)
            case .failure(_):
                print("yeap was an error")
            }
        }
    }

}

let userVC = UserViewController()

Getting the printed result in the init method:

Screen Shot 2021-02-18 at 08.12.47.png

And yes, this works just fine. The problem here is that our func is reactive based on his response we aren't getting reactively the users from the controller. You could have more methods calling API's and all of them must implement the completion handler, this is not a very comfortable API to work with, so we can use boxing to react each time the user is set, this way we only deal with ONE closure for all network calls of UserController.

The Box

To do the box struct it's pretty straightforward. We just need a struct that wraps the value and also could receive a closure that is executed each time the value changes. First we start adding the properties:

struct UsersBox {

    typealias Action = ([User]) -> Void

    private var value: [User] { // 1
        didSet {
            action?(value)
        }
    }

    private var action : Action? // 2

    init(value: [User]) {
        self.value = value
    }
}

This is interesting because the design of this struct on 1 and 2 marks has conformity with the open-close principle that says "the software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification". We making the properties private in Swift tells the compiler that no one could modify this behavior, what is intended.

And now we will add the functions that handle with the bind of the closure and with new values.

struct UsersBox {

    private var value: [User] {
        didSet {
            action?(value)
        }
    }

    typealias Action = ([User]) -> Void

    private var action : Action?

    init(value: [User] = []) {
        self.value = value
    }

    mutating func set(value: [User]) { // 1
        self.value = value
    }

    mutating func bind(action: @escaping Action) { // 2
        self.action = action
        action(value)
    }
}

The 1 and 2 marks are functions that helps us with the box. 1 mark just set new values and the mark 2 is where we get the reactive behavior of it. So now, every time the list os users changes, we can react to that, independently of what function triggered that change. That's great, isn't it?

Now we came back to the UserController and use the UsersBox instead of the the plain array of users:

class UserController {
    var users = UsersBox()

    func getTwoUsersFromNetwork() { // 2
        // get from network stuff
        let users = [User(name: "Leo", age: 30),User(name: "Ana", age: 26)]
        self.users.set(value: users)
    }

    func getOneUserFromNetwork() { // 2
        // get from network stuff
        let users = [User(name: "Leo", age: 30)]
        self.users.set(value: users)
    }

    func getThreeUsersFromNetwork() { // 2
        // get from network stuff
        let users = [User(name: "Leo", age: 30),User(name: "Leo", age: 30),User(name: "Leo", age: 30)]
        self.users.set(value: users)
    }


    func configureUsersBox(completion: @escaping ([User]) -> Void) {
        users.bind(action: completion)
    }
}

Here I add other mock methods just to test and prove that they all are responding to changes reactively. Int the UserViewController we can now just do one bind, and all the network calls will be reactively fulfilled:

class UserViewController {

    let userController = UserController()

    init() {
        userController.configureUsersBox {
            print("Users: \($0)")

        }
        userController.getTwoUsersFromNetwork()
        userController.getOneUserFromNetwork()
        userController.getThreeUsersFromNetwork()
        print("update table/collections views whatever you want")
    }
}

And we get the result of calling UserViewController:

let userVC = UserViewController()

Screen Shot 2021-02-18 at 08.51.19.png

Generic Box

One interesting thing that you can do is to turn the UserBox into ah Generic Box like this:

struct Box<T> {

    private var value: [T] {
        didSet {
            action?(value)
        }
    }

    typealias Action = ([T]) -> Void

    private var action : Action?

    init(value: [T] = []) {
        self.value = value
    }

    mutating func set(value: [T]) {
        self.value = value
    }

    mutating func bind(action: @escaping Action) {
        self.action = action
    }
}

And modifying user controller to use the generic type:

    var users = Box<User>()

Conclusion

Today we went through some aspects of the reactive programming, had a chance to see a very basic implementation of it using generics and closures, and how our code could me more reactive simply adding a helper structure called Box.

I hope you enjoy reading and if you have any thoughts or comments please share below.

Thank you for the reading and... that's all folks.

credits: image

No Comments Yet