Spicing up Swift

 

In 2015, I ate a hot pepper and talked about Xcode. After a four-year cool-down, I’m back with seven painful, agonizing tips and tricks for Swift developers. Some of the finer points are a little hard to make out in the video, so here they are in writing as well. Bon appétit!

SwiftLint

SwiftLint is a tool that looks for style and formatting mistakes in your code. It helps reduce your time spent arguing over aesthetic minutiae, so you can focus on the things that matter. Take this code, for example:

There’s nothing technically wrong with it, but a few details could be nicer. Let’s let SwiftLint do the tedious part for us. Here’s what it finds wrong with the above code:

Each of those lines is now a conversation that doesn’t need to happen. SwiftLint can even autocorrect some of these issues for us at compile time. For the rest, it shows warnings right in Xcode, allowing us to fix them before our teammates ever see them.

Bonus SwiftLint Tips

A. Pin SwiftLint Version

If we use CocoaPods to manage dependencies, we can use it to specify which version of SwiftLint to use. This does double duty: it ensures that everyone on our team is using the same version, and it means that our teammates don’t have to figure out how to install SwiftLint on their computer separately. We just stick this in our Podfile:

We can also simplify the build script that the SwiftLint documentation recommends, since we no longer have to check whether SwiftLint is installed:

B. Lint Strictly on Build Server

If we run a continuous integration server, we may want to configure SwiftLint to fail the build if it finds linting violations. We can do this by passing --strict to the SwiftLint command. However, this can be cumbersome for local development, because in-progress code often has style issues, and cleaning them up is usually the last step before committing.

We can have the best of both worlds, though. The trick is to find out whether our build server has any environment variables that let us determine whether the current build is occurring on a build server or on a developer’s local machine. For example, several hosted build services set an environment variable called $CI. We can use this in our Xcode project where we run SwiftLint:

Now we get nice warnings when we’re developing locally, but they will never sneak past code review.

Initialization

There are several ways to initialize and configure properties in Swift. Let’s examine a few of them.

Separate Initialization and Configuration

Consider this snippet of code from a view controller:

We construct a UILabel on the line where the label property is declared, and then in viewDidLoad, we set the text color and font. This is the most straightforward approach, but it separates instantiation from configuration.

Immediate Closure Application

This one can look confusing at first, until we observe the () at the end of the declaration. We can read this code as “set the label property to the result of calling this closure that returns a UILabel.

Now, we have everything grouped together, instead of having some things at the property declaration and some things in viewDidLoad. This is especially nice when we’re setting values that will never change. We can’t ensure that the color and font won’t ever change again, since UILabel is a class with reference semantics, but we can more clearly convey our intent with this setup: this color and font are part of the initial state of this label.

However, this approach is also wordier. We have to mention UILabel twice, we always pair a let label = with a return label, and every single line of configuration has to mention label again. Let’s look at an option that reduces some of this redundancy.

Too-Clever-By-Half Initialization

It’s similar to the prior one. The trick here is that we pass a new UILabel instance at the end of the code block, as a parameter to the closure. This is clever, but hard to read. It leads the eye on a merry clockwise chase around the code, and is probably not a good idea in code that will be read by other people.

Then.swift

We can use a function to help us get the conciseness of the above example, with the clarity of the earlier ones. The code is pretty simple, so we can either write it ourselves or use the tiny Then library.

Here is the same code with that sweet, sweet syntactic sugar:

Advantages of this method:

  • Initial value before configuration.
  • option to use $0 to cut down on wordiness.
  • No repetition of label or UILabel.
  • Only configuration inside the closure; no need to instantiate or return anything.

Disadvantages of this method:

  • It can only be used with classes or with types that you conform to the Then protocol. This is because you can’t write an extension on the Any type in Swift, so we can’t write extension Any: Then.

With

If the disadvantages of Then are a dealbreaker, we can write a variation called with. It’s a free function, and you can pass any value or reference type into it:

If Case Let

Optional binding is a great way to enjoy the safety benefits of Swift’s optionals without ending up with a pyramid of doom. However, if we need to bind a non-optional value in the middle of an optional chain, we’ll run into a problem. Let’s look at an example:

We will get an error on the let address = person.address line:

🛑 Initializer for conditional binding must have Optional type, not ‘Address’

The error is because the address property of Person is non-optional, and optional binding works only on optional values.

The fix is unintuitive: add the case keyword to the line, and it will silence the error:

We’re used to seeing case show up with its partner, switch, so it’s weird to see it alone like this. However, the syntax makes more sense when we consider that it’s just a small part of Swift’s larger pattern matching syntax. It feels more at home if we think of it as a simplified version of this example from BonMot:

(Swift’s pattern matching syntax still looks weird, though. The = feels like it should be ==, and it feels like the left and right sides of the expression should be reversed. This is a common source of confusion for new and experienced Swift programmers alike.)

Map on Optionals

In Swift, as in many languages with a functional bent, map is a function that transforms all the elements in an array. For example, this code takes an array of integers and squares them:

Swift takes this a step further by extending the concept of map to the Optional type. It makes sense if we think of Optional as a tiny array of either zero or one items. In this mindset, the role of map becomes clear: just as it transforms every item (if any) in an array, it will also transform the item (if there is one) inside an Optional. And just as an empty array will go untransformed, a nil optional will remain nil.

We can use this map function to neatly condense code that would otherwise take several lines to express. Take this function, which takes a string as input, and if the string represents an integer, return that integer times 2. Otherwise, return nil:

When we see code like this, we can start to recognize the shape of the conditional. Another way to say if let intValue = Int(string) would be, “if the result of Int(string) is non-nil, do something with it.” In this case, that “something” is “multiply it by 2.” In the else branch, we return nil. This is the same shape as the map function on Optional, so let’s see what it would look like to replace the if let with a single map:

In English, this would read, “try converting the string to an integer. If it succeeds, multiply it by 2. Either way, return the end result.” Once we learn to recognize this pattern, it’s amazing how many small if let chunks of code can be reduced to one-liners.

FlatMap on Optionals

flatMap is used in cases where the operation that we do inside the map produces optional values. Our previous sample didn’t have this problem, because multiplying two integers always succeeds. Let’s look at a case that demonstrates this issue a little better:

In this example, our loadDataFromDisk function might fail to load, so it returns an optional: Data?. On the following line, we want to use our map trick to construct a string from the data if it exists. But there’s a problem: Initializing a string from data can also fail, so the call to String.init(data:encoding:) also returns an optional: String?. When we try to compile this code, we get an error on the return line:

🛑 Value of optional type ‘String??’ must be unwrapped to a value of type ‘String?’

Notice the double-optional String?? in the error message. It’s there because the result of Optional.map produces an optional version of whatever was returned by the transform closure, and if that turns out to be optional as well, we get an optional inside an optional.

flatMap to the rescue! It works the same as map, except it coalesces the potential double optional into a single optional that contains a value if both operations succeed, and otherwise returns nil:

Swift Weekly Brief

Tech break! This isn’t a programming tip; it’s a community one. Swift Weekly Brief is “a community-driven weekly newsletter about what’s happening in the Swift open source projects at Swift.org.” If the forum discussions, Swift Evolution proposals, and GitHub repositories all seem overwhelming, Swift Weekly Brief provides weekly summaries of the latest news in the Swift language and community, including descriptions of each proposal that is up for review. They also have a podcast.

Weak Self

We know weak self as that thing we’re supposed to do to avoid those dreaded reference cycles, which can lead to memory leaks. And while we’re not going to cover [weak self] in depth here (since many others have already done it), we are going to look at a best practice and a nice trick.

Here, we see an asynchronous fetchData function that runs two functions on self in its completion handler. The first thing to note is that self could become nil between the first and second lines. Since the closure does not retain self, we are at the mercy of any other thread that may decide to deallocate self. We can protect against this by capturing a local strong reference to self, which will keep self alive during execution of the closure:

This takes care of the issue where self becomes nil between the first and second lines, but strongSelf is a bit of an eyesore. Fortunately, Swift 4.2 lifted some restrictions on naming, allowing us to name the local variable self as well:

There, much better!

Identifiers

[Note: this section is semi-obviated by SE-0261, which added an Identifiable protocol to the Swift standard library. However, the ideas presented here are still valid and worth exploring.)

This section is about using Swift’s powerful type system to our advantage to prevent common mistakes. Let’s consider the following code, which is part of the model layer of a conference app:

We have Speaker and Talk types, and a Talk has a speakerID property that’s supposed to correspond to the id string of a Speaker. But it’s easy to accidentally confuse various id parameters:

We passed a Speaker‘s id where we were supposed to pass an id for the Talk. Unfortunately, all IDs in this example have the type String, which means there’s nothing the compiler can do to prevent us from making this mistake.

To fix this, let’s define a new Identifier type that will prevent us from mixing and matching our IDs.

Our Identifier is generic over a type T, but notice that T is not used anywhere in the body of the type. Its only purpose is to serve as a hint to the compiler about what kind of identifier each value is. It allows us to have an Identifier<Speaker>, and know at compile time that this is distinct from an Identifier<Talk>. This use of generic type parameters as flags for the compiler is called phantom types.

Let’s look at how it improves the above code:

Now, we get a useful error message at compile time:

🛑 Cannot convert value of type ‘Identifier’ to expected argument type ‘Identifier’

What Are Your Favorites?

There is so much more to Swift than what I was able to cover here. What were your favorite tricks mentioned above? Did I skip one that you find invaluable or too cool to leave out? Post them in the comments below!

Leave a Reply

Your email address will not be published. Required fields are marked *