Simple Asynchronous Swift code with ReactiveCocoa 4
How using ReactiveCocoa's tools can be valuable for an iOS engineer
Welcome. It's such a pleasure to be back at Swift Summit. Thank you all for coming here. So a quick show of hands - how many of you saw, either online or in London, my first talk at that Swift Summit? Just so I get an idea of... Okay. That's okay. You don't have to watch the talk to understand any of this, but this is kind of a chapter two so I encourage you to, if you have some free time, check it out.
00:47: To summarize, in that talk I went over the benefits of abstraction in asynchronous code with an implementation of futures with an emphasis in error handling. Let me summarize the content very quickly. What it showed is, one way in which we can leverage some of Swift's APIs and features to implement some abstractions that allow us to greatly simplify this type of code. In this example, we have a function that composes itself with two other asynchronous functions to perform some work. This is a lot of boiler plate. So I showed how we can remove some of it by implementing a future API to get to something like this. Today, I'd like to take this a step further but first I wanna talk about why. Why do I care so much about this that I'm giving not only one but two talks about futures and asynchronous APIs? Well, the fact is that asynchrony is all over the place in mobile apps. We have all these things that one way or another force us to deal with asynchrony: KVO, networking, mutable state of some sort.
Well, the fact is that asynchrony is all over the place in mobile apps.
02:11: So by this, I don't only mean multi-threading per se, even things that are single threaded like animations introduce some sort of asynchrony. Essentially, a lot of things in our applications are dependent on time and this just makes it a whole lot harder to build mental models around the flows of data and information within our apps. My brother, Nacho, gave a great talk about this but I'm not gonna repeat the same things to avoid you confusing who's who. But I really wanted to reference it here because I really liked it not just because he's my brother, and I wanted to encourage you all to go watch it, it's on the awesome Realm website. But the TLDR is that this stuff, it's just really hard and if you don't think that asynchronous code is hard, we're hiring.
So why is asynchronous code hard? Well, for starters we write code in languages kind of what like Andy talked about, they're kind of old and these languages and paradigms were not necessarily designed with this in mind. So, it turns out that when we're writing asynchronous code, we constantly find ourselves needing to deal with these same problems over and over. Cancellation and throttling and re-trying and threading, and all these things. So the logic to solve those problems kind of ends up sprinkled all over our application and wouldn't it be great if we just focused on the domain problems of our application and we leverage abstraction to hide away all that complexity?
So today I wanted to encourage you all (...), to challenge your ideas often and to be open to the idea that our tools may be imperfect.
03:57: I've seen several code bases grow from tiny and manageable to huge and I've spent a lot of time wondering and researching and trying to figure out just better ways and patterns and tools to help me write better software with less headaches. So today I wanted to encourage you all to do the same, to challenge your ideas often and to be open to the idea that our tools may be imperfect. I wanted to share with you all some of the things that I've learned in the past couple of years as I kind of began on this journey of figuring out how to better architect iOS apps because ever since I've started working on bigger code bases, this idea kept coming back to me. There has to be a better way, right? The fact is that, I think it's really easy for all of us as programmers to get comfortable with what we know and are familiar with because once we get used to it we get this sort of Stockholm syndrome and even Xcode seems to be okay.
I like to remind myself of this often, kind of as a way of motivating myself to not settle and keep pushing my boundaries, try to get out of my comfort zone and get better everyday. In this journey ReactiveCocoa was a huge discovery. I started using it back in Objective-C and lately thanks to Swift, it just got a lot better. But for awhile I was really hesitant to even recommend it to people. It was just not very approachable, but today I believe it's so much more mature and to me it's an indispensable tool not only to build iOS apps but just to think about software in general. I wanna share some of that with you.
What I wanna share with you is that there's a lot of power in these ideas and I believe that they're incredibly useful...
06:00: I wanna make a disclaimer. I'm not trying to sell anything. I don't get paid to sell ReactiveCocoa. Really at the end of the day, while I may have a personal preference, I really don't mind if you choose to adopt ReactiveCocoa or one of the alternative libraries that have come out like RxSwift or ReactKit. What I wanna share with you is that there's a lot of power in these ideas and I believe that they're incredibly useful and I encourage you to give them a chance if you haven't already. But let's be real, ReactiveCocoa is hard. Many of you have probably heard about it already. In fact, some of you are probably already bored about hearing about ReactiveCocoa. I'm not gonna go over every single feature in 20 minutes. However, I'd like to discuss the philosophy a little bit, and hopefully, it'll make more sense when I say that I think it's very valuable. But before I talk about the good things, let's be realistic. It is hard, and why is it hard?
Well, for starters, the syntax is just unfamiliar. It's not what we're used to. If you've been doing Cocoa for a while, the patterns are gonna be completely foreign. But one of the ideas that stuck with me is that ReactiveCocoa doesn't necessarily attempt to make it easy, in the strict meaning of the word easy, because easy implies familiarity. Of course, ReactiveCocoa, is not going to be familiar at first. But what ReactiveCocoa attempts is to make asynchronous code simple. Simple in this context, I'm referring to a talk that Rich Hickey gave that I really liked; I've watched it like 10 times, it's called, “Simple Made Easy”. He defined simple as strictly the opposite of complex. ReactiveCocoa tries to bring very few patterns and very few concepts to the table. With those few concepts and ideas, we can do a lot of the things that we've seen that asynchronous programming needs to solve with much simpler code. That is the goal of ReactiveCocoa. Now it's of course not going to be easy to learn, but hopefully, it'll be worth it in the long run. Because mobile apps are increasingly complex, they're not toy apps anymore. As hardware got better over time, and this picture from an Apple keynote, we can see that CPU performance increased by 50 times since the original iPhone.
...what ReactiveCocoa attempts is to make asynchronous code simple.
08:43: As that happened, we just crammed more functionality into apps. Every app evolved to be a fully fledged chat client. Even some apps became an operating system with 18,000 classes. So I think we need sane ways to model our apps so that we can spend more time making great features and less time worrying and figuring out things like risk conditions. These are all concepts that at some point we needed to learn in order to use Cocoa APIs. This is a very long list. Even though each of them is slightly different, they kind of solve similar problems around asynchronous data flow. ReactiveCocoa unifies all of these under one umbrella, one API to understand. Even though it takes a while, once you get it, that's all you need to know. You don't need to learn 10 different patterns like in Cocoa, and of course, none of us learned all these overnight.
This is ReactiveCocoa's core; Signals.
This is ReactiveCocoa's core; Signals. By representing all those mechanisms in the same way, it's easy to declaratively chain and combine them together with less spaghetti code and state to bridge them, to bridge between all those worlds. A signal is simply a pipe that carries events of these types, either next with a value or failed with an error or completed or interrupted. Just this allows us to represent all the things that I think we need when we build asynchronous APIs, like cancellation or errors and things like that.
10:31: Now if you dig into ReactiveCocoa's APIs, you're quickly going to run into this, so I wanted to get it out of the way. This distinction between Signal and SignalProducer was introduced in ReactiveCocoa 3. It helps us understand the effect of observing something. So I wanted to briefly discuss it because using an asynchronous API can take two shapes like in this example. The first one returns the SignalProducer, and what this API establishes is that the contract says; whenever you observe this thing, some work is going to happen. The second one, on the other hand, when observing that Signal, we can know that that won't cause any side effects. This distinction may seem very subtle, but compared to ReactiveCocoa 2 or some other Rx implementations or even called out block-based APIs, it makes using these asynchronous APIs a lot easier to understand and predict and more importantly harder to misuse.
...the beauty of the Signal obstruction is the varied set of operators that we have that let us manipulate the values that are carried through in the Signals.
It's important to realize that this doesn't mean that we need to learn to completely separate APIs because for the most part, their API surface is very similar and they're used in a very similar way. But the beauty of the Signal obstruction is the varied set of operators that we have that let us manipulate the values that are carried through in the Signals. In a very declarative and uniform way. You've probably seen this example a million times but I wanted to explain with my own words how I feel about Declarative versus Imperative and why I think there is a lot of value in this. This is an example of using the array type. So if we have an array of strings and we wanna get another array of strings where each string is an upper-case, the Imperative way would have code that would essentially mix a little bit of code that communicates with other developers what we're trying to do.
With other code, that's a little less useful. It just communicates with the machine how we're doing that thing. Whereas in the Declarative way, we focus on the what we're trying to accomplish. This is even more obvious when we do asynchronous, when we solve asynchronous problems. This is a parallel example of those same two approaches of problem solving. This is trying to throttle the number of requests that we make based on a search query that the user types. The imperative approach requires all that logic and state. I'm not even sure if that's correct. It probably isn't. I think this is what's really powerful about ReactiveCocoa's built-in operators. They encapsulate really complex logic in very simple APIs. Like in this case, he's using the throttle operator. These are some of the operators in ReactiveCocoa. It may seem pretty daunting. It's quite a long list and I think it's not complete, but what I wanted to draw your attention to is that one does not need to know all of them in order to use ReactiveCocoa. I remember when I started, I only knew a few but I had the feeling that with that, I could accomplish like 90% of what I needed to do. Once you understand those, you can easily pick up more and more as you go.
One of the most common criticisms about Swift is that it's too terse.
15:00: Let's kind of switch topics here. Let's talk about KVO because they kind of go hand in hand in these discussions. One of the most common criticisms about Swift is that it's too terse. Something like its static nature is too strict. And one example that's often given of that is that it doesn't let you do KVO or you can do KVO but it has limitations and it's not great. KVO is one of the most powerful concepts to me that I learned when I started doing iOS. In fact, ReactiveCocoa 2 used to rely really heavily on it. But I argue that KVO was also incredibly fragile. Let's see why I think this is the case.
These are all problems that I've run into when using KVO. While the idea of KVO is great, its API has a lot of reasons to make your app crash, and it doesn't precisely help you write small maintainable functions. All the observations come through in the same method. But to me, one of the biggest problems is that this concept is not explicit. When we try to use KVO with an API, there is no contract that says, "This property is observable and you can subscribe to changes." You could think, "Oh well, it's documented, right?" Well, in most cases, it's really not documented or even if it's documented, I think there is still to this day, there's behavior that doesn't work as you would expect if you have a weak property that turns to nil, that's not gonna fire a KVO notification. It all comes down to the fact that KVO is not an explicit API. We can guess that it works but we can't really know for sure that it does.
...what Property allows us to do is to declare a member of an object as explicitly observable.
So let me talk about one last tool from ReactiveCocoa's toolbox, it's called Property. And what Property allows us to do is to declare a member of an object as explicitly observable. As in the fact that the values of a Property can be observed is part of the API contract, and the client of the API doesn't have to guess or to trust Stack Overflow or something like that.
16:34: So here's an example of how that works compared to KVO. The producer, if you look at the bottom, the producer that we can get out of the value property will automatically complete when the object is de-allocated, and this is a great benefit compared to KVO because an object that we're observing being de-allocated before we de-register would cause a crash. There’s also no strings involved. We don't need to use the ”value string” and the benefit of code locality means that the code that reacts to changes to the property is really close to where we observe it as opposed to in two different functions. So the next time that you hear, "Yes, Swift doesn't let you do KVO," show them this. I think this is much better than KVO. Okay, so if I've done an okay job so far, you're all probably like, "Yeah, okay this is cool but I'm not gonna rewrite my whole app to use ReactiveCocoa," right? So what I'd like to stress today is that ReactiveCocoa is not all or nothing. It's not like UIKit in that sense. If you're using UIKit, you kind of have to use View Controllers and UIViews all over your app.
...it's possible to start using ReactiveCocoa in some small part of your application, learn and benefit from it there while leaving the rest of the app untouched.
However it's possible to start using ReactiveCocoa in some small part of your application, learn and benefit from it there while leaving the rest of the app untouched. So my suggestions, start small, try adopting it maybe in the API layer or in some View Controllers, perhaps in wherever there is really heavy asynchronous logic; I think it's worth it. So these are a few conclusions that I'd like you to take away. Remember that our tools may be imperfect, and strive to reconsider pattern, seek better ways of accomplishing and solving those problems and there is a lot of value in these obstructions even though they may be hard to start using and learn. These are some links that you can find when I publish these slides online, if you wanna watch the talks that I referenced. Thank you.
Q1: There are two things that I'm wondering about, one is; what ReactiveCocoa gives me beyond Futures and Promises, which I'm just getting my head wrapped around. The bigger worry I have is the debugging story. Whenever anyone comes up with a new package or library or language and you do stuff and it doesn't work, then what do you do to open up the black box and figure out what's going wrong?
Javier: Yeah, that's a great question. I think debugging is definitely one of the biggest concerns when you use ReactiveCocoa. I'll speak from experience. When I started using ReactiveCocoa in Objective-C that was definitely one of the hardest things and a debugger is sometimes not gonna help you, so you end up printing to the console the values that come through the Signals and it is pretty cumbersome. What I found is that when using it in Swift, there are much fewer reasons that you end up having to use a debugger in the first place. Thanks to generics, in particular, a lot of the time, if the app compiles and ReactiveCocoa and the Swift compiler, doesn't complain about your code, things are gonna work for the most part. But yeah I think there is a lot of room for improvement and for tooling to make debugging Signals and things like that much easier.
Audience: And could you say a word about why to switch to Reactive after learning Promises or Futures?
Javier: That's a good question. I think the short answer would be that Signals are a superset of Promises and Futures. While those are really valuable and I definitely recommend it, that’s what my first talk was about, it's gonna simplify your asynchronous code around networking and things like that. They do come with limitations, they're not gonna work with other things like streams of values that you encounter when you work with things like gesture recognizers, for example. You could use Futures and Signals, but then you have the problem that I was talking about where you have two different sets of APIs and patterns. The great thing about Signals is that it solves both those things in one unified API.
Q2: So I liked your example with the KVO because I think in our app currently we don't use ReactiveCocoa, but I identified a good point where I could start trying to use it. So if you have experience with this, so if I wanna try it out in our app, maybe the other people in my team don't know anything about ReactiveCocoa, what are some good ways to kind of either convince people on the team to start using it or at least be okay with me rewriting their code?
Javier: That is a great question. I would love to ask that question to anybody who's using ReactiveCocoa. I'm trying to figure that out as I go. Maybe I would say make them watch this talk, because this is kind of me saying, "I've learned, it took me a while, but I've learned that this is really useful, so how can I help people learn that and discover that in the same way that I did". But it is definitely challenging, it's hard to move away from the things that we're used to and familiar with. It's not a satisfying answer, but yeah.