Some coding goes here

Loading...
You got lucky! We have no ad to show to you!
Advertisement

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!ProblemYou 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:COPYCOPYCOPYstruct User {

let name: String

let age: Int

}

class UserController {

var users: [User] = [] // 1

Loading...
You got lucky! We have no ad to show to you!
Advertisement

func getUsersFromNetwork() { // 2

// get from network stuff

users = [User(name: "Leo", age: 30),User(name: "Ana", age: 26)]

Loading...
You got lucky! We have no ad to show to you!
Advertisement

}

}

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:COPYCOPYCOPY func getUsersFromNetwork(completion: @escaping (Result<[User],Error>)->()) { // 2

// mock a completion handler

Loading...
You got lucky! We have no ad to show to you!
Advertisement

let users = [User(name: "Leo", age: 30),User(name: "Ana", age: 26)]

self.users = users

completion(.success(users))

}

And use it like this:COPYCOPYCOPYclass UserViewController {

let userController = UserController()

init() {

userController.getUsersFromNetwork {

Loading...
You got lucky! We have no ad to show to you!
Advertisement

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: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 BoxTo 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:COPYCOPYCOPYstruct 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.COPYCOPYCOPYstruct 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:COPYCOPYCOPYclass 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:COPYCOPYCOPYclass 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:COPYCOPYCOPYlet userVC = UserViewController()

Generic BoxOne interesting thing that you can do is to turn the UserBox into ah Generic Box like this:COPYCOPYCOPYstruct 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:COPYCOPYCOPY var users = Box<User>()

Written by

irai anbu