Регулярные выражения в Swift с примерами: на практике
Регулярные выражения (regex) — мощный инструмент для работы с текстовыми данными. В Swift для работы с регулярными выражениями используется класс NSRegularExpression из фреймворка Foundation. В этой статье мы разберем практические примеры использования регулярных выражений в Swift для iOS и macOS приложений: от простого поиска до сложной валидации и замены текста.
Основы работы с NSRegularExpression в Swift
Класс NSRegularExpression предоставляет все необходимые методы для работы с регулярными выражениями. Базовый алгоритм работы включает три шага:
- Создание объекта шаблона регулярного выражения
- Применение шаблона к строке
- Обработка результатов (совпадений, замен)
Создание регулярного выражения
Вот как создать и применить простейшее регулярное выражение для поиска слова "Swift":
import Foundation
let text = "Изучаем Swift и iOS разработку"
let pattern = "Swift"
do {
let regex = try NSRegularExpression(pattern: pattern)
let results = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
for match in results {
if let range = Range(match.range, in: text) {
print("Найдено: \(text[range])")
}
}
} catch {
print("Ошибка в регулярном выражении: \(error.localizedDescription)")
}
Обратите внимание на преобразование Range — это стандартный способ работы с позициями в строках Swift. Результат выполнения: "Найдено: Swift".
Практический пример 1: Поиск email-адресов
Одна из самых частых задач — извлечение email-адресов из текста. Рассмотрим пример, который найдет все email в строке:
import Foundation
func findEmails(in text: String) -> [String] {
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
var emails: [String] = []
do {
let regex = try NSRegularExpression(pattern: pattern)
let results = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
for match in results {
if let range = Range(match.range, in: text) {
emails.append(String(text[range]))
}
}
} catch {
print("Ошибка: \(error)")
}
return emails
}
// Пример использования
let sampleText = "Контакты: support@example.com и admin@site.org"
let foundEmails = findEmails(in: sampleText)
print(foundEmails) // ["support@example.com", "admin@site.org"]
Этот шаблон учитывает стандартные символы, разрешенные в email, и проверяет наличие точки в доменной части. Для продакшена можно добавить поддержку кириллических доменов.
Практический пример 2: Валидация номера телефона
Валидация вводимых данных — важная часть любого приложения. Вот как проверить российский номер телефона в формате +7 (XXX) XXX-XX-XX:
import Foundation
func isValidPhoneNumber(_ phone: String) -> Bool {
// Шаблон для российского номера: +7 (XXX) XXX-XX-XX
let pattern = "^\\+7\\s?\\(?[0-9]{3}\\)?\\s?[0-9]{3}-?[0-9]{2}-?[0-9]{2}$"
do {
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(location: 0, length: phone.utf16.count)
return regex.firstMatch(in: phone, range: range) != nil
} catch {
return false
}
}
// Тестирование
let validNumbers = ["+7 (495) 123-45-67", "+74951234567", "+7 495 1234567"]
let invalidNumbers = ["8 (495) 123-45-67", "+7 (495) 123-45-678", "abc"]
for number in validNumbers {
print("\(number): \(isValidPhoneNumber(number))") // true
}
for number in invalidNumbers {
print("\(number): \(isValidPhoneNumber(number))") // false
}
Шаблон использует якоря ^ и $ для проверки всей строки, что исключает частичное совпадение. Это критично для валидации.
Практический пример 3: Замена и извлечение данных
Регулярные выражения отлично подходят для замены подстрок. Например, можно заменить все даты в формате "дд.мм.гггг" на "гггг-мм-дд":
import Foundation
func reformatDates(in text: String) -> String {
let pattern = "(\\d{2})\\.(\\d{2})\\.(\\d{4})"
let replacement = "$3-$2-$1"
do {
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(text.startIndex..., in: text)
let result = regex.stringByReplacingMatches(in: text, range: range, withTemplate: replacement)
return result
} catch {
return text
}
}
// Пример
let input = "Сегодня 15.03.2024, завтра 16.03.2024"
let output = reformatDates(in: input)
print(output) // "Сегодня 2024-03-15, завтра 2024-03-16"
Здесь используются группы захвата () и обратные ссылки $1, $2, $3. Это мощный механизм для перестановки частей текста.
Извлечение данных с помощью групп
Часто нужно не просто найти совпадение, а извлечь его части. Например, разбор URL на компоненты:
import Foundation
struct URLComponents {
let scheme: String
let host: String
let path: String
}
func parseURL(_ url: String) -> URLComponents? {
let pattern = "^(https?)://([A-Za-z0-9.-]+)(/[A-Za-z0-9/.-]*)?$"
do {
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(url.startIndex..., in: url)
if let match = regex.firstMatch(in: url, range: range) {
let schemeRange = Range(match.range(at: 1), in: url)!
let hostRange = Range(match.range(at: 2), in: url)!
let pathRange = match.range(at: 3).location != NSNotFound ? Range(match.range(at: 3), in: url) : nil
return URLComponents(
scheme: String(url[schemeRange]),
host: String(url[hostRange]),
path: pathRange != nil ? String(url[pathRange!]) : ""
)
}
} catch {
print("Ошибка парсинга URL: \(error)")
}
return nil
}
// Пример
if let components = parseURL