Swift’s always changing, so here’s how to keep up!
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.
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
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 = str.data(using: .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)
Equatable
conformanceBack 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 lhs.name == rhs.name
}
}
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.
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.
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.
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
#warning
and #error
directivesIt’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.
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...]
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
#else
// this is a real device
#endif
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
#else
// this is a real device
#endif
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!
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.