NEW: Check out my podcast with Sean Allen, Swift over Coffee! >>

How to update your code to modern Swift

Paul Hudson       @twostraws

Although Xcode has a built-in command to convert current projects to the current Swift syntax, it’s limited – it can fix any changes to overlays (e.g. going from NSAttributedStringKey to NSAttributedString.Key in Swift 4.2), for example, but it can’t tell you about new ways of doing things or idiomatic, Swifty replacements for older APIs.

In this article we’re going to walk through eight pieces of older code you can look for in your projects, along with what their modern replacements are. Each of these aren’t covered by Xcode’s syntax modernizer, so you’ll need to use the search function instead.

1. Converting between String and Data

When you’re switching between instances of String and Data, you might previously have used code like this:

// convert from data to string
let old1 = String(data: data, encoding: .utf8)   

// convert from string to data
let old2 = .utf8)

If you search for encoding: .utf8 or data(using you should able to find those in your project.

That code still works fine, but modern Swift gives us better alternatives for most use cases. First, if you want to go from data to a string you should use this:

let new1 = String(decoding: data, as: UTF8.self)    

That has the advantage of always returning a String instance, even if nothing could be decoded from the data. The old method returned an optional string, so you needed to use nil coalescing to get the same behavior.

Similarly, going from a string to data also removes optionality, and as a bonus is now much shorter:

let new2 = Data(str.utf8)

2. Hand-written Equatable conformance

Back in Ye Olde Dayes of Swifte we’d write conformances for Equatable by hand like animals:

struct Person: Equatable {
    var name: String

    static func ==(lhs: Person, rhs: Person) -> Bool {
        return ==

Of course, there are lots of reasons why you might still want to do this by hand, not least if you only want a subset of properties to be compared. But if you search your project for instances of func == you might come across places where you can delete your custom code entirely – Swift will now synthesize an == method that compares all properties automatically, making any of the same code redundant.

3. Randomization and shuffling

Search your code for arc4random and see what comes up – you might have some calls to arc4random() and arc4random_uniform() scattered around, both of which can and should be replaced by Swift’s new randomization API:

// old
let randomInt = arc4random_uniform(10)

// new
let randomInt = Int.random(in: 0...10)

You can also use the same random(in:) method on Double, Float, and CGFloat, and even Bool.random() works to choose randomly between true and false.

As for shuffling, you might previously have been using an extension like this:

extension Array {
    mutating func customShuffle() {
        for i in 0 ..< (count - 1) {
            let j = Int(arc4random_uniform(UInt32(count - i))) + i
            swapAt(i, j)

Now Swift’s arrays have built-in shuffle() and shuffled() methods that are superior – use those instead.

4. Multiline strings

Go ahead and search your project for \n – the new-line metacharacter. In older Swift versions it was common to use that to make multi-line strings, like this:

let old = "He thrusts his fists\nagainst the posts\nand still insists\nhe sees the ghosts"

There are still some places you might want to use that syntax, particularly if it’s just a single line break. However, for longer strings you should switch to Swift’s multi-line string format, """:

let new = """
He thrusts his fists
against the posts
and still insists
he sees the ghosts

Multi-line strings work just the same with string interpolation, so there’s no reason not to make the switch.

5. Dictionary default values

Reading values from a dictionary returns an optional type, because the key you requested might not exist. So, it’s common to have code like this:

if let score = scores["Sophie"] {
    scores["Sophie"] = score + 1
} else {
    scores["Sophie"] = 1

To find this in your code, try enabling regular expression searches and use this: if let .*\[" – that should pick up code like the above.

Rather than constantly relying on nil coalescing or unwraps, you can often switch to providing the default value you want so you don’t have to deal with optionality at all:

scores["Sophie", default: 0] += 1

6. #warning and #error directives

It’s common to add comments like // FIXME: This needs to replaced next sprint to your code, because they appear in Xcode’s jump bar and help remind you to come back to some code later on.

While those comments still have a place – usually for something that won’t be looked at in the near future – modern Swift code has a better solution: #warning. This will add a build warning to your code at a particular location, flagging up work so you can see it at a glance.

So, rather than writing this:

// FIXME: This needs to replaced next sprint

Try using this instead:

#warning("This needs to replaced next sprint")

You can also use #error if you want Xcode to refuse to build your code until something has been changed, for example an important setting such as an API key.

7. One-sided ranges

Swift’s one-sided ranges let us avoid off-by-one errors, express our intent more clearly, and are even shorter to type. For example, old code looked like this:

let singers = ["John", "Paul", "George", "Ringo"]
let subset = singers[2..<singers.count]

You search for it using Xcode’s regular expression search system, entering something like this: \[\d+\.\.<[a-z].*\.count.

That can now be replaced by a one-sided range like this:

let subset = singers[2...]

8. Checking for the simulator

If you search your project for #if (arch you might come across code like this:

#if (arch(i386) || arch(x86_64))
    // this is the simulator
    // this is a real device

That code is still valid, but Swift now has a better way of handling that same situation: the targetEnvironment directive. This checks specifically for the simulator rather than using compiler directives, so when Apple inevitably switches to ARM for macOS you won’t have a problem:

#if targetEnvironment(simulator)
    // this is the simulator
    // this is a real device

What else is there?

Swift currently gets three or four large revisions every year, and is showing no sign of slowing down. Don’t worry if you’re having a hard time keeping up with all the language features: it’s changing so fast it’s not easy, which is why I made What’s new in Swift? – a website that lets you select your current and target Swift versions, and see what’s changed with code examples.

Do you have any tips for keeping your Swift projects up to date, or new Swift features you particularly love? Let me know on Twitter – I’m @twostraws!


Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Average rating: 5.0/5

Click here to visit the Hacking with Swift store >>