Intro to SwiftUI — Part 1: The language features behind it

Suyash Srijan
10 min readJun 6, 2019

--

A few days ago at WWDC ’19, Apple surprised everyone with a brand-new framework called SwiftUI, which allows developers to write apps for Apple platforms using a new declarative Swift syntax, right in Xcode.

SwiftUI is built on top of Swift* and allows developers to quickly write apps without much boilerplate.

In this series, we will explore SwiftUI in more detail and learn how you can quickly get started with developing apps using this new framework.

The first few posts will be about learning the fundamentals of SwiftUI, such as spending some time understanding the syntax and basic concepts like View and ViewBuilder. The remaining posts will cover writing a simple app and going through more advanced capabilities, such as custom views, animations and interoperability with UIKit.

Let’s get started! As with any new framework, it’s important to learn the basics before trying to dive deep into it. So, we’re going to kick off this series with an introduction to some new language features in Swift 5.1 that provides us with a foundation for writing SwiftUI code.

* Technically, some parts are written in C++, such as the scenegraph which is a part of the private AttributeGraph framework, but most of the code is in fact in Swift. I suppose it makes sense to write certain performance critical code in a low level language like C++.

  • Part 1: The language features behind it
  • Part 2: The basics of views

What powers this new framework

The upcoming new languages features in Swift 5.1 plays a huge role in the development and use of SwiftUI. Without features such as opaque result types, property wrappers, function builders and dynamic replacement, it wouldn’t have been possible to build this new framework.

I have personally been following the development of these features both on Swift Evolution forums and on GitHub for several months. However, I had no clue that one of the main reasons why these features were being developed was for use in SwiftUI. I think that’s really great — all ideas that go through Swift Evolution must demonstrate that it works (implementation ready) and solves a real problem. Putting these ideas through its paces by using it in SwiftUI is certainly a great way to prove that.

Anyway, so what really are these new features and what do they do? Let’s take a look.

Opaque Result Types

Imagine we had a drawing library, which provided several primitive drawable types, such as lines and points, along with types that allow us to apply composable transformations (like joining and rotating) to these primitive types.

A drawing or painting app might use this library to define common drawable things (like shapes) in terms of this type, such as by creating a DrawableObject protocol.

However, the users of DrawableObject will now have to write long and explicit types for their drawable things.

Here, the return type of render() is extremely verbose and doesn’t really provide any useful information to the reader. The exact return type is irrelevant, all that really matters is that the return type conforms to Drawable.

Also, the fact that we’ve explicitly spelled out the return type of render() means we’re leaking implementation detail and it’s possible that clients could end up relying on this exact return type, which makes it harder for the author of Square to change how it renders the shape.

Now, you could use existentials or type-erasure to solve this problem, but it comes with its own issues, can sometimes add run-time overhead and limits the amount of type safety which the compiler can enforce at compile time, which might not always be acceptable.

So, as mentioned above, instead of spelling out the exact return type of Square.render(), we really just want to be able to say it returns a type that conforms to Drawable.

In Swift 5.1, there is now a way to do it, using the new some keyword.

This allows to declare the return type of Square.render() in terms of its capabilities (conforms to Drawable) instead of explicitly specifying what it is. This enables us to hide the concrete implementation type of render() from the caller and allows us to change it between versions without breaking the clients of DrawableObject, as the underlying type identity is not exposed.

Basically, you can think of opaque return types as “reverse generics”. Normally, the use of generics allows the caller to choose the concrete type bound to the function’s generic parameters:

However, opaque result types can be thought of putting the generic signature on the return position of a function:

This kind of notation is a bit weird to use however, so instead we use some to convey this meaning (the opposite of this would be any — you can read more about it here).

One of the other areas where it can helpful is when the protocol has associatedtype or Self requirements. Normally, when you return such a protocol, the compiler will emit an error which many of us might have seen countless times:

By making the return type opaque to the callers, this problem vanishes:

Coming back to the topic of type identity — just like generics, the compiler has knowledge about what the underlying type is and thus can enforce several guarantees at compile-time. For example — comparing the results of two calls to the same function will always be legal. However, two calls to separate functions with the same opaque return type but different underlying types will be illegal and result in an error:

By now, you must be wondering how this feature is related to SwiftUI? Well, SwiftUI uses it to hide the concrete type of the body’s view graph, otherwise you would be leaking implementation detail and worse, you would have to spell out the return type explicitly, like below, which is not very nice!

😱

There’s a lot that I haven’t talked about, for example — if you have a control flow within a function with opaque result type, then you must return the same concrete type on all paths (which begs the question — how do you return different views depending on orientation perhaps? Here’s a hint: Groups).

If you want to learn more deeply about this feature and why it exists, I highly recommend that you start from the original pitch which was written back in Aug ’18.

Property Wrappers

A while ago, as I was working on a bug fix related to lazy variables in the compiler, I learned that lazy variables in Swift are actually implemented as computed properties. It’s an implementation detail, but basically they’re transformed during type checking into a pair of backing storage variable and a computed property:

The compiler has to perform this transformation for each lazy variable in your program, which is a bit excessive as the only thing that is changing between the generated code is the type and the initial value.

This kind of extends to many different property behaviours (such as @NSCopying) — the compiler has to hardcode these “patterns” and do the transformation for us, which adds uneeded complexity and also does not support all possible variations of such behaviours.

Wouldn’t it be great if we could define or “abstract” away the patterns or behaviours ourselves? Now we can, thanks to a new language feature called Property Wrappers.

Here’s an example — below, we have extracted the “pattern” or “behaviour” of a lazy variable into this new type called Lazy. Inside this type, we’re basically doing the same thing that the compiler does for us at the moment. We’ve also annotated it with the @propertyWrapper attribute, which allows us to inject the behaviour of a lazy variable into any property, by annotating that property with @Lazy.

One of the uses of property wrappers in SwiftUI is to inject state monitoring behaviour into properties, which can be done by annotating properties with a built-in property wrapper called @State.

This basically enables SwiftUI to manage the storage of the property as a state. When the state value changes, the view will invalidate its appearance and recomputes the body of the content.

This is what it looks like:

So, instead of defining state monitoring behaviour for properties ourselves and repeating it for each property, it’s now abstracted away into a custom type, that allows for ease of use and reusability.

This feature is still currently in development, so if you want to learn more then you can head over to the pitch thread here.

Function Builders

What actually allows us to write such declarative code using SwiftUI is a language feature that will allow anyone to write their own mini DSL (Domain Specific Language) in Swift — function builders.

The introduction of this feature caught everyone in the development community off guard — the code and the proposal wasn’t even published until the day of the keynote. The reason for such a late announcement was because this feature was recently developed and pitching it right before WWDC wouldn’t have been realistic.

So what’s a function builder? It’s a new attribute that you can add to a type to allow it to transform “ignored” expressions within a body of a function into a single expression (or a collection of expressions).

SwiftUI uses this new feature in many ways, for example it uses it to define a ViewBuilder, which allows us to construct a view from views produced within a closure.

You could also use the attribute yourself to define your own builders. For example — I created one that allows me to create a vertical stack with a space at the end.

You can add more build* methods to your builder to handle things like control flows (if, if-else) or variadic views (for example — mine can only accept one view, but you can implement a new buildBlock method that accepts Text...).

This feature is also currently in development and was only unveiled yesterday. If you want to learn more, then head over to the pitch thread here. A member of the compiler team, Harlan, posted a pretty cool example using this new feature, that allows you to write HTML in Swift declaratively.

Note: The implementation of function builder that ships with Xcode 11 beta is a simple version of the one that is being proposed on Swift Evolution. If you want to try out the new one, you’ll have to checkout the development branch and build the compiler yourself.

Dynamic Replacement

I am sure many of you might be familiar with the word “swizzling”, especially if you worked with Objective-C before. Method swizzling is a really cool way to take advantage of dynamism, by changing the implementation of a method at runtime.

Normally, the way you do this is by creating selectors for the methods, calling class_getInstanceMethod to get the method implementation of the selector for the class and then calling method_exchangeImplementations to swap the implementations. This whole process involves dealing with the Objective-C runtime.

Now, you can do this natively in Swift. How? You can mark any pure Swift declaration (such as a method) with dynamic and then mark the new implementation with @_dynamicReplacement(for:) and Voila — the implementation will be swapped at runtime!

For example:

SwiftUI uses this feature for the live preview/hot reload feature in Xcode, which is really cool!

One important thing to remember is that only declarations explicitly marked dynamic can be swizzled. Also, in case you haven’t already noticed, this attribute starts with an underscore, which means its not meant for public use yet, so think twice before using this in production code!

If you want to learn more about this feature, then head over to the pitch thread.

Conclusion

That’s it folks, hope you enjoyed reading about these new language features that SwiftUI is built on top of.

An important thing to remember is that these features have benefits beyond SwiftUI —for example, you can use property wrappers to define CoW (Copy-on-Write) behavior and inject it into your own properties or build your own AutoLayout DSL using function builders.

I am looking forward to seeing what new solutions people come up with using these new features — I am so excited that these features are finally in people’s hands after all these months.

Next week, we’ll go through the fundamentals of SwiftUI and learn about concepts like Views, ViewBuilders, ViewModifiers, etc, which will help us develop a solid understanding of the basics before we jump on to developing an app.

Stay tuned!

--

--

Suyash Srijan

iOS Engineer at @theappbusiness. Swift compiler collaborator.