Видеокурсы по изучению языка программирования Swift. Подробнее

Unicode (Юникод)

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Юникод является международным стандартом для кодирования, представления и обработки текста в разных системах письма. Он позволяет представлять почти любой символ из любого языка в стандартизированную форму. А также читать и записывать эти символы во внешний источник, и из него. К таким источникам относятся, например текстовый файл или веб-страница. Типы String и Character в Swift полностью совместимы с Юникодом, что описано в этом разделе.

Скалярные величины Юникода

Если посмотреть глубже, то можно увидеть, что собственный String тип в Swift построен из скалярных значений (Unicode scalar ) Юникода. Скалярная величина Юникода является уникальным 21-разрядным числом для символа или модификатора, например, U+0061 для LATIN SMALL LETTER A ("a") , или U+1F425 для FRONT-FACING BABY CHICK ("🐥").

Заметка

Скалярная величина Юникода - это любая точка кода в диапазоне  U+0000 до U+D7FF включительно, или  U+E000 до U+10FFFF тоже включительно. Скалярные величины Юникода не включают Юникод суррогатные пары точек кода, т.е. точки кода в диапазоне U+D800 до U+DFFF включительно.

Обратите внимание, что не все 21-битные скалярные величины Юникода присваиваются символу. Некоторые скалярные величины содержатся в резерве для будущего присваивания. Скалярные величины, присвоенные символу, как правило имеют название, например LATIN SMALL LETTER A и FRONT-FACING BABY CHICK, что можно увидеть в примере выше.

Расширяемые наборы графем

Каждый экземпляр типа Character в Swift представляет один расширенный набор графем. Расширенный набор графем является последовательностью одного и более скалярных величин Юникода, которые (будучи объединенными) производят один читаемый символ.

Вот пример. Буква é может быть представлена в виде самостоятельной скалярной величины Юникода é (LATIN SMALL LETTER E WITH ACUTE, или U+00E9 ). Тем не менее, та же буква может быть представлена в виде пары скалярных величин-стандартной буквой е (LATIN SMALL LETTER E, или U+0065 ), и последующей скалярной величиной COMBINING ACUTE ACCENT (U + 0301). COMBINING ACUTE ACCENT графически присоединяется к предшествующей скалярной величине, превращая e в é , в момент, когда распознающая Юникод символы система, начинает рендер.

В обоих случаях буква é представляется в виде одного значения Character, которое представляет собой расширенный набор графем. В первом случае набор содержит одну скалярную величину; во втором случае две:

let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" // e с последующим  ́
// eAcute равен é, combinedEAcute равен é

Расширенный набор графем - это удобный способ представления многих сложных печатных символов, как одного значения Character. Например, Hangul звуки из корейского алфавита могут быть представлены либо как предварительно набранные, или наоборот, как разложенные ряды символов. Оба этих варианта определяются как одно Character значение в Swift:

let precomposed: Character = "\u{D55C}"
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"
//precomposed равен "한", decomposed равен "한"

Расширенный набор графем позволяет скалярам заключающих символов (например, COMBINING ENCLOSING CIRCLE, или U+20DD), заключать другие скалары Юникода и выглядеть как значение типа Character.

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute равен é⃝

Скалярные величины Юникода для региональных символов могут быть объединены в пары для создания одного Character значения, таких как эта комбинация: REGIONAL INDICATOR SYMBOL LETTER U (U+1F1FA) и REGIONAL INDICATOR SYMBOL LETTER S (U+1F1F8):

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS равен 🇺🇸

Подсчет символов

Чтобы получить количество значений Character в строке, используйте count для строки:

let unusualMenagerie = "Коала 🐨, Улитка 🐌, Пингвин 🐧, Верблюд 🐫"
print("unusualMenagerie содержит \(unusualMenagerie.count) символов")
// Выведет "unusualMenagerie содержит 39 символов"

Обратите внимание, что использование расширенных наборов графем для Character значений означает, что конкатенация и модификация не всегда могут повлиять на количество символов в строке.

Например, если вы инициализируете новую строку со словом из четырех символов cafe, а затем добавите COMBINING ACUTE ACCENT (U+0301) в ее конце, то результирующая строка будет по-прежнему иметь количество символов - 4 , с четвертым символ é , а не e:

var word = "cafe"
print("количество символов в слове \(word) равно \(word.count)")
// Выведет "количество символов в слове cafe равно 4"
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
print("количество символов в слове  \(word) равно \(word.count)")
// Выведет "количество символов в слове café равно 4"

Заметка

Расширенный набор графем может состоять из одного или более скалярных величин Юникода. Это означает, что различные символы, и различное отображение одного и того же символа, могут потребовать огромных объемов памяти для хранения. Из-за этого, символы в Swift не занимают одинаковый объем памяти в строке. В результате этого, количество символов в строке не может быть подсчитано без итерации в строке, для определения границ расширенного набора графем. Если вы работаете с особенно длинными значениями строк, имейте ввиду, что свойство count должно итерировать все скалярные величины Юникода в строке для того, чтобы определить символы в этой строке. Количество символов, возвращаемых значением count не всегда совпадает со свойством length у NSString, которое содержит те же символы. Длина NSString основывается на числе 16-битовых блоков кода в UTF-16 представлении строки, а не на количестве расширенных набора графем внутри строки.

Доступ и изменение строки

Вы получаете доступ и меняете строку через ее методы, и свойства, или с помощью синтаксиса сабскрипта.

Индексы строки

Каждое String значение имеет связанный тип индекса: String.Index, что соответствует позиции каждого Character в строке.

Как было упомянуто выше, различные символы могут требовать различные объемы памяти для хранения, поэтому для того, чтобы определить, какой Character в определенной позиции, необходимо итерировать каждую скалярную величину Юникода, от начала или конца этой строки. По этой причине, Swift строки не могут быть проиндексированы целочисленными значениями.

Используйте свойство startIndex для доступа позиции первого Character в String. Свойство endIndex — это позиция после последнего символа в String. В результате, endIndex свойство не является допустимым значением для сабскрипта строки. Если String пустая, то startIndex и endIndex равны.

Вы получаете доступ к индеку до и после указанного индекса при помощи методов index(before:) и index(after:). Для доступа к индексу, расположенного не по соседству с указанным индексом, вы можете использовать метод index(_:offsetBy:) вместо многократного вызова предыдущих методов.

Вы можете использовать синтаксис индекса для доступа Character в определенном индексе String.

“let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a”

Попытка доступа к Character в индексе за пределами диапазона строки выдаст сообщение об ошибке выполнения.

greeting[greeting.endIndex] // ошибка
greeting.index(after: endIndex) // ошибка

Используйте свойство indices, чтобы создать Range всех индексов, используемых для доступа к отдельным символам строки.

for index in greeting.indices {
   print("\(greeting[index]) ", terminator: " ")
}

// Выведет "G u t e n T a g !"

Заметка

Вы можете использовать свойства startIndex, endIndex и методы index(before:), index(after:) и index(_:offsetBy:) с любым типом, который соответствует протоколу Collection. Это включает в себя String, как и показано тут, различные типы коллекций, например Array, Dictionary и Set.

Добавление и удаление

Для того, чтобы вставить символ в строку по указанному индексу, используйте insert(_:at:) метод, а для того, чтобы вставить содержимое другой строки по указанному индексу, используйте метод insert(contentsOf:at:).

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome теперь равен "hello!"
 
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome теперь равен "hello there!”

Для того, чтобы удалить символ из строки по указанному индексу используйте remove(at:), если вы хотите удалить значения по указанному диапазону индексов, используйте метод removeSubrange(_:):

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome теперь равно "hello there"
 
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome теперь равно "hello”

Заметка

Вы можете использовать методы insert(_:at:), insert(contentsOf:at:), remove(at:) и removeSubrange(_:) с любыми типами, которые соответствуют протоколу RangeReplaceableCollection. Это включает в себя String, как показано тут, а так же коллекции, такие как Array, Dictionary и Set.

Подстроки

Когда вы получаете подстроку из строки, например, используя сабскрипт или метод типа и prefix(_:), результат которого возвращает экземпляр подстроки, а не другую строку. Подстроки в Swift имеют практически те же самые методы, что и строки, что означает, что вы можете работать с подстроками так же как и со строками. Однако, в отличие от строк, вы используете подстроки непродолжительное время, пока проводите какие-то манипуляции над строками. Когда вы готовы хранить результат более продолжительное время, то вы конвертируете подстроку в строку. Например:

let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"
 
// Конвертируем в строку для хранения более продолжительное время.
let newString = String(beginning)

Как и строки, каждая подстрока имеет область в памяти, где хранятся символы, создающие эту подстроку. Разница между строками и подстроками в том, что для оптимизации производительности подстока может использовать часть памяти, используемую для хранения исходной строки или часть памяти. которая используется для хранения другой подстроки. (Строки так же имеют похожую оптимизацию, но если две строки делят между собой память, то они считаются равными.) Это оптимизация означает, что у вас не будет потери производительности через копирование памяти, если вы не изменяете строку или подстроку. Как уже было сказано ранее, подстроки не подходят для долгосрочного хранения, так как они повторно используют хранилище исходной строки. Исходная строка должна находиться в памяти до тех пор, пока одна из ее подстрок все еще используется.

В примере выше и greeting является строкой, которая имеет свою облать памяти, где создающие ее символы хранятся. Так как и beginning, явяется подстрокой от greeting, то она переиспользует память. которую использует greeting. И наоборот, newString является строкой, которая была создана из-под строки и теперь она имеет свое хранилище. Рисунок ниже поможет нам разобраться с этими взаимоотношениями:

Заметка

И String и Substring реализуют протокол StringProtocol, что означает, что очень часто бывает удобно для строковых манипуляций принимать значение StringProtocol. Вы можете вызывать такие функции со значением String или Substring.

Сравнение строк

Swift предусматривает три способа сравнения текстовых значений: равенство строк и символов, равенство префиксов, и равенство суффиксов.

Равенство строк и символов

Равенство строк и символов проверяется оператором "равенства" (==) и оператором "неравенства" (!=), что описано в главе «операторы сравнения»:

let quotation = "Мы с тобой похожи"
let sameQuotation = "Мы с тобой похожи"
if quotation == sameQuotation {
  print("Эти строки считаются равными")
}
// Выведет "Эти строки считаются равными"

Два String значения (или два Character значения) считаются равными, если их расширенные наборы графем канонически эквивалентны . Расширенные наборы графем канонически эквивалентны, если они имеют один и тот же языковой смысл и внешний вид, даже если они изначально состоят из разных скалярных величин Юникода.

Например,  LATIN SMALL LETTER E WITH ACUTE (U+00E9) канонически эквивалентна LATIN SMALL LETTER E(U+0065) , если за ней следует COMBINING ACUTE ACCENT (U+0301) . Оба этих расширенных набора графем являются допустимыми вариантами представления символа  é , и поэтому они считаются канонически эквивалентными:

// "Voulez-vous un café?" используем LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
// "Voulez-vous un café?" используем LATIN SMALL LETTER E и COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
if eAcuteQuestion == combinedEAcuteQuestion {
  print("Эти строки считаются равными")
}
// Выведет "Эти строки считаются равными"

Наоборот,  LATIN CAPITAL LETTER A (U+0041, or "A") , используемый в английском языке, не является эквивалентом CYRILLIC CAPITAL LETTER A (U+0410, or "А"), используемой в русском языке. Символы визуально похожи, но имеют разный языковой смысл:

let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("Эти строки считаются не равными")
}
// Выведет "Эти строки считаются не равными"

Заметка

Сравнение строк и символов в Swift не зависит от локализации.

Равенство префиксов и суффиксов

Чтобы проверить, имеет ли строка определенный строковый префикс или суффикс, вызовите hasPrefix(_:) и hasSuffix(_:) методы, оба из которых принимают единственный аргумент типа String, и возвращают логическое значение.

В приведенных ниже примерах рассмотрим массив строк, представляющих местоположение сцены в первых двух актах Ромео и Джульетты Шекспира:

let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]

Вы можете использовать hasPrefix(_:)метод с массивом romeoAndJuliet для подсчета количества сцен в первом акте пьесы:

var act1SceneCount = 0
for scene in romeoAndJuliet {
  if scene.hasPrefix("Act 1 ") {
    act1SceneCount += 1
  }
}
print("Всего \(act1SceneCount) сцен в Акте 1")
// Выведет "Всего 5 сцен в Акте 1"

Точно так же, использование hasSuffix(_:)метода для подсчета количества сцен, которые происходят внутри или вокруг особняка Капулетти и клетки монаха Лоренцо:

var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
  if scene.hasSuffix("Capulet's mansion") {
    mansionCount += 1
  } else if scene.hasSuffix("Friar Lawrence's cell") {
    cellCount += 1
  }
}
print("\(mansionCount) сцен в особняке; \(cellCount) тюремные сцены")
// выводит "6 сцен в особняке; 2 тюремные сцены"

Заметка

hasPrefix(_:)и hasSuffix(_:) методы используются для символ-к-символу канонического эквивалентного сравнения между расширенными наборами графем в каждой строке, как описано в главе «Равенство строк и символов».

Отображение строк Юникода

Если строка Юникода записывается в текстовый файл или какое-либо другое хранилище, то скалярные величины Юникода в этой строке кодируются в одном из нескольких Юникод-определенных форм кодирования. Каждая форма кодирует строку мелкими кусками, известными как единица кода. Сюда включены: UTF-8 форма кодирования (которая кодирует строку в 8-битные блоков кода), UTF-16 форма кодирования (которая кодирует строку в качестве 16-битных блоков кода), и UTF-32 форма кодирования (которая кодирует строку в 32-битные единицы кода).

Swift предоставляет несколько разных способов доступа к отображению строк Юникода. Вы можете итерировать строки с for-in, для получения их индивидуальных Character значений, как расширенных наборов графем. Этот процесс описан в разделе "Работа с символами".

Кроме того, доступ к String значению в одном из трех других Юникод-совместимых отображений:

  • Набор UTF-8 единиц кода (можно получить доступ через свойство строки - utf8)
  • Набор UTF-16 единиц кода (можно получить доступ через свойство строки - utf16 )
  • Набор 21-битных скалярных значений Юникода, что эквивалентно форме кодирования UTF-32 (доступ через свойство строки - unicodeScalars)

Каждый пример, данный ниже, показывает разное отображение следующей строки, которая состоит из символов 'D', 'o', 'g', '!!' ("DOUBLE EXCLAMATION MARK", "U+203C") и , 🐶 ("DOG FACE", "U+1F436") или скалярной величины Юникода U+203C) , и 🐶 символа (DOG FACE , или скалярной величине Юникода  U+1F436 ):

let dogString = "Dog‼🐶"

Отображение UTF-8

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: " ")
}
print("")
// 68 111 103 226 128 188 240 159 144 182

В приведенном выше примере, первые три десятичных codeUnit значения (68, 111, 103 ) отображают символы D, o, и g , чье UTF-8 отображение такое же, как и в ASCII отображении. Следующие три десятичных codeUnit значения (226, 128, 188 ) являются трех байтным UTF-8 отображением символа DOUBLE EXCLAMATION MARK. Последние четыре codeUnit значения (240, 159, 144, 182 ) являются четырех байтным UTF-8 отображением символа DOG FACE.

Отображение UTF-16

for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: " ")
}
print("")
// Prints "68 111 103 8252 55357 56374 "

И опять первые три десятичных codeUnit значения (68, 111, 103 ) отображают символы  D, o, и g , чье UTF-16 отображение такое же, как и в строковом отображении UTF-8 (потому что эти скалярные величины Юникода отображают ASCII символы).

Четвертое codeUnit значение (8252) является десятичным эквивалентом шестнадцатеричного 203C, которое отображает скалярную величину Юникода U+203C для символа  DOUBLE EXCLAMATION MARK. Этот символ может быть отображен в виде самостоятельной единицы кода в UTF-16.

Пятое и шестое codeUnit значение (55357 и 56374) являются суррогатной парой UTF-16 отображения символа  DOG FACE. Эти значения имеют «высоко суррогатное» значение U+D83D (с десятичным значением 55357) и «низко суррогатное» значение U+DC36 (с десятичным значением 56374).

Отображение скалярных величин Юникода

for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: " ")
}
print("")
// Prints "68 111 103 8252 128054 "

Свойство value для первых трех десятичных codeUnit значений (68, 111, 103 ) снова отображают символы D, o, и g.

Четвертое codeUnit значение (8252) снова является десятичным эквивалентом шестнадцатеричного 203C, которое отображает скалярную величину Юникода U+203C для символа DOUBLE EXCLAMATION MARK.

Свойство value для пятого и последнего UnicodeScalar, 128054, является десятичным эквивалентом шестнадцатеричного значения 1F436, которое отображает скалярную величину Юникода U+1F436 для символа DOG FACE.

В качестве альтернативы обращения к свойствам value, каждое UnicodeScalar значение также может быть использовано для построения нового String значения, например, с помощью интерполяции строк:

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶
Swift: 
4.0