Eight opinionated tips for people learning Swift - Thomas Bibby

I was talking to two Objective-C iOS developers recently who told me that they were starting to learn Swift. This got me thinking: what did I wish I knew when I was learning Swift? Here are eight tips that are opinionated: they are not designed to help you learn Swift, but they might help you to work with the language instead of against it.

1. Avoid force unwrapping and force casting

I’ve written about this before – and it annoys me that Xcode sometimes recommends force unwrapping Optionals as a fix-it. Force unwrapping with ! and force casting with as! will bite you in the end – don’t do it. (Possible exception – objects from Storyboards and interface builder files, as their types are defined at runtime).

2. Prefer guard let over if let

You’ll learn that the classical way to unwrap an Optional is to use if let. The problem with if let is that it can cause pyramids of doom of closing braces in your functions. Consider this example:

if let processedString = funcThatReturnsAnOptionalString("parameter") {
    if let secondProcessedString = funcThatReturnsAnOptionalString("another parameter") {
        if !processedString.isEmpty && !secondProcessedString.isEmpty {
            //do stuff
        }
    }
} // yuk

Solution: use guard let introduced in Swift 2:

guard let processedString = funcThatReturnsAnOptionalString("parameter") else {
    //you may want to perform error handling here
    return
}
guard let secondProcessedString = funcThatReturnsAnOptionalString("another parameter") {
    //handle error if necessary
    return
}
if !processedString.isEmpty && !secondProcessedString.isEmpty {
    //do stuff
} //goodbye pyramid of doom

This gives you a fail early pattern and allows you to avoid unnecessary indentation.

3. Unwrap multiple things at once

You’ll run in to another problem once you start using guard let: you’ll find your functions compress horizontally (less indentation) but will expand vertically (more lines of code). Solution: unwrap multiple things at once. Consider the example above compared with:

guard let processedString = funcThatReturnsAnOptionalString("parameter"), let secondProcessedString = funcThatReturnsAnOptionalString("another parameter") else {
    //handle error if necessary
    return
}
if !processedString.isEmpty && !secondProcessedString.isEmpty {
    //do stuff
}

Caveat: I find that if I’m unwrapping more than three things at once, it’s a code smell and I need to refactor. Also: it’s possible to combine guard let with traditional guard statements with conditional clauses: although I’m on the fence whether this is a good idea or impedes readability, I do find that it works as long as I restrict it to conditionals that answer the question “is this thing valid enough for me to work on?”. This would be achieved with the following (make your own mind up if this is taking things too far):

guard let processedString = funcThatReturnsAnOptionalString("parameter"), let secondProcessedString = funcThatReturnsAnOptionalString("another parameter"), !processedString.isEmpty, !secondProcessedString.isEmpty else {
    //handle error if necessary
    return
}
//do stuff

4. Understand that optionals may cause your app to fail differently

To be fair this is less of a problem for Objective-C developers, who are used to the paradigm “sending a message to nil is grand, at least if you’re expecting an object in return, because you’ll just get nil back”. But for developers coming from object-oriented languages such as C++, Java, C#, etc. it might be a bit of a surprise that you get far fewer crashes in Swift apps if you don’t force unwrap, because the language can eliminate a whole class of “null pointer exception” crashes if you use it correctly. This is not the same as eliminating bugs due to unexpected input! Instead of bug reports “the app crashes when I tried to do this”, you’ll start to get reports of “nothing happened when I tried to do this”. Prepare accordingly.

5. Use structs (when you can)

Although Swift supports object-oriented programming with classes, it brings a new more powerful paradigm: protocol oriented programming. This works best with the value types struct and enum and also gets you extra benefits: default initialisers, thread safety, and a way out of the sometimes baroque structures that class inheritance can encourage. But there’s a caveat: note that the two popular object persistence methods used in Swift (Core Data and Realm) use classes. And at the UI layer, UIKit on iOS and AppKit on macOS are all class-based. So your potential for using structs may actually be quite limiting (which can be a bit confusing because most introductions to Swift focus on structs, not classes, which leaves you with a mismatch between learning the language and using it). That said, do try and use structs where you can, especially in the intermediate layers between your view controllers and your model objects.

6. Enum all the things

That’s the title of a talk given by Dave Sims at the Limerick iOS Developer meetup that was so good he famously ended up giving the talk at other meetups in Ireland and the US. Enums are first-class (pardon the pun) value types in Swift that can have functions, properties, and most of the things that structs can have, as well as associated types. They are a tremendously powerful tool when writing swift apps.

7. Avoid AnyObject, and start to think about protocols and generics

Say you have two UITableViews in your app, which are mostly similar – you might be tempted to write one UITableViewController to avoid code duplication. What do you use as your datasource? One way might be to use an array of AnyObjects (AnyObject is a special type which means an instance of any class). Any time you’re relying on run-time type checking is a good prompt to think about protocol orientated programming. At its simplest, create a protocol and make your two types conform to it (they can even be blank). Add any common properties to allow the Swift compiler to check the validity of your code. Generics offer another powerful way of avoiding code duplication and increasing compile time safety. You can even create generic protocols using Protocols with Associated Types.

8. Add defaults to function parameters

Finally a quick one – if a function you’ve written almost solves your current problem, but needs an extra parameter, add it to the existing function with a default value instead of creating a new function. The default value will mean that existing calls to that function won’t break. I’ve often found that making the parameter an Optional type and setting its default value to nil allows an if let in the function body to neatly deal with the extra parameter.

Conclusion

Swift is still a young language, and best practices are by no means settled. To take an example from Erica Sadun’s excellent book Swift Style: An Opinionated Guide to an Opinionated Language, the Swift Standard library team prefer a space either side of a colon in declarations:

class userDetailViewController : UIViewController {

where the Apple developer docs team prefer left-hugging colons almost everywhere:

class userDetailViewController: UIViewController {

(for what it’s worth I prefer the second approach). I don’t even follow all the recommendations listed in this article all of the time in my own code. What’s important is that as you learn the language, you’re aware of working with the language and its opinions, rather than against it.