Skip to content
Theory · Strings and characters · lesson 2 of 7

Characters and Unicode

In the previous lesson we counted the large beads on the string. Now let's look closer at the small ones that hang under them.

A computer stores only numbers in memory. Memory is divided into cells of eight bits, and each such cell is a byte: it holds a whole number from 0 to 255. The same eight bits in the type Int8 hold -128 to 127, because there half the values go to negative numbers, while the bytes of a string are never negative. To store a character, it is written as one or several bytes. These are the small beads: one hangs under c, and two hang under é.

That's why a string has two different sizes, and Swift can name both:

SWIFT
let word = "café"
print(word.count) // 4
print(word.utf8.count) // 5

Let's go through the output:

  1. word.count counts the characters, that is, the large beads. There are 4.
  2. word.utf8.count counts the bytes, that is, the small beads. There are 5: the letter é takes two bytes.

Unicode and UTF-8

The world has over a hundred thousand characters: Latin and Cyrillic letters, Chinese characters, emoji. Unicode is a shared table where every character has its own number. It has over a million numbers, and one byte holds a number only up to 255.

So a character's number is written as one or several bytes by a special rule. Such a rule is called an encoding. Swift stores strings in the UTF-8 encoding, and in it a character's number takes one to four bytes:

  • a Latin letter, a digit or a space — 1 byte;
  • a letter of a European language with an accent mark, like é or ü, and a Cyrillic letter — 2 bytes;
  • a Chinese character — usually 3 bytes;
  • an emoji — usually 4 bytes.

utf8 shows the string as a row of these bytes. So word.utf8.count is the number of bytes the string takes in UTF-8.

The Character type

The Character type holds one character — one large bead, however many small ones hang under it. A for-in loop can go through a string character by character:

SWIFT
for ch in word {
    print(ch, terminator: " ") // c a f é
}
print()

Let's see what happens here:

  1. for ch in word goes through the string from the first character to the last.
  2. The constant ch gets c, a, f and é in turn, and its type is Character.
  3. é arrives whole, as one character, not as two separate bytes.
  4. The empty print() ends the line of output.

A character, like a string, has utf8: ch.utf8.count is how many bytes this character takes. Press "Next step" and watch bytes: on the first three characters the counter grows by 1, and on é it grows by 2 at once.

LiveThe characters and bytes of caféstep 1 of 11 · frames: 1

The string has four characters: c, a, f and é.

Watch bytes: on the character é the counter grows by 2 at once

What the widget showed:

  • c, a and f take one byte each, so at first there are as many bytes as characters;
  • é is one character of two bytes, and from here on the numbers differ;
  • in the end count is 4, and there are 5 bytes.

Interviewers like to ask this: why isn't a Swift string's count equal to its number of bytes? A good answer: count counts characters the way a person sees them, and one character may take several bytes in UTF-8. The number of bytes is utf8.count.

One character from several numbers

Sometimes a character is made of several Unicode numbers. The emoji 👍🏽 is the number of the "thumbs up" sign plus the number of a skin tone. On screen it is one, and for Swift it is one character too:

SWIFT
let thumb = "👍🏽"
print(thumb.count, thumb.utf8.count) // 1 8

Each of the two numbers takes four bytes, so utf8.count is 8. And count gives 1: a Character is a character the way a person sees it.

Here the bead picture is inaccurate. The large beads on a real string are visible at once, and you can count them by eye. Swift can't do that: to find count, it goes through the string from the start and uses the bytes to decide where one character ends and the next begins.

Common mistake

The length of a nickname was checked with utf8.count, as if it were the number of letters:

SWIFT
let nick = "Zoë"
let isShort = nick.utf8.count <= 3 // ✗
print(isShort)

A three-letter nickname failed the check "no longer than three characters": the program prints false. The letter ë takes two bytes, and utf8.count is 4. Characters are counted by count: nick.count <= 3 gives true.

Key points

  • In UTF-8 a Latin letter takes one byte, é and a Cyrillic letter take two, an emoji takes four or more.
  • count counts characters, and utf8.count counts bytes: for "café" they are 4 and 5.
  • for ch in s goes through a string character by character, and ch has the type Character.
  • A Character is a character the way a person sees it, even if it is made of several Unicode numbers.