Строки в Swift: почему substring работает не так, как ты привык?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: "Подвох", который ждет каждого Swift-разработчика

Представь ситуацию: ты уверенно пишешь код на Swift, всё идет гладко, и тут тебе нужно взять первые три символа из строки. Ты, не задумываясь, пишешь что-то вроде myString[0..<3] — и получаешь... ошибку компиляции. Знакомо?

Добро пожаловать в мир String substring в Swift. Это одна из тех тем, которая вызывает настоящую боль у новичков и даже у опытных разработчиков, переходящих с других языков. Вопрос "How does String substring work in Swift" на Stack Overflow собрал тысячи просмотров — и это не случайно.

В этой статье мы разберем, почему Swift такой "странный" в работе с подстроками, как правильно использовать substring в Swift, и какие новые возможности появились в последних версиях. Поехали!

Почему Swift не позволяет просто так взять и вырезать кусок строки?

В большинстве языков (Python, JavaScript, C#) строка — это просто массив символов. Хочешь подстроку? Пиши str[0:3] и получай результат. В Swift всё иначе.

Причина кроется в работе с Unicode. Строки в Swift — это не просто байты или символы ASCII. Они состоят из графем-кластеров (grapheme clusters). Один символ, который видит пользователь (например, "é" или "👍🏽"), может занимать несколько Character или даже несколько Unicode.Scalar. Представь, что ты обрезаешь строку посередине такого кластера — получишь битые данные.

Поэтому Swift использует индексы (String.Index), а не целые числа. Это гарантирует, что ты никогда не разрежешь символ пополам.

Основы: String.Index и простое извлечение подстроки

Давай начнем с базового примера. Допустим, у нас есть строка:

let fullString = "Привет, Swift!"
// Хотим взять слово "Swift"
let startIndex = fullString.index(fullString.startIndex, offsetBy: 8)
let endIndex = fullString.index(fullString.startIndex, offsetBy: 13)
let substring = fullString[startIndex..<endIndex]
print(substring) // Выведет: "Swift"

Обрати внимание: substring имеет тип Substring, а не String. Это важное отличие. Substring — это "окошко" в исходную строку. Она не копирует данные, а просто ссылается на часть памяти исходной строки. Это супер-эффективно, но может привести к утечкам памяти, если исходная строка большая, а подстрока маленькая.

Чтобы превратить Substring в полноценную String, просто используй инициализатор:

let newString = String(substring)
print(newString) // "Swift"

Синтаксический сахар: RangeExpression

Писать каждый раз index(offsetBy:) утомительно. К счастью, Swift позволяет использовать более короткие формы с помощью RangeExpression.

Оператор ... (ClosedRange)

let str = "Hello, World!"
if let range = str.range(of: "World") {
    let worldSubstring = str[range]
    print(worldSubstring) // "World"
}

Частичные диапазоны (Partial Range)

Хочешь взять всё до определенного индекса или после него? Легко:

let text = "Swift Programming"
let prefix = text[..<text.index(text.startIndex, offsetBy: 5)]
print(prefix) // "Swift"

let suffix = text[text.index(text.startIndex, offsetBy: 6)...]
print(suffix) // "Programming"

Обрати внимание: prefix и suffix — это тоже Substring.

Встроенные методы: prefix, suffix, dropFirst, dropLast

Swift предоставляет удобные методы, которые избавляют от ручного вычисления индексов. Они возвращают Substring.

  • str.prefix(5) — первые 5 символов.
  • str.suffix(3) — последние 3 символа.
  • str.dropFirst(2) — строка без первых 2 символов.
  • str.dropLast(4) — строка без последних 4 символов.

Пример:

let greeting = "Доброе утро!"
let shortGreeting = greeting.prefix(6)
print(shortGreeting) // "Доброе"
let withoutExclamation = greeting.dropLast()
print(withoutExclamation) // "Доброе утро"

Это самый безопасный и читаемый способ получить substring в Swift.

Swift 4+: StringProtocol и единый интерфейс

Начиная с Swift 4, и String, и Substring подчиняются протоколу StringProtocol. Это значит, что все методы для работы с подстроками (.prefix, .split, .trimmingCharacters и т.д.) доступны и для Substring тоже. Ты можешь передавать Substring в функции, которые ожидают StringProtocol, без лишних преобразований.

Но помни: если функция явно требует String, тебе придется сделать String(substring).

Новинки iOS 14+: .formatted() и упрощение работы с диапазонами

В iOS 14 / Swift 5.3 появились новые способы работы с диапазонами. Например, можно использовать \.self в комбинации с ключевыми путями, но для подстрок это не дало революции. Гораздо интереснее метод .formatted() для чисел и дат, но к строкам он прямого отношения не имеет.

Однако стоит отметить, что в iOS 15+ появился метод String.init(Substring), который работает быстрее, чем раньше. Если ты часто конвертируешь Substring в String, обновление iOS — твой друг.

Типичные ошибки и как их избежать

Давай разберем три самые частые ловушки.

Ошибка 1: Использование целочисленных индексов

Неправильно:

let name = "Анна"
// let firstChar = name[0] // Ошибка!

Правильно:

let firstChar = name[name.startIndex] // "А"
let secondChar = name[name.index(after: name.startIndex)] // "н"

Ошибка 2: Забыть про Substring и держать ссылку на большую строку

Если ты сохраняешь Substring от огромной строки (например, текст книги) на длительное время, исходная строка не будет освобождена из памяти. Решение: всегда конвертируй в String

Блоги

Рекомендаций по книгам