Avatar ·

Как правильно работать с подстрокой (substring) в Swift?

Всем привет! Я новичок в Swift и никак не могу разобраться с тем, как вырезать часть строки. В других языках типа Python или JavaScript это делается очень просто: `"Hello"[0:4]` или `"Hello".substring(0, 4)`. Но в Swift всё как-то сложно. Вот что я пробую: ```swift let text = "Hello, world!" let sub = text[0...4] // Ошибка: 'subscript' is unavailable ``` Xcode ругается и говорит, что так нельзя. Потом я попробовал: ```swift let start = text.index(text.startIndex, offsetBy: 0) let end = text.index(text.startIndex, offsetBy: 4) let range = start...end let sub = text[range] // Получаю Substring, а не String ``` Я получил какой-то `Substring`, а не обычную строку. И как с ним работать? Почему нельзя просто взять и вырезать часть строки как в других языках? Почему всё так сложно? Объясните, пожалуйста, как правильно и просто получить подстроку в Swift. Мне нужно вырезать первые 5 символов, последние 3 символа, а также часть с 3-го по 7-й символ. И чтобы результат был обычным String, а не чем-то странным. Заранее спасибо! <<>>

Почему в Swift так сложно с подстроками?

Swift уделяет большое внимание безопасности и Unicode-корректности. Строки в Swift состоят не из байтов или простых "символов", а из графем-кластеров (grapheme clusters). Это значит, что один видимый символ (например, "é" или "👍🏽") может состоять из нескольких Unicode-кодовых точек. Поэтому просто взять и обратиться по индексу [0] — некорректно с точки зрения Unicode, так как индекс должен учитывать эти кластеры. Именно поэтому Swift использует String.Index, а не целые числа.

Правильный способ: работа с индексами

Чтобы получить подстроку, нужно сначала создать диапазон (range) индексов, а затем использовать сабскрипт. Вот базовый пример:

let text = "Hello, world!"

// Получаем первые 5 символов
let startIndex = text.startIndex
let endIndex = text.index(startIndex, offsetBy: 5)
let substring = text[startIndex..<endIndex] // "Hello"
print(substring) // Выведет: Hello

Обратите внимание: text[startIndex..<endIndex] возвращает Substring, а не String. Substring — это легковесное представление, которое ссылается на память исходной строки. Чтобы получить полноценный String, просто инициализируйте новую строку:

let newString = String(text[startIndex..<endIndex]) // "Hello"

Практические примеры для ваших задач

Вот как решить три ваши задачи:

1. Первые 5 символов

let text = "Hello, world!"
let prefix = String(text.prefix(5)) // "Hello"
// Или через индексы:
let range = text.startIndex..<text.index(text.startIndex, offsetBy: 5)
let prefix2 = String(text[range])

2. Последние 3 символа

let suffix = String(text.suffix(3)) // "ld!"
// Через индексы:
let start = text.index(text.endIndex, offsetBy: -3)
let range = start..<text.endIndex
let suffix2 = String(text[range])

3. Часть с 3-го по 7-й символ (индексы с 0)

let start = text.index(text.startIndex, offsetBy: 2) // 3-й символ (индекс 2)
let end = text.index(text.startIndex, offsetBy: 7)   // 8-й символ (индекс 7)
let range = start..<end
let middle = String(text[range]) // "llo, "

Удобные методы-обёртки

Чтобы не писать каждый раз индексы, можно создать свои функции или расширения:

extension String {
    func substring(from: Int, to: Int) -> String {
        let start = index(startIndex, offsetBy: from)
        let end = index(startIndex, offsetBy: to)
        return String(self[start..<end])
    }
    
    func substring(from: Int) -> String {
        let start = index(startIndex, offsetBy: from)
        return String(self[start...])
    }
    
    func substring(to: Int) -> String {
        let end = index(startIndex, offsetBy: to)
        return String(self[..<end])
    }
}

// Использование:
let text = "Hello, world!"
print(text.substring(from: 0, to: 5)) // "Hello"
print(text.substring(from: 7))        // "world!"
print(text.substring(to: 5))          // "Hello"

Важно: Substring vs String

  • Substring — это временное представление, которое ссылается на память исходной строки. Это эффективно, так как не требует копирования.
  • String — это независимая копия. Если вы планируете хранить подстроку долго или передавать её в другой контекст, всегда конвертируйте Substring в String.
  • Substring поддерживает те же методы, что и String (например, .count, .uppercased()), но его нельзя использовать там, где ожидается String (например, в параметрах функции).

Надеюсь, теперь стало понятнее. Swift жертвует краткостью кода ради безопасности и корректной работы с Unicode. Как только вы привыкнете к индексам, это перестанет казаться сложным.

<<>>

Альтернативный подход: использование NSString

Если вам очень неудобно работать с String.Index, вы можете временно преобразовать строку в NSString из Objective-C, который поддерживает целочисленные индексы. Это особенно удобно, если вы работаете с ASCII-текстом или английскими строками без специальных символов.

import Foundation

let text = "Hello, world!" as NSString
let sub1 = text.substring(with: NSRange(location: 0, length: 5)) // "Hello"
let sub2 = text.substring(from: 7) // "world!"
let sub3 = text.substring(to: 5)   // "Hello"

// Результат — String, не Substring
print(type(of: sub1)) // String

Однако у этого подхода есть недостатки:

  • Работает корректно только с символами, которые умещаются в один UTF-16 код (большинство латиницы, кириллицы, цифры). Для эмодзи или составных символов (например, "é" как комбинация "e" + accent) может дать неверный результат.
  • Требует импорта Foundation.
  • Не является "чистым Swift" — это мост к Objective-C.

Когда использовать?

Если вы точно знаете, что строка содержит только простые символы (например, номера телефонов, ID, английские слова), и вам нужно быстро прототипировать — можно использовать NSString. Для production-кода, особенно с поддержкой мультиязычности, лучше освоить родной Swift-подход с индексами.

Также помните: если вы используете NSString, то длина строки считается в UTF-16 единицах, а не в графем-кластерах. Для большинства европейских языков разницы нет, но для эмодзи и азиатских иероглифов она критична.