Swift известен системой pattern matching. Большинство разработчиков используют её в switch, иногда в if case или guard case. Но в языке есть ещё одна конструкция, которую многие никогда не видели — for case let.

Интересно, что даже разработчики с несколькими годами опыта часто о ней не знают. Более того, в официальной документации Apple она упоминается лишь вскользь, потому что технически это не отдельная фича языка, а комбинация существующих механизмов. Тем не менее, эта конструкция может заметно упростить код.

В этой статье я хочу разобраться:

  • что такое for case let

  • как она работает

  • где её можно применять

  • и когда её лучше не использовать

Я пишу про iOS-разработку, Swift и интересные фичи языка в своём Telegram-канале:

https://t.me/swiftynew

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:

  1. Swift проверяет, соответствует ли значение .message

  2. если соответствует — извлекает 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:

  1. проверяет тип

  2. выполняет as

  3. пропускает остальные элементы

Вложенный 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
}

Что здесь происходит:

  1. создаётся closure

  2. происходит проход по массиву

  3. создаётся новый массив

Когда стоит использовать 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-разработчики открывают её для себя только спустя несколько лет работы с языком.