How I Forgot Everything I Knew About Programming and Relearned it All Using ReactiveCocoa and MVVM
When I walked into my first day of work at Timehop this past summer, I wasn’t sure what to expect. I had just graduated from college and had spent my spare time during the school year doing contract work for them. This mainly consisted of fixing bugs and adding small features.
On that first day, I sat down with Chris and he briefed me on what was going on in the iOS world at Timehop. He told me about TimehopThree, which was meant to be a complete rewrite of the iOS app that would take full advantage of ReactiveCocoa and MVVM. I had heard of ReactiveCocoa before, but knew nothing about it. MVVM, on the other hand, I had never heard anything about that. He gave me a quick overview of what MVVM was and how it made more sense for our app than MVC. I was convinced pretty quickly on that front.
MVC never really seemed to fit in the world of Cocoa to me having seen many views and controllers mashed into giant `UIViewController` subclasses that quickly grew out of control. A lot of `UIViewController` code was typically dedicated to formatting model data for the views. In MVVM, this was abstracted out into a concept called view models. While this does mean more files in your project, it also means much leaner files; combined with ReactiveCocoa, it also means that the majority of your code will be in the `-init` or `-viewDidLoad` methods. A good example of this would be a view that displays an image with a loading indicator.
ReactiveCocoa is largely made up of a construct called signals. A signal is essentially a pipe that sends values. Looking at the above code you’ll see a lot of `RAC()` and `RACObserve()` — macros defined by ReactiveCocoa. `RAC()` will continually set some variable to whatever a signal sends, whereas `RACObserve()` is essentially a wrapper around key-value observing (KVO) that returns a signal that sends whenever the value of some property changes. Back to the example, we’re binding the image view’s `image` property to the view model’s `image`. MVVM allows us to abstract out the image fetching logic into the view model, which in MVC would typically be contained to the view controller. The abstraction of this logic, along with the binding, allows the view to be really dumb in that it knows nothing about where the image came from, just that it needs to be displayed.
Next, we’re binding the progress indicator’s `progress` property to the view model’s `progress`. We don’t want to show the progress indicator when there’s an image, so we can observe the view model’s `shouldShowProgress` property which maps whether the image is nil or not to a boolean. A similar method is used to display an error view when an error occurs. The view model uses the `-distinctUntilChanged` method to withhold sending values until the value actually changes, this ensures that we only create a new `ErrorViewModel` when the error actually changes. The `-ignore:` method allows us to prevent values from continuing down the signal chain, in this case, we don’t want to create an `ErrorViewModel` without an error.
The image view example is a good example of how ReactiveCocoa and MVVM excel on a small scale, but as I continued using it on larger and larger scales, the benefits started to become even more apparent. At first, none of it made any sense to me. I spent the next week pouring over the ReactiveCocoa docs, but no matter how much I read, signals, streams and commands were all just words to me. Looking at sample code, all I saw were unnecessarily giant method calls and an unsettling amount of square brackets. Reading about programming never really helped me get anywhere. Looking back, I’m still glad I took the time to read over the documentation. It gave me a good overview of what functional programming was, even if it didn’t mean much to me at the time. I knew the only way to get a handle on it was to actually write some code. So I set off to build a CitiBike app for the iPhone.
It was pretty difficult to get started. A lot of the initial code and architecture was lifted from TimehopThree. I came into work on multiple occasions and sat down with Chris as he helped me fix bugs I had run into the night before, or explained to me how I should architect some component of the app. I managed to get the app working. And it got me to the point where I felt comfortable writing code for TimehopThree.
While I now had a greater understanding of ReactiveCocoa & MVVM, I was still skeptical. There were so many crazy techniques I had to use in my CitiBike app to get trivial tasks done.
“I have to add a command to the view model that sets a property on the model? Why can’t I just set the property directly from the view?”
Because of this, I found writing functional MVVM code to be much slower than writing imperative MVC code. It didn’t seem worth it to me. Why should I throw out several years of experience writing imperative code?
It wasn’t until I dove head first into the TimehopThree code that I started to see the light. The original Timehop iOS app was written in just a few weeks. Compromises that were made to ship the app sooner started to come back to bite us and slowed our ability to ship new product. All of that technical debt became too much to bear. So when I started to see how the Timehop 2 code translated to TimehopThree (and understood it), I was sold.
After spending another few weeks with ReactiveCocoa and MVVM, I knew there was no going back. One of the great things about this approach is that you’re forced to put much more thought into your code. We take the approach of strictly giving objects only the information that they need by passing the necessary information in through the object’s constructor. This can be a huge headache when you find that a view very far down the chain needs to know something about the user, for example. You’ll quickly find yourself modifying the constructor method signatures of several different classes to get at that information. This could easily be solved by using singletons. Need the user? Just do `[THUserSession currentUser]` and you’re done. This is great if you want easy access to the user all over your app. But what happens after you log out and someone else logs in? Has the view that depends on that user object already been created? Is it showing stale data now? Using ReactiveCocoa, most of these questions are moot as you just observe the UserSession and react (pun intended) to its changes by updating the view to reflect the most up to date state.
I’ve learned a lot since my CitiBike app. I plan to release it to the App Store someday, but for now it’s open source on Github. TimehopThree is done and we released it a few weeks ago. We did run into some performance issues from relying so heavily on ReactiveCocoa, but nothing we couldn’t handle. After several months of programming with ReactiveCocoa and MVVM, I can’t imagine ever going back to writing Objective-C code “the old way.” I’m even holding off on Swift until ReactiveCocoa 3.0 is ready.
ReactiveCocoa’s learning curve is steep. If you’re unsure of whether or not you should get into it, start off by reading the readme. Don’t be discouraged if you don’t understand everything right away. If you want some more hand-holding, Ash Furrow’s Functional Reactive Programming on iOS was a huge help for me. And don’t forgot to put what you learn to practice. Start programming with ReactiveCocoa and MVVM. Build an app that has a similar structure to my CitiBike app, or Justin Spahr-Summers’ GroceryList. But most of all, remember to be patient. Like anything worth learning, you won’t learn this stuff overnight. And when you’re ready, feel free to give us a call.
If you’re in the NYC area and are interested in learning more about how we use ReactiveCocoa and MVVM in Timehop, Chris will be giving a talk about it at the iOSoho meetup on March 9th, 2015.