The introduction of Swift has sparked a revival of interest in functional programming concepts like composition, immutability, and algebraic datatypes. At Facebook, the same ideas have been gaining traction for many years now, inspiring frameworks like ComponentKit and React Native. In this talk from #Pragma 2015, Hannes Verlinde discusses common design principles and different trade-offs made, and what it all means to you as an iOS developer.
My name is Hannes and I’m a iOS developer at Facebook in London. Today, I would like to talk about things that change and things that don’t change. To start, I think it’s safe to say that Swift changed something on June 2, 2014. Before Swift, we had things like ReactiveCocoa, but those were never really mainstream. Today, everyone seems to be talking about map reduce filter. And if you go to an iOS conference today, you might find sessions on Haskell and Monad. That’s pretty cool.
Ten years ago I was a Haskell programmer and did nothing but Haskell. Eventually, I decided to grow up, get a real job and use a real programming language. Now, it’s all back and it makes you realize that nothing new is really new. The same ideas seem to keep coming back. And the reason they keep coming back might be, as we all know, there are no silver bullets. Some complexity cannot be abstracted away. On the other hand, a lot of complexity can be abstracted away, and it can make a big difference. We need these meaningful abstractions as they’re the only thing we have to manage complexity.
Which abstractions are meaningful? Which frameworks are hype? Which ones are almost silver bullets, or have the potential to be? You don’t want to be left behind when the next big thing arrives, but you don’t want to jump on every new framework or technology either. There’s always a price to pay, even if you have a meaningful abstraction. How do you decide this?
It turns out this is hard for everyone and the answer very much depends on your specific context. I love to read the Swift diaries that Brent Simmons wrote because you can see how even an experienced iOS developer is struggling with something like this. We’ve struggled with the same problem at Facebook as well. We had a native app and then we decided to throw it all away and build a HTML app. Abstract away all of the platform dependent details and share coding across platforms. It seemed like a great idea, but the performance price we paid for it was too high so we went back to native and started using Core Data. That looked like a very meaningful abstraction, and it was. We even bench-marked it to make sure it would perform well, but as we kept growing our client side data model, we eventually started hitting performance issues that we couldn’t work around anymore. We started struggling with youthful models everywhere.
Eventually, we got rid of all our Core Data stack. It was an expensive decision. This, of course, doesn’t mean Core Data is bad. It just means it didn’t work for us specifically. You cannot avoid making occasional wrong decisions and paying the price, but what you can do is focus on ideas rather than frameworks—especially those common ideas and patterns that keep coming back. When you have different movements in software development and they all seem to come to the same conclusion, then you might be on to something. I don’t listen to everything Apple says, but when I hear an engineer at WWDC say something, and it’s the same thing I heard while functional programming ten years ago, and it’s the same thing that experienced developers at Facebook are telling me, then I start paying attention. And, of course, you want to focus on why these ideas are valuable, not just the ideas themselves.
I will be discussing some frameworks, but I also want to focus on the ideas behind those frameworks. One of the big ideas from functional programming is in order to avoid or to manage complexity, you should avoid state. That’s what functional programming is really about. Declarative is better than imperative. In functional programming you have a
map and in imperative programming you have a
for each. Today, we’ll look at how this idea applies to user interfaces, but you could tell a very similar story for multilayer.
Get more development news like this
Declarative user interfaces mean you focus on what rather than how. You tell the system what you want and you let the system figure out how to do it. This is very much the idea behind Auto layout and Interface Builder. Apple has been promoting this for many years. We considered using Auto Layout at Facebook, but it didn’t work out for us for a few reasons. If you have a code base of a certain size and a number of developers who are working on it, using Interface Builder is not an option because you cannot merge those files. It can also be hard to debug and it didn’t perform well on the very complex hierarchies that we had in our newsfeed. Again, this is a context dependent answer, so if you have a smaller team and fewer hierarchies, Auto Layout may be perfect for you.
What do I mean when I say you should avoid stating your UI or avoid stateful UI? I mean that ideally, your UI should be a pure function of the state, and your UI should not need to consider any state transitions. The transitions should be covered by whatever framework you’re using. To illustrate this, here you have a little bit of UI from the Facebook app. There might be a like button, a comment button and a label that tells you how many likes and comments there are. If you like this specific story, then the like button should be blue, otherwise it should be gray. In this case, there might be something like eight different states. It should be clear that it’s easier to reason what UI should be for each state than to reason all the possible transitions from one state to another. If there are N states then there can be N squared transitions.
UI is a pure render function in a mathematical sense. You have state as input and UI rendering as output. There are no side effects. It doesn’t matter how many times you call this function, you always get the same result. A related idea is the concept of a one-way data flow. Ideally, data should flow from the mother layer to the the UI layer, in a single direction. At one point, the data flow in part our app might have looked like this. There would be a model store and it would send the model to the view controller. There would be the hierarchy of view controllers and each view controller would configure its view. Then, at some point you would tap the like button and the like view would update or notify the like controller which would update the model and send it back to the model store. It also needs to notify the parent view controller because the height of the view might change.
Today it looks like much more like this.
You still have data flowing from the model layer to the UI layer, but it doesn’t flow back. What happens when you tap this like button? It’s going to go back to the model store or further to update the model. And when it updates the model it will re-flow the entire UI from there. You get this single directional, circular flow. Which is very different from anything you would get with key value observing or two-way data binding. There, data is flowing in both directions. Here, we have a singular, directional flow. This is an idea that’s also being promoted by Apple. If you look at last year’s WWDC, there was a session on advanced iOS application architecture and patterns. You will find very similar slides there.
Another related idea is the concept of immutability. This is related, because it helps enforce one-way data flow. If you don’t allow it to update a model, then you’re forced to create a new one. And it’s much more likely to start at the model store and re-flow from there. Immutability is also thread safe by design. If you do not allow it to modify an object then you don’t need to worry about different threads modifying that object at the same time.
Managing concurrency is still a big challenge today because it doesn’t really scale well and there’s only a limited set of options you have. Option one is doing everything on the main thread. It doesn’t scale well because of performance. Option two is making every property atomic. It also doesn’t scale well. You will still get race conditions even if every single property is atomic. Option three is doing things properly. You use the stuff they teach you in college, like shared lock and semi-force. It also doesn’t really scale. As your code base grows, sooner or later, you’re going to run into bugs.
If none of these options scale, we need to cheat by immutability. We don’t allow to modify objects, and suddenly everything becomes easier to reason about.This too, is promoted by Apple. If you look at Swift, there’s a lot of focus on value types and constants.
Next is composition. Composition is great because if your UI components are truly composable, it gives you an alternative to sub classing. Sub classing is typically bad. If you have a hierarchy of sub classes, it becomes very hard to reason about where a specific behavior is implemented. This too is an idea that’s currently being promoted by Apple. If you look at the session on protocol oriented programming at this year’s WWDC, they state this very clearly. Avoid inheritance, prefer composition.
Frameworks at Facebook (13:40)
Now I want to talk about frameworks because we do have a few frameworks at Facebook. The bigger your code base, the more developers you have working on it, the more sense it makes, the more it pays off to invest in stable infrastructure to support meaningful abstractions.
This lead to ComponentKit. We released this as Open Source about a year ago. This is a framework for iOS which is heavily React inspired. It’s also based on pure render functions and has a view diffing algorithm. Conceptually, you re-flow your UI when something changes, but the framework will detect what has actually changed and it will apply those changes to your UI views. Today, ComponentKit powers a big part of our main Facebook app, the News Feed. This is really important for our code base today. I don’t want to delve into code too much today, I think there are some very good tutorials out there, but to give you some idea of what it looks like.
Here you have an
ArticleComponent. This takes an article model as input and it renders this as three sub-components and it’s a stack of sub-components. There’s a
FooterComponent. It’s a vertical stack of those sub-components. Instead of views, you have components. And components are like descriptions of views, specifications and stencils. They specify what, not how. And the
CKStackLayout is being used here, that’s an implementation of Flexbox, which is a CSS standard that’s also used in React to specify layouts. You can think of it as something very similar to
UIStackView, which Apple released with iOS 9. You can build views as stacks of other views and specify some alignment options and justification and the space between them. And you build more complex views by composing stacks of stacks, where you have composition rather than inheritance. You built your component tree, each component is rendered as a stack, or some other layout of sub-components. And then, of course, at some point you have some kind of leaves, you have primitives like image components or rich text components. This is what an image component might look like.
It takes an image’s state and it has a UIImage view as its backing view. You apply the state by pulling the specified selector on the view, but you would never call the selector manually. Instead, you declare it and the framework will detect when the image for a UIImage view changes and what it needs to call this selector to apply the new state. ComponentKit is declarative. You simply declare components as stacks of sub-components and specify the way they are stacked. It’s also functional. You have a one-way data flow from immutable models to immutable components. The infrastructure will call this method and build a component hierarchy from it. Then, from that description it will build a view hierarchy. When something changes, it will rebuild the component tree, run the view diffing algorithm to detect the actual changes, and it will apply those actual changes to your views. Most of this runs in the background thread. Components can be backed by views, but they are not views. You can build a component tree on a background thread. you can run a view diffing algorithm on a background thread. It’s only when you apply the actual view property changes that you need to run on the main thread. This can be very efficient and composable. You never sub classed components. Instead you ensure that sub-components are truly reusable, composable and that you can compose them in arbitrary ways using a StackLayout or another kind of layout.
You’ve noticed that this was not pure Objective‑C code. We’re using Objective‑C++ for this to increase efficiency. This powers News Feed in our main app. There’s a lot of stuff going on there. What C++ gives us is stack allocations. When we build a component tree, when we run a view diffing algorithm, we are create a lot of temporary view model structures. With C++ we can allocate them on the stack. Because there are a lot of them, it makes a difference. Type safety means that we are using SDL containers rather than Objective‑C containers. We know the type when we put something in there or get something out.
Syntax was a reason, basically C++ lets you do aggregate initialization. That means, when you initialize a structure, you can only specify the fields you care about. For instance, when we initialize a StackLayout, we specified the orientation vertically, but we didn’t specify any justification or alignment options because we didn’t care about it those. We just wanted the defaults. Aggregate initialization allows lets you have a very terse syntax, which is still type safe. And it makes it easier to write these component hierarchies. Nil safety means that Objective‑C structures and containers don’t freak out when you try to insert Nil.
React Native (21:20)
UIViews. And that’s important. In the end, you get Native
UIViews. We are not faking anything, we are not using web views or anything like that. If you look at the apps that are powered by React Native, the Groups app, the Ads Manager app, these feel like Native apps. In a sense, they are Native apps because they have Native
UIViews. Style sheet properties are mapped to properties on those
UIViews. Why do we need React Native if we already have something like ComponentKit?
And then there’s the potential for code sharing. We don’t consider React Native anything like write once, run everywhere. You still need to write your native apps separately. What you can do is re-use the paradigms you used for web development. We like to think of it as learn once, write everywhere. We have a good example of this.
Which one should you use? Of course, it depends on your context. ComponentKit is very much optimized for apps that have a lot of collection views or table views, apps that have some kind of feed, like Facebook. If you ever struggled with things like height for row, dynamic row heights, cell reuse, this is where ComponentKit really shines. It abstracts all of that stuff away from you.
React Native has all these additional benefits that we talked about. They may or may not be important to you. In general, React Native is a bit further away from the metal. And the reason is, you need to bridge native code. If Apple releases some kind of new
We do appear to be in this state of statelessness. A lot of exciting things going on. If you’re still writing classic stateful UI, height for row, size that fits, layout sub views, you might be missing out on some of the fun.
Q: What does it mean when, in the rendering state, every view is stripped from the window and is rendered again on the view or changed in place?
Hannes: That’s where the view diffing algorithm comes in place. Conceptually, it’s re-flowing. That’s what you reason about, but the library will run in the background and build a new component tree, but it will not build a new view tree. It will compare the component tree before and after your changes and it will detect the actual changes. We don’t want to throw away any views. We will detect any changes to the views, but you don’t need to reason about those changes, the framework will do that for you.
Q: You mention one data flow at the beginning and on the web we have abstractions apart from the UI layer. We have abstractions like flux or relay or all this kind of stuff. Can they be used off the shelf for React Native development, or do you have any other abstractions that you use on ComponentKit?
Q: Question not heard.
Hannes: Today, we are mostly using ComponentKit in the main Facebook app. We have some other apps that are using React Native. We are looking at integrating React Native in the main app, but today it’s mostly ComponentKit.
Q: If you’re using ComponentKit for the main application in Facebook, how do you handle basic animations in News Feed?
Hannes: ComponentKit has some support for animation. Ideally you want to secure render functions. There are some hooks that we have to allow simple animation. When you want very complex animation like I said, today AsyncDisplayKit might be a better option, but we do allow animation in ComponentKit.
Q: How do you handle these lists or scrolling, like table views or collection views, in ComponentKit?
Hannes: Most of that is covered by the framework. Like I said, you don’t really need to worry about cell reuse or anything like that. The framework will deal with all of that.
Q: How are you propagating user events for that ComponentKit? For example, when someone clicks the Like button on a story, do you use Responder Chain or something like that?
Hannes: Yes, there is a kind of Responder Chain, or something very closely to it. Basically, a component could have a component controller as well. And then we do have a chain where events go from a component to the parent component where they can be intercepted at any point in the component tree.
Q: What about memory leaks, memory consumption and overall debugging, because if you get crashing, do you get something like crashlytics or anything else?
Hannes: I think in general something like ComponentKit, it’s easier to debug than something like Auto Layout. But, you’re right. Whenever you use a framework, whenever you introduce an additional abstraction you are complicating your stack trace. You will always be a bit further away from the metal with every abstraction you introduce, so it may complicate that. But in general, I don’t think this is an issue for us. When things go wrong, of course, the advantage is if you really have bugs in your framework, you only need to fix them once. It pays off to invest more time there to make sure your infrastructure is stable.
Q: I’m thinking about React Native, how much do you think is going to be the code sharing there in terms of percentage?
Hannes: I only have a few specific examples since it’s all pretty new technology. We have this Ads Manager app and I know they were able to reuse 85 percent of their code between iOS and Android. That’s really significant.
About the content
This talk was delivered live in October 2015 at #Pragma Conference. The video was transcribed by Realm and is published here with the permission of the conference organizers.