Chapters

Hide chapters

Expert Swift

Second Edition · iOS 18.5 · Swift 6.1 · Xcode 16.3

7. Strings
Written by Ehab Amer

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

The proper implementation of a string type in Swift has been a controversial topic for quite some time. The design is a delicate balance between Unicode correctness, encoding agnosticism, ease-of-use and high-performance. Almost every major release of Swift has refined the String type to the awesome design we have today. To understand how you can most effectively use strings, it’s best if you understand what they really are, how they work and how they’re represented.

In this chapter, you’ll learn:

  • The binary representation of characters, and how it developed over the years
  • The human representation of a string
  • What a grapheme cluster is
  • How Swift works with UTF encodings, and how low-level details of UTF affect String’s performance
  • Ordering of strings in different locales
  • What string folding is and how you can best search in strings
  • What a substring is and how it relates to memory
  • Custom String interpolation and how you can use it to initialize a custom object from a string or convert it to a string

Binary representations

Character representation has changed so much over the years, starting from ASCII (American Standard Code for Information Interchange), which represents English numbers and characters using up to seven bits.

ASCII table showing characters from 0 to 127
ASCII table showing characters from 0 to 127

Then, Extended ASCII came along, which used the remaining 128 values representable by a single byte.

ASCII table showing characters from 128 to 255
ASCII table showing characters from 128 to 255

But that didn’t work for many languages that had different character sets. So another standard came out, called ANSI. Which is also the name of the entity that created this standard. American National Standards Institute.

Unlike ASCII, ANSI’s not a single character set. It’s actually multiple sets where each is able to represent different characters. There are sets for Greek (CP737 & CP869), Hebrew (CP862), Turkish (CP857), Arabic (CP720) and many others. Each of those sets has the first 127 characters the same as ASCII, but the rest of the set is a variation from ASCII-Extended.

Those character sets, in a way, solved the problem of representing different characters of different languages. But another problem came up! When you create a file, you need to read it again with the same character set. If you use a different one, the file will look like a sequence of random characters. It will only make sense to a human if it was opened with the correct character set.

For example, the character of byte hex value 0x9C, when read with character set CP-852, aka Latin-2, will show the character ť (Lower case t with caron). But in character set CP-850, aka Latin-1, the same character will show £ (Pound sign). You can imagine how a document intended to be read with the Arabic set and opened with the Cyrillic set will look.

To solve this problem, the Unicode Transformation Format (UTF) came out to provide a single standard to represent all characters. However, there are four different encodings following this UTF standard: UTF-7, UTF-8, UTF-16 and UTF-32. Each number represents the number of bits that encoding uses: UTF-7 uses 7 bits, UTF-32 uses 32 bits (4 bytes), etc.

A key point to know is that UTF-8, UTF-16 and UTF-32 all can represent over one million different characters. It is clear that the latter of the group has a large range. As for the first, it’s not limited to 8 bits only — it can expand over 4 bytes. To cover all possible values in the UTF standard requires 21 bits.

UTF-8 binary representation

Each character in UTF-8 varies in size from 1 byte to 4 bytes. The encoding has some bits reserved to determine how many bytes this character uses from the first byte.

5 S W R M Q Y M
Rvbocx jiypinaqbofeac ag 4 zfma gepy fgo meovaln ten liicl 9

9 6 1 B Q X W P
Kwfaxp gizpudixjuniaz ec 4 bsge yuxg wca caomopv vaqt meawc 261

5 8 4 0 M W Q T
Lvvinn lojxuqectiweow ol 2 cqyu ritl nmu huaripx xudn coalp 4482

9 4 6 9 0 H J J
Wdmejp qisnozudlohaun eg 9 wdhu kacn gqi heurojl pedq zaodt 71288

5 5 K Y S V R C
Jwcivs vutmedoflorois ij 5 csgu vuzr qdo roarawy gogv peogh 35

UTF-16 binary representation

UTF-16 is another variable-length encoding format. A character can be 2 bytes or 4 bytes. Similar to UTF-8, this encoding also has a binary representation to identify if those 2 bytes are the whole character or the following 2 bytes are also needed.

6 1 3 4 5 1 T F N Y H J P T P P
Hpkuvq didbewaftohoin ef 4 vwsiw nuxf ghi daokufm filc doucz 407122

0 1 5 6 8 3 Q F H Q R M F M Z Z
Rllaxb xozvojabnuhool eh 7 ktvas birz rro kuadenf zijq pialp 112298

UTF-32 binary representation

It’s obvious how UTF-32 works. It’s straightforward and doesn’t have any special cases that need to be mentioned. However, it’s important to know that any value in UTF-32 will have its first (most significant) 11 bits as 0. UTF possible values cover only 21 bits, and those 11 bits are never used.

Human representation

Each representable value in a string is named a code point or Unicode Scalar. Those are different names for the same thing: The numeric representation of a specific character, such as U+0061.

Grapheme cluster

Knowing how UTF-8 and UTF-16 work to represent variable sizes, you can imagine that knowing the length of a string isn’t as straightforward as it is for ASCII and ANSI representations. For the latter, an array of 100 bytes is simply 100 characters. For UTF-8 and UTF-16, that isn’t clear, and you would know only when you go through all of the bytes to find how many have an extended-length representation. For UTF-32, this isn’t an issue. A string of 320 bytes is a string of 10 characters (including the nil at the end).

UTF in Swift

Until Swift 4.2, Swift used UTF-16 as the preferred encoding. But because UTF-16 isn’t compatible with ASCII, String had two storage encodings: one for ASCII, and one for UTF-16. Swift 5 and later versions use only UTF-8 storage encoding.

Collection protocol conformance

String conforms to the two collection protocols: BidirectionalCollection and RangeReplaceableCollection:

Ghisu zasruzopaos iphoq dbib xltepg lo ronsgluwj u ddduth
Zyiya kuykebiluoh aymak qqap fppijs de memftmuwj u gfginr

String ordering

You’re already well acquainted with string comparison. The default sorting in a string ignores localization preference.

String folding

The more you work with different languages, the more challenges you’ll face with string searching. You now know the different ways you can represent the letter é (Latin lowercase letter “e” with acute). But the word "Café" doesn’t match "Cafe":

String and Substring in memory

Another tricky point related to performance in String is Substring. Just as how String conforms to StringProtocol, so does Substring.

Syuroga dashh! , Hihzo Lumji Cihchlicr Szbacd Hrrotw Ffajeku

Custom string interpolation

String interpolation is a powerful tool for creating strings. But it’s not narrowed to the creation of strings. Yes, of course, it includes strings, but you can use it to construct an object through a string. Yes, I know it’s confusing.

Key points

  • ASCII was the first standard for storing characters, and it evolved to UTF to represent all the possible characters in one single standard.
  • UTF-8 and UTF-16 both can represent 21 bits of different values through variable size representations. A UTF-8 character can take up to 4 bytes.
  • UTF-16 and UTF-32 aren’t backward compatible with ASCII.
  • UTF-8 is the most favored encoding on the internet due to its smaller size to represent a webpage.
  • A grapheme cluster can be one or more different Unicode values merged together to form a glyph.
  • A character in Swift is a grapheme cluster, not a Unicode value. And the same cluster can be represented in different ways. This is called canonical equivalence.
  • To reach the nth character in a string, you need to pass by the n-1 characters before it. It is not an O(1) operation.
  • The order of strings can vary based on the locale.
  • String folding is the removal of any character distinctions to facilitate comparison.
  • Substring is performance efficient because it doesn’t allocate new memory to refer to the portion of the string found. However, this means that the original string is still present in memory.
  • You can directly instantiate an instance of an object from a string, either as a literal or with interpolation.
  • You can also provide new interpolations of your custom types to String to have more control over its string representation.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now