Swift: String (Строки)

Строки в swift — значимого типа (value type), и по идее при передаче их в качестве параметра в функцию — каждый раз должна выполняться полная копия этой структуры, но обещают что компилятор знает когда передавать копию а когда ту же ссылку (в зависимости от того, будет ли меняться строка внутри функции или нет)
String взаимозаменяем (bridged) с NSString из Objective-C

Строки состоят из символов (Character), полностью поддерживают unicode.
Unicode порождает определенные нюансы, каждый символ представляет из себя расширенный графемовый кластер (Extended Grapheme Clusters), который может состоять из нескольких Unicode scalar
К примеру символ é может быть представлен как одним скаляром
é (LATIN SMALL LETTER E WITH ACUTE, or U+00E9)
так и двумя:
e (LATIN SMALL LETTER E, or U+0065) + COMBINING ACUTE ACCENT scalar (U+0301)
Но по смыслу это будет один и тот же символ, что можно проверить

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

var str: String = «test»
var chr = str[2] // так нельзя

Связано это с тем же двояким представлением символа, как одним так и несколькими scalar’ами, что требует разное количество памяти, и нельзя просто индекс преобразовать в отступ в памяти и получить доступ к символу, придется пробегать по всем символам, т.е. внутри строка скорее не массив а двусвязный список.

Вместо этого предлагается использовать глобальные функции.
Получить длину строки — count(str), при этом если символ будет состоять из двух unicode scalar, он будет посчитан как 1 символ (как тот же é представленный «\u{65}\u{301}»)
Но имейте ввиду, если строка большая, — каждый вызов count будет пробегать по всей строке, так что лучше кэшировать длину.
Но мы можем получить длину строки как length в NSString. До Xcode 6.3 beta было доступно свойство utf16Count. Сейчас его убрали и вместо него предлагают работать с utf16 представлением строки. Но в случае использования Extended Grapheme Clusters результаты будут разными

В предрелизной версии Swift 2 снова поменяли поведение, теперь вместо глобальной функции count(), следует использовать свойство count свойства characters из расширения _CollectionDefaultsType, что я в принципе и предложил ниже еще до появления Swift 2. Язык молод, все меняется, что тут скажешь. Так что в Swift 2.0 уже надо будет писать так

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

Как по мне в последующих версиях Swift такие свойства добавит сама Apple (P.S. как в воду глядел).

А теперь разберемся как же работать с символами внутри строки.
Т.к. строки — по сути списки, работать будем посредством индексов (index type) String.Index, которые указывают на текущий символ.
Мне привычней было бы называть этот тип — итератором, т.к. индекс путаешь с числовым индексом в массиве, но просто будем иметь ввиду, что в контексте строк индекс будет означать именно String.Index
startIndex — указывает на первый символ в строке
endIndex — на символ ЗА последним последний

Конкретный символ можно получить как раз по такому индексу (а не числовому)

Передвигать индекс можно с помощью методов successor() — следующий символ и predessor() — предыдущий

Для определения конкретного символа по числовому индексу — ввели глобальную функцию advance(start:n:), первый параметр — Index, с которого ведется отсчет, второй параметр — числовой индекс символа который нам нужен

Но каждый раз делать такие преобразования — издевательство, так что расширение строки — дело нужное и полезное

Вставить символ можно с помощью insert(_:atIndex:)

Вставить строку с помощью splice(_:atIndex:)

В swift 1.2 работал следующий код

В swift 2.0 String более не соответствует SequenceType, но добавили свойство characters типа String.CharacterView, которое соответствует SequenceType
Так что код превратится в

Удалить несколько символов с помощью диапазона можно так:

Заменить несколько символов другими с помощью replaceRange

Реализовать set в лоб для изменения символа по числовому индексу — не получилось (self[] только для чтения), но можно реализовать через replaceRange

Хотя бы так (код тестовый, без assert‘ов и прочих плюшек production кода)

Можно сделать еще веселее и позволить заменять не символ по числовому индексу, а делать замену символа на строку

Но есть нюанс, теперь необходимо напрямую указывать тип значения которое мы должны получить при обращении по индексу — Character или String.

Удобство изменения нивелируется неудобством получения.

При сравнении строк так же учитываются не scalar, а Extended Grapheme Clusters
Методы hasPrefix(_:) и hasSuffix(_:) позволяют узнать начинается или оканчивается строка с указанных символов или нет (сравнение посимвольно учитывая Extended Grapheme Clusters)

Из строки можно получить utf8 значения, utf16 и пресловутые scalar

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *