Новая жизнь строк в Swift: прощай, substring, здравствуй, slicing

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

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

Начать курс

Как перестать бояться deprecated и полюбить slicing в Swift

Если вы когда-нибудь писали код на Swift и пытались вырезать часть строки, вы наверняка сталкивались с этим пугающим сообщением: 'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator. Звучит как приговор? На самом деле это — приглашение в новый, более безопасный и мощный мир работы со строками. Давайте разберемся, что произошло, почему Apple решила изменить привычный API и как теперь правильно «резать» строки в Swift 4 и новее.

Почему substring ушел в прошлое?

Старая добрая substring(to:) работала, но была... не очень умной. Она возвращала новый объект Substring, который, по сути, был просто «окошком» в исходную строку. Это приводило к путанице: вы думали, что работаете с самостоятельной строкой, а на самом деле держали ссылку на старую память. В Swift 4 решили навести порядок: теперь все операции с подстроками возвращают тип Substring, а для получения полноценной строки нужно явно конвертировать результат. Звучит сложнее? На практике — гораздо понятнее и безопаснее.

Знакомьтесь: String slicing subscript

Новый подход использует сабскрипты (subscripts) и диапазоны (ranges). Вместо методов substring(to:), substring(from:) и substring(with:) вы теперь пишете код, который выглядит как работа с массивами. Вот базовая аналогия:

let fullString = "Привет, мир!"
// Старый способ (deprecated)
// let oldResult = fullString.substring(to: fullString.index(fullString.startIndex, offsetBy: 6))

// Новый способ
let newResult = fullString[..<fullString.index(fullString.startIndex, offsetBy: 6)]
print(newResult) // "Привет"

Видите оператор ..<? Это и есть тот самый partial range from operator — частичный диапазон «от начала до указанного индекса, не включая его». Мощно? Ещё бы.

Три кита нового подхода: from, to, through

Давайте разберем три основных сценария, которые раньше покрывали методы substring.

1. substring(to:) → prefix(upTo:) или частичный диапазон

Хотите взять всё до определённого индекса? Используйте ..< (half-open range) или метод prefix(upTo:):

let str = "Hello, Swift"
let index = str.firstIndex(of: ",") ?? str.endIndex

// Вариант через сабскрипт
let result1 = str[..<index]
print(result1) // "Hello"

// Вариант через метод
let result2 = str.prefix(upTo: index)
print(result2) // "Hello"

2. substring(from:) → suffix(from:) или частичный диапазон

Нужно взять всё от индекса до конца? Используйте ... (closed range) с опущенным началом:

let startIndex = str.index(str.startIndex, offsetBy: 7)
let result3 = str[startIndex...]
print(result3) // "Swift"

// Или через метод
let result4 = str.suffix(from: startIndex)
print(result4) // "Swift"

3. substring(with:) → обычный диапазон

Хотите вырезать конкретный кусок? Просто укажите диапазон:

let range = str.index(str.startIndex, offsetBy: 7)..<str.endIndex
let result5 = str[range]
print(result5) // "Swift"

// Или через closed range
let closedRange = str.index(str.startIndex, offsetBy: 7)...str.index(before: str.endIndex)
let result6 = str[closedRange]
print(result6) // "Swift"

Обратите внимание: все эти операции возвращают Substring, а не String. Если вам нужна полноценная строка, просто оберните результат в String():

let newString = String(result1)
print(type(of: newString)) // String

А что с prefix и suffix? Они же не deprecated!

Да, методы prefix(_:), suffix(_:), prefix(upTo:), suffix(from:) остались и активно используются. Они удобны, когда нужно взять несколько символов от начала или конца. Но для точной работы с произвольными индексами сабскрипты — ваш выбор.

Вот таблица для быстрой справки (запомните её, она спасёт вас на собеседовании):

  • Старый метод substring(to:) → Новый способ str[..<index] или str.prefix(upTo:)
  • Старый метод substring(from:) → Новый способ str[index...] или str.suffix(from:)
  • Старый метод substring(with:) → Новый способ str[range]

Практический пример: разбор строки с данными

Представьте, что вы парсите ответ от сервера: "user123:John:Doe:1990". Вам нужно извлечь ID пользователя (первые 7 символов) и год рождения (последние 4 символа). Вот как это делается по-новому:

let data = "user123:John:Doe:1990"

// Извлекаем ID (первые 7 символов)
let idSubstring = data[..<data.index(data.startIndex, offsetBy: 7)]
let userId = String(idSubstring)
print("User ID: \(userId)") // "user123"

// Извлекаем год (последние 4 символа)
let yearSubstring = data[data.index(data.endIndex, offsetBy: -4)...]
let birthYear = String(yearSubstring)
print("Birth year: \(birthYear)") // "1990"

// А теперь извлечём имя (между первым и вторым двоеточием)
if let firstColon = data.firstIndex(of: ":"),
   let secondColon = data[data.index(after: firstColon)...].firstIndex(of: ":") {
    let nameSubstring = data[data.index(after: firstColon)..<secondColon]
    let name = String(nameSubstring)
    print("Name: \(name)") // "John"
}

Чувствуете разницу? Вместо цепочки substring(from:).substring(to:) мы используем чёткие индексы и диапазоны. Код стал читаемее и предсказуемее.

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

Вот три грабли, на которые наступают почти все:

  1. Забывают конвертировать Substring в String. Если вы попытаетесь передать Substring туда, где ожидается String, компилятор не ругнётся, но могут возникнуть проблемы с производительностью (Substring хранит ссылку на исходную строку). Всегда делайте String(yourSubstring).
  2. Путают half-open и closed диапазоны

    Блоги

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