ReactiveCocoa

Notes on ReactiveCocoa 3.0 RC

I'll be honest - I did have real problems getting back to grips with ReactiveCocoa 3.0 since their API overhaul (despite being reasonably familiar with v2 - I even gave a talk on it). I'm getting there now (tacks.cc is written extensively with ReactiveCocoa 3.0 RC), and I'll try to update this post with tidbits from what I've learned because, to-date - even though the RAC codebase is well documented, function to function - there's not enough examples out there for how you actually use this stuff as a whole. Most of the example code that exists is still in Objective-C and generally relates to previous versions of RAC. There are some great blog posts (linked at the bottom), which really helped me figure it out.

Creating a signal

We can create a "pipe" which returns a Tuple pair: the Signal itself, and an 'observer' which we can use to manually send events, - events which pop out of the created signal. They go together.

let (self.locationSignal, self.locationObserver) = Signal<CLLocation, NoError>.pipe()

Signal of empty Events:

If you want a Signal that sends empty Next events, create your signal like so: Signal<Void, NoError>, and just send it Voids: .Next(Void)

You don't always need a real Error type

Signals rarely fail, so it's usually easier just to model them using NoError.

let locationSignal: Signal<CLLocation, NoError>

Using next: to start a new signal after completion - this is gone

In RAC 2, we could use next: to start a new signal once another completes. We don't this anymore - instead we concatenate signals, for example: localFetchProducer |> concat(networkFetchProducer)

Create a SignalProducer from a value

let individualComicProducer = SignalProducer<Comic, NSError>(values:comics)

Resources:

Swift 2.0 - ReactiveCocoa API changes

|> -> .

Aside from converting all usages of |> to simply . ( - much better! we now have code completion! 🎉), a few other notable things tripped me up when switching to the Swift 2.0 api:

.Catch

catch is now a reserved word in Swift 2.0, so you should simply replace all usages with flatMapErrors - it's the same.

.Start

Secondly, the compiler now seems to have problems identifying which default parameter it should use when you pass a closure to start:. Previously it was common to use the following shorthand:

producer.start { value in ... }`

Where we really meant was:

producer.start(next: {value in ...})

The method signature of start: is really:

start(error error: (E -> ())? = nil, completed: (() -> ())? = nil, interrupted: (() -> ())? = nil, next: (T -> ())? = nil) -> Disposable

The Swift 2.0 compiler (Xcode 7 b6, at least) now makes a different decision about what we're trying to do. Due to the fact that we're passing a single block parameter, it now seems to assume that we want to call the start(sink: Event<T, E>.Sink) method instead. This is our clue that something's not right, as we start seeing that our blocks are being passed variables of type Event<YourType, NoError> rather than the expected type.

The solution is easy: either just be clearer about what you're calling by reinstating the next: parameter name:

producer.start(next: {value in ...})`

.. or provide more type information in your closure, to help the poor compiler out:

producer.start { (displayableAnnotations: YourType) in ... }

.Put

We no longer use .put(x) to set the value on a MutableProperty type - .value is now settable, so we just get and set .value directly. This is probably good, though I liked the separation before - it made it more obvious that MutableProperty also dealt in events. But like I say, it's probably simpler now.

Converting a RAC 3.0 project from Swift 1.2 to Swift 2.0

I've been loving ReactiveCocoa 3.0 (now that I've finally grok'd it), and today I was able to switch to the swift2 branch to start updating Tacks's Swift 1.2 code for iOS9.0.

Aside from converting all usages of '|>' to simply '.' instead ( which is much better! we now have code completion! 🎉), a few things tripped me up when switching to the Swift 2.0 api.

.Catch()

Firstly, catch is now a reserved word in Swift 2.0, so you should simply replace all usages with flatMapErrors - it's the same.

.Start()

edit: as of Sept 7th, convenience methods have been added to get around this, i.e. startWithNext, startWithCompleted, startWithError, as well as for observing Signals: observeNext, etc.

Secondly, the compiler now seems to have problems identifying which default parameter it should use when you pass a closure to start:. The method signature of start: is really:

start(error error: (E -> ())? = nil, completed: (() -> ())? = nil, interrupted: (() -> ())? = nil, next: (T -> ())? = nil) -> Disposable

and previously it was common to use the following shorthand:

producer.start { value in ... }`

Which would really mean (I assume because next was the last parameter?):

producer.start(next: {value in ...})

The Swift 2.0 compiler (Xcode 7 b6, at least) makes a different decision about what we're trying to do. Due to the fact that we're passing a single block parameter, it now assumes that we want to call the start(sink: Event<T, E>.Sink) method instead. This is our clue that something's not right, as we start seeing that our blocks are being passed variables of type Event<YourType, NoError> rather than the expected type.

The solution is easy: either just be clearer about what you're calling by reinstating the next: parameter name:

producer.start(next: {value in ...})`

.. or provide more type information in your closure, to help the poor compiler out:

producer.start { (displayableAnnotations: YourType) in ... }

.Put()

We no longer use .put(x) to set the value on a MutableProperty type - .value is now settable, so we just get and set .value directly. This is probably good, though I liked the separation before - it made it more obvious that MutableProperty also dealt in events. But like I say, it's probably simpler now.

Edit: Extra!

Don't forget to remove the Box dependancy as well, because it's no longer needed with Swift 2.0

Swift learning: Integrating ReactiveCocoa into my canonical Swift app

Ok so I got this far without too many issues, so now to go back to my old favourite (or should that be nemesis?) ReactiveCocoa.

I abortively tried to begin learning Swift 2.0 with ReactiveCocoa 3.beta8 together from the get-go (because if it worked, that would be awesome right?): it didn't work because my Swift experience was too patchy, ReactiveCocoa 3 wasn't even finished and, particularly, wasn't ported to Swift 2 yet. (also I was still trying to get used to Xcode after three happy years using AppCode - which unfortunately is still lacking mature Swift support).

Now that the tears of frustration have dried, this time I'll go back somewhat less ambitiously (retreating to Swift 1.2 for my own sanity) and try ReactiveCocoa 3 again, it now being at RC1.

I've developed my ListViewController already with a ViewModel pattern, using callbacks (didChangeContent, didChangeSection etc) to notify the VC of FetchedResultsController changes. This is a perfect point to start and using Signals (or are they called SignalProducers now..?) instead.

ReactiveCocoa 3.0 alpha 1

I had some troubles even installing this build (Swift 1.2).

Firstly, the version of LlamaKit that cocoapods installed wasn't compatible with Swift 1.2 (which RAC3.0 is currently targetted at).

I tried the manual install next, but it seems like there's a step missing. If you get some issues like "No such module Result" or "No such module Box", the solution is here - you have to add more than just ReactiveCocoa.xcodeproj. I filed an issue and the Readme is updated to explain this now.