Unlock This Episode
Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
Brandon: So this is all looking really great. We now have better tools for modeling our domains more concisely.
Previously when we wanted to be able to navigate to a new child feature from an existing feature we would just throw an optional into our domain. However, with each additional optional you add to a domain you double the number of invalid states in your feature. For example, we had 4 options representing 4 different destinations, which means there were 16 different states our feature could be in, only 5 of which were valid.
Now we can embrace enums when modeling the destinations for our features, which gives us just one single place to describe all the different places a feature can navigate to. That gives us us more correctness in our code because the compiler is proving for us that only one destination can be activated at a time.
Stephen: Next let’s address a problem that gets nearly everyone eventually when using our library, especially when composing lots of features together. Because modeling navigation in state requires us to nest child state in parent state, any sufficiently complex application eventually has a very large root app state that could potentially have hundreds of fields. This also means that the amount of memory you are storing on the stack will increase as you integrate more child features together.
This may not matter for awhile, but eventually your state may get too big or you may have too many frames on the stack, and you could accidentally overflow the stack. That crashes the application, and so that of course isn’t great.
Now it’s worth noting a couple of caveats here. Not all of your application’s state is necessary stored on the stack. Arrays, dictionaries, sets, and even most strings are all stored on the heap, and so it doesn’t matter if you have a 100,000 element array in your state, that makes no difference for the stack.
Also we made great strides towards reducing the number of stack frames that are incurred when combining lots of features together, all thanks to the
Reducer protocol. In release mode most of those compositions get inlined away and you are left with very few actual stack frames.
But still, people do run into this limitation, and it’s a real bummer.
However, by far the most common reason for multiple features to be integrated together is because of navigation. You plug “FeatureB” into “FeatureA” when you need to navigate from A to B. As you do this more and more your state becomes bigger and bigger.
And now we are going to be giving everyone more tools to build up state like this for navigation, and so it may start happening a lot more. Perhaps we can directly bake into the tools a more efficient way of storing state so that it plays more nicely with deeply nested, composed features.
For more information on copy-on-write, be sure to check out this detailed video from Johannes Weiss:
Languages that have a rather low barrier to entry often struggle when it comes to performance because too much is abstracted from the programmer to make things simple. Therefore in those languages, the key to unlock performance is often to write some of the code in C, collaterally abandoning the safety of the higher-level language.
Swift on the other hand lets you unlock best of both worlds: performance and safety. Naturally not all Swift code is magically fast and just like everything else in programming performance requires constant learning.
Johannes discusses one aspect of what was learned during SwiftNIO development. He debunks one particular performance-related myth that has been in the Swift community ever since, namely that classes are faster to pass to functions than structs.