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:
let word = "café"
print(word.count) // 4
print(word.utf8.count) // 5Let's go through the output:
word.countcounts the characters, that is, the large beads. There are4.word.utf8.countcounts the bytes, that is, the small beads. There are5: 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:
for ch in word {
print(ch, terminator: " ") // c a f é
}
print()Let's see what happens here:
for ch in wordgoes through the string from the first character to the last.- The constant
chgetsc,a,fandéin turn, and its type isCharacter. éarrives whole, as one character, not as two separate bytes.- 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.
1let word = "café"2var bytes = 03for ch in word {4 bytes += ch.utf8.count5 print(ch, bytes)6}7print(word.count, bytes)
The string has four characters: c, a, f and é.
What the widget showed:
c,aandftake 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
countis4, and there are5bytes.
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:
let thumb = "👍🏽"
print(thumb.count, thumb.utf8.count) // 1 8Each 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:
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. countcounts characters, andutf8.countcounts bytes: for"café"they are4and5.for ch in sgoes through a string character by character, andchhas the typeCharacter.- A
Characteris a character the way a person sees it, even if it is made of several Unicode numbers.