Swift известен системой pattern matching. Большинство разработчиков используют её в switch, иногда в if case или guard case. Но в языке есть ещё одна конструкция, которую многие никогда не видели — for case let.
Интересно, что даже разработчики с несколькими годами опыта часто о ней не знают. Более того, в официальной документации Apple она упоминается лишь вскользь, потому что технически это не отдельная фича языка, а комбинация существующих механизмов. Тем не менее, эта конструкция может заметно упростить код.
В этой статье я хочу разобраться:
что такое for case let
как она работает
где её можно применять
и когда её лучше не использовать
Я пишу про iOS-разработку, Swift и интересные фичи языка в своём Telegram-канале:
Pattern Matching в Swift
Swift позволяет сопоставлять не только значения, но и структуру данных. Swift поддерживает pattern matching в четырёх местах:
switch
if case
guard case
for case
И for case — пожалуй, самая малоизвестная из них.
Например, enum:
enum Event { case message(String) case image(URL) case system(String) }
Извлечь значение из enum можно так:
if case let .message(text) = event { print(text) }
Здесь происходит pattern matching:
Swift проверяет, соответствует ли значение .message
если соответствует — извлекает text
Обычный цикл
Предположим, у нас есть массив событий:
let events: [Event]
И нужно обработать только .message.
Чаще всего код будет выглядеть так:
for event in events { if case let .message(text) = event { print(text) } }
Работает нормально, но есть проблемы:
лишняя вложенность
фильтрация происходит внутри тела цикла
код немного шумный
for case let
Swift позволяет записать ту же логику компактнее:
for case let .message(text) in events { print(text) }
Swift автоматически:
пропускает элементы, не подходящие под паттерн
извлекает связанные значения
выполняет код только для совпавших элементов
Что происходит под капотом
for case let — это синтаксический сахар.
Следующие два примера эквивалентны.
// for case for case let .message(text) in events { print(text) } //обычный цикл for element in events { if case let .message(text) = element { print(text) } }
Компилятор просто генерирует if case внутри цикла.
Работа с Optional
Один из самых удобных сценариев — обработка массива Optional.
let users: [User?] // Обычно пишут так: for user in users { guard let user else { continue } print(user.name) } // Но можно проще: for case let user? in users { print(user.name) }
Swift:
пропускает nil
автоматически распаковывает значения
Работа с Result
При работе с Result это тоже очень удобно.
let results: [Result<Int, Error>] // Только успешные результаты: for case let .success(value) in results { print(value) } // Только ошибки: for case let .failure(error) in results { print(error) }
Обработка событий Combine / RxSwift
В реактивных потоках часто встречаются enum-события.
enum StreamEvent<T> { case value(T) case error(Error) case completed } // Если нужно обработать только значения: for case let .value(item) in events { process(item) // Без switch, без if }
Фильтрация типов
for case можно использовать и для фильтрации типов. Например, при работе с UIView:
for case let button as UIButton in view.subviews { button.setTitle("Tap", for: .normal) }
Swift:
проверяет тип
выполняет as
пропускает остальные элементы
Вложенный pattern matching
Swift умеет матчить вложенные структуры.
enum Response { case success(Event) case failure(Error) } // Можно сделать так: for case let .success(.message(text)) in responses { print(text) }
То есть Swift проверяет два уровня enum сразу.
Работа с tuple
Можно сопоставлять структуры данных.
let pairs: [(Int?, Int?)] = [ (1,2), (nil,3), (4,nil), (5,6) ] // Обработать только полностью заполненные пары: for case let (x?, y?) in pairs { print(x + y) }
where внутри цикла
Можно добавить дополнительную фильтрацию:
for case let .message(text) in events where text.count > 10 { print(text) }
Компилятор:
делает один проход
не создаёт промежуточные массивы
не использует closure
Сравнение с functional-подходом
Та же задача через compactMap:
events.compactMap { if case let .message(text) = $0 { return text } return nil }
Что здесь происходит:
создаётся closure
происходит проход по массиву
создаётся новый массив
Когда стоит использовать for case
Хорошие сценарии:
фильтрация enum
обработка Optional
работа с Result
фильтрация типов
обработка потоков событий
Когда лучше использовать switch
Если логика становится сложной:
for event in events { switch event { case .message(let text): ... case .image(let url): ... case .system: ... } }
В таких случаях switch будет понятнее.
Итог
for case let — это инструмент Swift, фактически это pattern matching внутри цикла, который позволяет:
фильтровать элементы
извлекать значения
писать более декларативный код
И хотя эта конструкция редко встречается в коде, она может заметно упростить многие сценарии. Если вы до этого момента о ней не знали — вы точно не одиноки. Многие Swift-разработчики открывают её для себя только спустя несколько лет работы с языком.
