How do you do Key-Value Observing in Swift? Can you? And what about just using Objective-C’s native KVO mechanism? What follows is a gentle introduction to ReactiveCocoa’s unsung hero of property observation: Property (and MutableProperty).

Matt Thompson explained the ins and outs of KVO in exasperating detail in 2013 for NSHipster. In summary, let’s just say it’s a messy Objective-C API, which still happens to work in Swift today. Aside from the occasions where you can’t do without (NSProgress anyone?), I would advise against using it. Its complexity makes it prone to bugs. But Swift doesn’t offer a native equivalent, you say…

I hear you, but suppose you could simply do something like this, to listen for changes and reload a table view in response, picking up any new items:

override func viewDidLoad() {
    super.viewDidLoad()

    viewModel.drafts.signal.observeValues { [weak self] drafts in
        self?.tableView.reloadData()
    }
}

Meet ReactiveSwift Properties

I would be omitting an important tidbit about Swift’s native observation capabilities, if I neglected to mention the didSet and willSet property observers. With enough boilerplate code, you can definitely cobble together something akin to KVO. You could, for instance, listen to the property changes yourself with didSet and re-dispatch these events over a delegate. Purists will prefer this approach, because no magic is involved, and no third-party libraries are required.

However, ReactiveSwift aims to encapsulate all this complexity by means of a singular concept, called Property. Simple, because at its core, Property is merely a value box, with a means to notify others of its changes. Here’s the pared-down protocol, taken from the 1.0 release of ReactiveSwift (github.com/ReactiveCocoa/ReactiveSwift/blob/1.0.0/Sources/Property.swift):

public protocol PropertyProtocol: class, BindingSourceProtocol {
	associatedtype Value

	var value: Value { get }
	var producer: SignalProducer<Value, NoError> { get }
	var signal: Signal<Value, NoError> { get }
}

The producer and signal properties are the actual observables you subscribe to for changes. Why the distinction? The RxJS README has a nice summary:

“Cold observables start running upon subscription, i.e., the observable sequence only starts pushing values to the observers when Subscribe is called. Values are also not shared among subscribers. This is different from hot observables such as mouse move events or stock tickers which are already producing values even before a subscription is active.”

When applied to ReactiveSwift, cold observables correspond to signal producers, while hot signals are represented by “regular” signals. In the context of properties, this boils down to:

  • Starting the producer will emit the property’s initial value, as well as any subsequent changes;
  • Observing the signal will only emit the values changed after the observation was started.

Usage

The concrete implementations of PropertyProtocol are contained by the classes Property and MutableProperty. Generally, you’ll be using the mutable variant, since it allows you change the property’s underlying value, thus triggering change notifications. The easiest way is to forego optionals and use let constants instead:

// Defining the property
let avatarUrl = MutableProperty("https://httpbin.org/image/jpeg")

// Setting the property's value
avatarUrl.value = "..."

// Listening for changes, without the initial value
avatarUrl.signal.observeValues { url in
}

// Listening for changes, including the initial value
avatarUrl.producer.startWithValues { url in
}

Data binding

Data binding is a means of tying properties to UI components, so that the component updates itself whenever the property changes. Contrary to AppKit, iOS’ UIKit has no notion of data binding. ReactiveCocoa exposes data binding through a single custom property: a less-than sign followed by a tilde.

<~

Its shape emphasizes the binding’s unidirectionality. The following example clearly expresses the fact that the artist name is being set on the artist label’s text property whenever it changes, not the other way around:

// Hook up bindings in your controller's viewDidLoad()
func viewDidLoad() {
    super.viewDidLoad()
    artistLabel.reactive.text <~ viewModel.artistName
}

// Example implementation of this viewModel (details omitted for brevity)
class ViewModel {
    let artistName = MutableProperty<String?>(nil)
}

Data binding is a declarative way to express relationships between data and UI components. It reduces the cognitive overhead of manually assigning values. Code locality is also improved, as this approach centers binding logic into a single place.

ReactiveCocoa & ReactiveSwift

What’s the difference? With ReactiveCocoa 5, the core team decided to split up the codebase, in order to more clearly delineate each component’s responsibility and scope. This division also allows framework users to more easily cherrypick which modules they need in their projects.

  • ReactiveSwift: The new kid on the block. This is a pure-Swift implementation of the reactive API. It provides all the nuts and bolts, including Signals, SignalProducers, Properties, Actions and Bindings.
  • ReactiveObjC: ReactiveSwift’s Objective-C-based sibling. This is essentially ReactiveCocoa 2.0 and remains in maintenance mode.
  • ReactiveCocoa: The connecting piece between UIKit and ReactiveSwift. It provides all of the reactive extensions for UIKit components and exposes binding targets for them. It also introduces Triggers, a means for converting Objective-C selectors into signals. You can also wrap good ol’ ObjC KVO with signals, so they can be interspersed with ReactiveSwift signals and producers.
  • ReactiveObjCBridge: Bridges the ReactiveCocoa 2.0 API with the ReactiveSwift API, so ReactiveObjC remains accessible from Swift, enabling interoperability with old code.
Previous ArticleNext Article

Leave a Reply

Your email address will not be published. Required fields are marked *

A.

Android device debugging on Linux Mint: “error: insufficient permissions for device: udev requires plugdev group membership”

As I rejoiced in the achievement of finally finding a Linux distro which plays well with my Dell XPS 13 (2019 edition, model 9380), I plugged in my Moto test device, intending to continue working on an Android app. Pressing the Run button promptly made Android Studio (3.4) do its compilation magic, but got stopped in its tracks rather quickly. An angry-looking error message awaited me:

error: insufficient permissions for device: udev requires plugdev group membership

Oops, no device debugging for you.

Android Studio also left a note pointing me to their developer page on the subject. As I suspected, it seemed I had some configuration work left to get device debugging working on Linux. However, following Google’s instructions on setting up adb didn’t do much to resolve my problem. The part about adding yourself to the udev group is important, though, as it’s linked to the actual solution described in the next paragraph.

sudo usermod -aG plugdev $LOGNAME

Adds your user to the plugdev group.

Update: Someone mentioned recently that the below steps may not actually be required; just logging out and back in again at this point, should also do the trick. YMMV of course :-).

An ill-timed dog walk (let’s just say I hadn’t expected to be caught in the middle of a downpour), and a few mildly frustrated Google searches later, I ran across a blog post from 2013 (!) on the subject of “Adding udev rules for USB debugging Android devices“, by Janos Gyerik.

I will not pretend to know why this extra configuration is required, but it describes looking up the device’s identifier and adding it to the aforementioned plugdev access group, so Android Studio can properly access the USB device.

And there you go, a working USB debugging connection to my Android device, thanks to some great advice from 2013!