Reactive <~ Closure

This article is about how to adapt a closure based api to have a reactive layer on top of it, without modifying any underlying logic!

Ritesh Gupta
Nov 26, 2017 · 5 min read

Motivation

In the last one year, I have played a lot with ReactiveSwift and since then I haven’t looked back to non-reactive style of programming. Reactive programming completely re-wires your brain and gradually it changes the approach of solving a problem. Since I like OSS a lot, these days I’m trying to adapt my existing swift libraries to have a reactive layer as well. Though one can argue that why not re-write them completely using reactive approach. Well adding just a top layer give user the freedom to choose between reactive & non-reactive versions of the same library. This is advantageous because we don’t want someone to not use our libraries just because they don’t want ReactiveCocoa since it’s a relatively bigger dependency (you can checkout Receiver if you are looking for a reactive µ-framework)

Closure based api

Let’s say we are using a framework, DataProvider, which takes care of populating UITableView & UICollectionView. After removing the low level details of the class, it looks like something below,

class DataProvider {
...
var sections: ([Section] -> Void)
...
}

ReactiveSwift

1. ReactiveExtensionsProvider

If you are familiar with ReactiveSwift, there’s a protocol ReactiveExtensionsProvider which marks the foundation of adding a separate layer of reactive apis. This separate layer is provided by a property .reactive. Below you can checkout the extension in more detail,

public struct Reactive<Base> { ... }extension ReactiveExtensionsProvider {    public var reactive: Reactive<Self>
...
}
...
view.alpha // non-reactive
view.reactive.alpha //reactive
view.isHidden
view.reactive.isHidden
...
extension DataProvider: ReactiveExtensionsProvider {}

2. BindingTarget

UIView has properties like –– view.isHidden, view.alpha etc. We already know that isHidden is of type Bool and alpha is of type CGFloat. Now if we checkout the types of their reative counterparts like view.reactive.isHidden, we can find out that it's BindingTarget<Bool> rather than simply Bool. Similarly view.reactive.alpha is of type BindingTarget<CGFloat>. So if we want a property which we want to bind, we need an instance of BindingTarget of that corresponding property. Let's try to add a corresponding reactive property for sections in DataProvider so that we can do something like dataprovider.reactive.sections which will have type BindingTarget<[Section]>,

extension Reactive where Base: DataProvider {

var sections: BindingTarget<[Section]> {
return makeBindingTarget { (dataProvider, newSections) in
dataProvider.sections(newSections)
}
}
}
  • var sections: BindingTarget<[Section]> –– We have already discussed it above i.e. if we have a closure api dependent on [Section] then we would want it’s reactive counterpart of type BindingTarget<[Section]>.
  • makeBindingTarget { (dataProvider, newSections) in } –– It’s an in-built function inside ReactiveSwift framework which returns an instance of BindingTarget. It has a closure based initialiser which takes two arguments as part of a tuple i.e. an instance of the Base class (DataProvider) and the input ([Section]).
  • dataProvider.sections(newSections) –– This is the internal underlying logic which we want to use when we get new values of sections so that we can trigger the table/collection to update.
let dataProvider = DataProvider()// reactive
dataProvider.reactive.sections <~ ... // acts like a target
// non reactive
dataProvider.sections(...)

3. BindingSource (Signal)

Just now we saw how we can reactively get the sections which allows us to bind it with other functions. Now what if we want to observe the sections reactively as well. To achieve it, there's another protocol –– BindingSource which simply means it can act like source of values which we can listen or bind forward. We already have a class Signal comforming to BindingSource, as part of ReactiveSwift framework, to make our job easier. Let's see some UIKit examples to understand the flow,

textField.reactive.continuousTextValues // Signal<String?>
button.reactive.controlEvents // Signal<UIControlEvents>
extension Reactive where Base: DataProvider {

var sectionsSignal: Signal<[Section]> {
return Signal { observer, _ in
base.sections = { sections in
observer.send(value: sections)
}
}
}
}
  • base.onNext –– base is the instance of the custom class which we want to extend to have a reactive layer. In this case it's DataProvider, that's why we can access sections property which is nothing but a closure.
  • observer.send(value: sections) –– whenever someone calls base.sections(), sectionsSignal will emit a new value of sections by calling the method observer.send().
// acts like a source of values
... <~ dataProvider.reactive.sectionsSignal

Conclusion

Thus without modifying any underlying logic of DataProvider or creating a new class, we are able to extend it to make it plugeable with other reactive apis. The protocols or classes used above are –– ReactiveExtensionsProvider, BindingTarget, Reactive & Signal (BindingSource) Let's the compare the final api,

extension DataProvider: ReactiveExtensionsProvider {}extension Reactive where Base: DataProvider {

var sections: BindingTarget<[Section]> {
return makeBindingTarget { (dataProvider, newSections) in
dataProvider.sections(newSections)
}
}

var sectionsSignal: Signal<[Section]> {
return Signal { observer, _ in
base.sections = { sections in
observer.send(value: sections)
}
}
}
}
let dataProvider = DataProvider()// reactive
dataProvider.reactive.sections <~ ... // used as a target
... <~ dataProvider.reactive.sectionsSignal // used as a source
// non-reactive
dataProvider.sections(...)
dataProvider.sections = { sections in ... }

Swift Sundae 🍨

Every sundae has a different flavour, so does best…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store