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

Why do strings behave differently from arrays in Swift?

Paul Hudson    @twostraws   

Updated for Xcode 15

If you think about it, strings are really just lots of individual characters that come together to form text: “Hello”, for example. But whereas Swift lets us read array values using myArray[3], we can’t do the same with strings – myString[3] is invalid.

The reason for this goes back to emoji and other similarly complex characters, which are made up of multiple special characters back to back. Although they are individual letters, they must be treated as one cohesive unit: we can’t take just part of an emoji, because it wouldn’t make sense. For example, the US flag is made up of the characters “Regional indicator symbol letter U” and “Regional indicator symbol letter S”, which, when put together, are interpreted as the United States flag.

Those two characters can’t be pulled apart individually – if you read the first character of the string, you wouldn’t want to get back “Regional indicator symbol letter U”, or half the US flag, or something weird. Instead, both of them need to be kept together in order for the emoji to make sense.

What this means is that if you have a string containing four emoji, it’s possible your string contains 10 or even 20 of these special symbols. Many of them are likely meaningless to humans by themselves, and only have the correct emoji meaning when joined with the other special symbols around them.

Now think about trying to read the fourth character of a string using something like myString[3]. If I wrote out a string on some graph paper, one letter per box, and asked you to jump to the fourth letter, you could just count along four boxes and you’d be there – you wouldn’t actually need to know how many letters were in each box. And that system scales beautifully: if our graph paper had 50 boxes per page, and I asked you to read letter 50,000, you could literally skip over hundreds of pages without even counting the boxes, because you know there’s always exactly one letter per box and 50 boxes per page.

Now imagine I said to you that actually only some letters took up one box on the grid paper. Some took up two boxes, some three, some four, some five and some even more than that. How would you find letter 50,000 now?

The answer is that you’d need to start at the very first letter, then move along box by box, one by one, checking every box to see whether it contained a letter by itself or was one special character that was part of a larger letter.

Well, this little thought experiment is actually exactly how Swift works: if you ask for character 50,000 in a string, it needs to start at the very beginning and count all the way through letter by letter until it finds the one you want. It’s slow, and gets slower the further through a string you want to read.

And so the Swift team made a decision: yes, they absolutely could make myString[49999] work, but if they did that you might think the code was easy. So, they specifically made that functionality unavailable so that folks wouldn’t write it by accident and wonder why their code was slow.

In case you were curious, arrays don’t have the same problem, because they store everything in a box that’s exactly the same size. This is because their usage is very different from strings: we nearly always use strings in their entirety, whereas it’s more common to read individual items from an array.

Before I’m done, I want to add one thing that might not have occurred to you: if you want to check whether a string is empty, you should write this:

if myString.isEmpty {
    // code
}

And not this:

if myString.count == 0 {
    // code
}

The first code can return true if the string has any letters, but the second has to count all the letters in the string – has to go through all the boxes in our grid paper – just to compare that final number against 0.

If you’d like more detail on this topic, I have a blog post that will help: https://www.hackingwithswift.com/articles/181/why-using-isempty-is-faster-than-checking-count-0

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!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.