Введение: "Подвох", который ждет каждого 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