UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

10 Quick Swift Tips

Try out something new with these tips and tricks

Paul Hudson       @twostraws

Swift is a huge language with lots of interesting syntax, features, and quirks to take advantage of if you know they exist. In this article I want to walk you through ten of them quickly, including hands-on code examples so you can try them immediately.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

1. Existentials of classes and protocols

An “existential type” allows us to say what kind of functionality we want a type to have rather than requesting something specific. So, you should already know that you can we can write functions that accept a class or a subclass:

func process(user: User) { }

And we can write functions that accept any kind of object as long as it conforms to a protocol:

func identify(thing: Identifiable) { }

Swift allows us to represent existentials of classes and protocols at the same time.

As an example, here’s a protocol and a class that conforms to the protocol:

protocol CanCook { }
class CelebrityChef: CanCook { }

And here’s a class along with a subclass of it:

class Appliance { }
class Hairdryer: Appliance { }

So, we have a protocol defining things that can cook, and a class that defines things we have in our home. Things become more complex when we want to combine the two – to work with appliances that can cook food.

Defining them in easy enough, because they can subclass from Appliance and conform to CanCook, like this:

class Oven: Appliance, CanCook { }
class Microwave: Appliance, CanCook { }

However using them is where Swift’s existential support comes in. Unless you’re best friends with Mary Berry, it’s unlikely you’ll get a celebrity chef to come to your house to cook a meal. Similarly, unless you’re desperate it’s unlikely you’ll try to cook your dinner with a hairdryer.

As a result, both of these two functions aren’t good enough – they don’t fully describe the types of data we want to accept:

func makeDinner(using: Appliance) { }
func makeDinner(using: CanCook) { }

Fortunately, Swift lets us combine protocols and subtypes in a single existential by writing Appliance & CanCook – we want something that is a household appliance that also conforms to the CanCook protocol. It looks like this:

func makeDinner(using: Appliance & CanCook) { }

You can learn more about this feature here: What are class and subtype existentials?

2. Protocol extensions can provide default property values

Protocol extensions are a great way of providing default method implementations that can then be overridden as needed by conforming types, but you can also use them to provide default values for properties.

For example, we might create a Fadeable protocol that fades out views over a set number of seconds, like this:

protocol Fadeable {
    var fadeSpeed: TimeInterval { get }
    func fadeOut()
}

Rather than make all conforming types add their own fade speed and fadeOut() method, we can provide defaults for both inside a protocol extension:

extension Fadeable where Self: UIView {
    var fadeSpeed: TimeInterval {
        return 1.0
    }

    func fadeOut() {
        UIView.animate(withDuration: fadeSpeed) {
            self.alpha = 0
        }
    }
}

As a result, you can make new subclass conform to that without worrying about writing the same default value repeatedly:

class MyViewClass: UIView, Fadeable { }

3. Check whether all collection items satisfy a condition

New in Swift 4.2 is an allSatisfy() method: give it a condition closure to run, and if all elements return true when passed into that closure then the allSatisfy() call will return true.

For example, if we had an array of exam results like this:

let scores = [85, 88, 95, 92]

We could decide whether a student passed their course by checking whether all their exam results were 85 or higher:

let passed = scores.allSatisfy { $0 >= 85 }

4. Use destructuring to manipulate tuples

Destructuring is the ability to pull apart tuples into individual values so you can manipulate them more easily. For example, you might want to call a function like this one:

func getCredentials() -> (name: String, password: String) {
    return ("Taylor Swift", "biebersux")
}

That returns a tuple containing two strings, and if you want to keep them together you can:

let user = getCredentials()
print(user.name)
print(user.password)

However, destructuring allows us to pull them apart like this:

let (username, password) = getCredentials()
print(username)
print(password)

You can even do it after the function has been called – it does exactly the same thing:

let user = getCredentials()
let (username, password) = user

This technique is what gives Swift its ability to solve a classic beginner’s coding problem in a trivial, easy to remember way: how do you swap two variable integers without using a third variable?

Thanks to destructuring, Swift’s solution couldn’t get much easier:

var a = 10
var b = 20
(a, b) = (b, a)

You can learn more about this feature here: What is destructuring?

5. Adding and subtracting with wrapping using overflow operators

All Swift’s integer types have maximum values. For example, the maximum value of UInt8 is 255, and for Int64 it’s 9,223,372,036,854,775,807.

To keep your code safe, Swift will automatically crash your code if you try to go beyond the limits of your integer type. For example, this code compile fine then crash at runtime:

let highScore = Int8.max
let newHighScore = highScore + 1

It crashes because it tries to increment Int8.max by 1, making 128, which is beyond the scope of what Int8 can store. Even though crashing sounds bad, it’s often safer than other alternatives.

However, Swift gives us the option of choosing different behavior: we can add with overflow, which causes Swift to wrap around to the lowest value rather than crashing:

let highNumber = UInt8.max
let nextNumber = highNumber &+ 1

This is used more often than you might think. For example, the MySQL database can automatically assign integer IDs to rows in a database table, but when it runs out of integers it wraps around and starts looking for free ID numbers starting at 1 because some might have been deleted over time.

6. Public getter, private setter

Although Swift access control has occasionally been ridiculed in the past, it does make it super easy to apply two different access controls to properties.

For example, here’s a struct that represents a Bank:

struct Bank {
    var address: String
}

We didn’t apply any access controls for address, which means any other code can read it but also change it. If we’d used private for that property, no one would be able to change it but neither would they be able to read it.

Swift has a great middle ground: public private(set). This lets us mark a property as being open for reading but closed for writing, which in the case of our bank means anyone can read the bank’s address but only the bank itself can change it:

struct Bank {
    public private(set) var address: String
}

7. Memberwise initializers alongside custom initializers

Swift’s structs come with memberwise initializers by default, which are a fast and convenient way to create instances:

struct Score {
    var player: String
    var score: Int
}

let highScore = Score(player: "twostraws", score: 556)

However, if you create your own initializer you automatically lose the memberwise one. This is a matter of safety: your initializer is likely to do some extra work that you deem important, so if Swift kept its memberwise initializer around your extra work would be skipped when it was used.

If you want your initializers to work alongside the memberwise initializer, the fix is simple: declare your own initializers inside an extension. For example:

struct Score {
    var player: String
    var score: Int
}

extension Score {
    init(player: String) {
        self.player = player
        score = 0
    }
}

// these are now both valid
let highScore1 = Score(player: "twostraws", score: 0)
let highScore2 = Score(player: "twostraws")

You can learn more about this feature here: How to add a custom initializer to a struct without losing its memberwise initializer.

8. Static vs class properties

Class properties in Swift can be created using two keywords: static and class. They both make the property shared across all instances of a class, but static implies final so that it cannot be overridden in subclasses.

For example, we could create a Building class that defines a class property to store zoning regulations (where the building can be built in a city), and a static property to store safety requirements:

class Building {
    class var zoningRestrictions: String {
        return "None"
    }

    static var safetyRequirements: [String] {
        return ["Fire escapes", "Sprinklers"]
    }
}

Because zoningRestrictions is a class property it can be changed by subclasses – houses could be built in residential areas, offices in commercial areas, and so on. In contrast, safetyRequirements is a static property so that all buildings and subclasses must implement legally required safety requirements.

In code:

class Skyscraper: Building {
    // this is allowed
    override class var zoningRestrictions: String {
        return "Dense commercial only"
    }

    // but this is not
    override static var safetyRequirements: [String] {
        return ["Sprinklers"]
    }
}

You can learn more about this feature here: What’s the difference between a static variable and a class variable?

9. == and === are not the same thing

The == operator checks whether two Equatable types match. For example:

1 == 1
"kayak" == String("kayak".reversed())
[2, 4, 6] == [1, 2, 3].map { $0 * 2 }

Thanks to the automatic synthesis of Equatable getting support for == is often as simple as adding Equatable to your type’s definition, but if you’re using classes there’s an alternative: ===, the identity operator.

Because instances of a class are merely references to a specific address in memory (hence their name: “reference types”), the === operator can check whether two instances of a class are identical simply by checking whether they point to the same memory address.

So, the condition at the end will evaluate as true:

class Lightsaber {
    var color = "Blue"
}

let saber1 = Lightsaber()
let saber2 = saber1
saber1 === saber2

The identity operator doesn’t use Equatable at all, which means if you create two independent objects with the same properties === will return false:

let saber3 = Lightsaber()
saber1 === saber3

You can learn more about this feature here: What’s the difference between == and ===?

10. Convert between integer types using numericCast()

Swift has always been highly selective about how you use integers, so if you’re not careful its easy to find yourself scattering Int(), UInt32(), and other type conversions into your code. While this code might be correct, it can be baffling to folks reading it: why do you need to force a certain integer type here?

This can often be avoided by using Swift’s dedicated integer conversion function, numericCast(), which effectively means “I don’t care what type this needs to be, please figure it out.” This conveys your intent more clearly than hard-coding types: you need to convert one integer type to another in order to make Swift happy, but don’t care specifically what the conversion is.

A common culprit is the arc4random_uniform() function, which accepts a UInt32 as its only parameter and returns another UInt32 – it’s common to add explicit typecasts between Int and UInt32.

If you want to use numericCast instead, you can write a nice random range implementation like this:

func random(in range: Range<Int>) -> Int {
    return numericCast(arc4random_uniform(numericCast(range.count)))
        + range.lowerBound
}

Bonus just for fun: if not ! then what?

Not everyone likes the logical NOT operator, !, largely because it doesn’t read naturally. However, Swift really blurs the lines between functions, methods, closures, and operators, so if you wanted you can turn ! into its own function like this:

let not = (!)

Now you can use not(someBool) instead of !someBool, like this:

let loggedIn = false

if not(loggedIn) {
    print("Please log in.")
}

There – much better! (Or maybe – ahem – not.)

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.