How I implemented my own LiveData for iOS using Swift

I implemented my own LiveData for iOS using Swift. Here’s how:


So, I was working on an app, using the MVVM architecture and I needed a way to notify my ViewController of changes happening in the ViewModel. I knew this could be done with KVO, RxSwift, NotificationCenter or even the Delegate pattern. But as an android developer, I wanted something that will function like LiveData in android, so I went online and found a talk by Ray Wenderlich on ‘MVVM in Practice’. There, he gave a Box model that looks like this:

class Box<T> {
    typealias Listener = T -> Void
    var listener: Listener?

    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    init(_ value: T) {
        self.value = value
    }
    
    func bind(listener: Listener?) {
        self.listener = listener
        listener?(value)
    }
}

With that, all you need to do is to wrap your variable with the Box in your ViewModel and then bind the variable in your ViewController, Just like this:

class UserViewModel {

    var username: Box<String> = Box("Default username")
    
    func setUsername(uName: String) {
        username.value = uName
    }
}
class UserViewController: UIViewController {
    @IBOutlet weak var usernameLabel: UILabel!

    let userViewModel = UserViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        userViewModel.username.bind { (username) in
            //Display new username value
            self.usernameLabel.text = username
        }
    }
}

Looking at the code above, once the username value is updated in the setUsername() function of the UserViewModel, the corresponding listener in the UserViewController is triggered with the new value. Soft right?

So, I thought, how could I make this seem more like LiveData. So I did a few editings. Here it is:

class LiveData<T> {
    typealias Listener = T -> Void
    var listener: Listener?

    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    init(_ value: T) {
        self.value = value
    }
    
    func observe(listener: Listener?) {
        self.listener = listener
        listener?(value)
    }
}

Notice a few changes? The class now has the name, LiveData and the bind function is now observe, meaning you now ‘observe’ your variable for changes instead. The implementation now looks like below:

class UserViewModel {

    var username: LiveData<String> = LiveData("Default username")
    
    func setUsername(uName: String) {
        username.value = uName
    }
}
class UserViewController: UIViewController {
    @IBOutlet weak var usernameLabel: UILabel!

    let userViewModel = UserViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        userViewModel.username.observe { (username) in
            //Display new username value
            self.usernameLabel.text = username
        }
    }
}

Something we can do further is to manage the retain cycle. Right now, observe is retaining self, we need a way to break the retain cycle and still keep the observer as long as we need it (as long as the ViewController is in use). We can introduce the [unowned self] reference, as shown below:

class UserViewController: UIViewController {
    @IBOutlet weak var usernameLabel: UILabel!

    let userViewModel = UserViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        userViewModel.username.observe { [unowned self] (username) in
            //Display new username value
            self.usernameLabel.text = username
        }
    }
}

You can also make use of the [weak self] reference instead:

class UserViewController: UIViewController {
    @IBOutlet weak var usernameLabel: UILabel!

    let userViewModel = UserViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        userViewModel.username.observe { [weak self] (username) in
            //Display new username value
            self?.usernameLabel.text = username
        }
    }
}

Can you see how easy it is to use? And it looks more native than just a ‘Box’. Pretty nice!


Surely, there are a lot more overheads that could be handled, like multiple bindings and disposals, but this solves pretty much a simple problem with just a few lines of code. Why not try it out and let me know what you think?

Gracias! ✌?