Hello future functional programmer!
If you're reading this, I'm assuming you've at least heard of functional programming (or even better, functional reactive programming). If you aren't entirely sure what functional programming is, don't fret, you're not alone. There are tons of great resources online for learning more about what it means to be "functional".
For now, just know the core of functional programming, and what's important to FRP (functional reactive programming), is that there is no mutable state; functions don't have side-effects. In reactive programming, objects react to changes and update themselves. FRP is a beautiful blend of the two. An excerpt from the philosophy page on reactivecocoa.io describes it like so,
Well, we know to some degree what functional programming is. Functions don't have side-effects; there's no mutable state. It's a difficult way to program since the real world is mutable. Modeling things like user input becomes a nightmare. Reactive programming? What's that? Well, the best way to describe reactive programming is to think of a spreadsheet.
Imagine three cells, A, B, and C. A is defined as the sum of B and C. Whenever B or C changes, A reacts to update itself. That's reactive programming: changes propagate throughout a system automatically.
Functional reactive programming is just a combination of functional and reactive paradigms. We model user input as a function that changes over time, abstracting away the idea of mutable state. Functional reactive programming is the peanut butter and chocolate of programming paradigms.
Ok, so far so cool right? But why take the time to learn an FRP framework and start implementing it into your iOS apps?
- Memory & CPU Management -- You can do alot at once, using a lot less memory and CPU than would be possible otherwise. This is due to the way RAC (reactive cocoa) uses streams and threading, which I'll bring up again later.
- Elimination of State -- No more storing objects in one view controller and writing handlers to access certain things during some process and passing bits and pieces between view controllers during segues. The MVVM pattern (similar to the classic MVC pattern) forces the use of View Models which allow the ViewController to handle just UI stuff, while the View Model handles data stuff. This allows for vastly slimmer ViewControllers and much better organized code.
- Encouraged Reduction of Objects -- RAC makes significant use of the map, filter, and reduce operations, along with a few similar RAC-specific operations. These operations encourage stripping down your objects and keeping things simple. It also encourages error catching and elimination of "hacky" on-the-fly type conversions. You'll pass only what's needed, only to who wants to receive it.
Now the fun part
Getting started
I'm going to walk you through creating an app that downloads a large .zip file of hi-res images from the internet and uses a UIProgressView to animate the live progress of the download. The starter project should simply contain the main ViewController set up with IBOutlet mappings and a file directory scaffolded out nicely along with the carthage packages we'll need installed and imported where they need to be.
Reactive Cocoa MVPs
Before we hop into the code, you should know a few most commonly used RAC concepts. I would definitely encourage you to check out the basic operators documentation on the ReactiveCocoa github repo as well.
- RAC works a lot like the children's game telephone. This concept is translated to RAC as a "stream of events". The stream allows listeners to do something with the events (or more accurately the event updates) that they see passed down the stream. In order to have a stream to listen to, we must have someone or something create the stream. So without further ado, I give you Signals! Signals come in two types: hot and cold.
- Hot signals are always "on". They never need to be "kicked" off. These hot signals are simply called
Signal
s in RAC. - Cold signals are signals that include scaffolding for how the data will move from the kick off to one listener to the next and so on. They are almost the same as hot signals, the big difference being, they need to be kicked off because they are not "always-on". In RAC they are called
SignalProducer
s. An important note about the kicking off: it should almost always be kicked off by the last listener.- Another note: general practice is to use almost entirely signal producers and only signals when the "always-on" functionality is necessary.
- The final MVP is the transport mechanism for moving data from one messenger to a listener. This mechanism, which carries the stream, is called the
observer
. Theobserver
is created by aSignal
orSignalProducer
. It works a lot like a telephone conversation. Once created, it willsendNext(value: AnyObject?)
until complete, at which point it willsendCompleted(value: AnyObject?)
.- In terms of a telephone conversation where the caller is saying the phrase "Hello John what is up?", the stream would look something like this: "Hello" -> "Hello John" -> "Hello John what" -> "Hello John what is" -> "Hello John what is up?". The
observer
willsendNext
whenever the value changes or updates. - I know this is all pretty confusing to talk about, but it will start to make more sense as you see it demonstrated in our app example below :)
- In terms of a telephone conversation where the caller is saying the phrase "Hello John what is up?", the stream would look something like this: "Hello" -> "Hello John" -> "Hello John what" -> "Hello John what is" -> "Hello John what is up?". The
- Hot signals are always "on". They never need to be "kicked" off. These hot signals are simply called
Let's begin!
When programming an FRP app, I like to think in reverse. Or in the order of server -> device, rather than device -> server.
So the first thing we're going to do is set up our http GET function within RestClient.swift
.
func fetchPackage() -> SignalProducer {
return SignalProducer { observer, _ in
...
Alamofire.download(.GET, "http://andrewware.xyz/banff/banff.zip", destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
let progress = Float(totalBytesRead)/Float(totalBytesExpectedToRead)
observer.sendNext(progress)
}
.response { request, response, data, error in
if let error = error {
print(error) observer.sendFailed(error)
return
}
observer.sendNext(1.0) // download completed!
observer.sendCompleted()
}
}
}
We declare that we want to return a SignalProducer of type Float. The body of our function is setting up our GET request. As we receive our desired package, we use Alamofire's .progress
function to read the live progress of our download. We use observer.sendNext(progress)
to send the progress value every time it changes. From here, we'll need to set up a listener in the ViewModel that can receive our progress value and pass it along towards the UI.
RAC works like dominoes. Or like the game telephone we all played in school. One thing is responsible for kicking it all off, and other things can in turn listen, and either do something directly or change the message and pass it along to someone else who is listening.
Moving on to our viewModel. First we'll initialize a MutableProperty
of type Float
to hold our progress. Then we'll initialize a private RestClient
.
let progressMutable = MutableProperty(0.0)
private let restClient = RestClient()
Next, we create our downloadPackage
function. In this function we call the fetchPackage
function from our restClient
by calling startWithNext{}
. This kicks off restClient.fetchPackage
and allows us to do something with the next value that comes down the stream each time we receive a next from the observer in fetchPackage
. This is the last listener in the line. We take the progress value and update the value of our progressMutable
.
func downloadPackage() {
restClient.fetchPackage()
.filter{ progress in
return progress != 0.0
}
.startWithNext{ progress in
self.progressMutable.value = progress
}
}
This function will allow us to trigger the download via the download button tap, it filters out any progress value that equals 0.0 (the init value), and it sets self.progressMutable.value
as it gets updated. In the our step, we'll bind progressMutable
to the UI component progressView.
In the HomeViewController, we'll declare an optional HomeViewModel
which we'll initialize within the viewDidLoad
. Within our viewDidLoad
we'll also use the <~
operator to bind the value of progressMutable
to our progressView. This binding is made possible via an RAC UI extension library called Rex. Note: The RAC team has made it clear that Rex is the side project of RAC and will be integrated into RAC5.
var viewModel: HomeViewModel?
override func viewDidLoad() {
super.viewDidLoad()
viewModel = HomeViewModel() as HomeViewModel
/// RAC bindings
progressView.rex_progress <~ viewModel!.progressMutable.producer.observeOn(UIScheduler())
}
That's it!
If you build and run, you should see that upon tapping the download button, the progress bar begins animating the download of this zip package from the server! Visit the file path printed to the Xcode console and you'll find a zip file containing some hi-res images of the beautiful Banff National Park.
I hope you've enjoyed taking RAC out for a test drive. There's tons more to learn about RAC! I would strongly encourage trying to use RAC with any asynchronous functionality in your next iOS app. To learn more, I would suggest reading up on the ReactiveCocoa documentation as well as checking out Colin Eberhardt, who has written a lot on RAC. I especially like his blog post about making a live search twitter app. This app is using RAC3 so there will be a few syntax differences, but all the main concepts will be the same. There's also a nice collection of well-built RAC apps here.
If you've followed me this far, tweet at me and let me know how you liked this blog post!
Happy coding!
Need a fresh perspective on a tough project?
Let’s talk about how RDG can help.