Do-Catch: какво е това, конструкция и обработка на грешки в Swift

Автор: IT Sectr Публикувано: 2026-05-26 Време за четене: 8 мин

Do-Catch — конструкция на езика Swift за обработка на грешки, която прихваща изключения, хвърлени чрез throw, и изпълнява съответния блок код. За разлика от традиционния try-catch в други езици, Swift изисква изрично маркиране на функциите, които могат да хвърлят грешка, с модификатора throws. Според Swift.org, 2026, Do-Catch е основният механизъм за обработка на грешки в Swift, осигуряващ безопасност на ниво типове.

Основни точки

  • Do-Catch — конструкция на Swift, прихващаща грешки от блокове с try и throw.
  • throws — маркер на функция, сигнализиращ, че тя може да хвърли грешка.
  • try — ключова дума за извикване на throwing-функция вътре в do-блок.
  • catch — блок за обработка на грешка с възможност за съпоставяне по тип.
  • try? и try! — алтернативни форми, преобразуващи грешката в nil или причиняващи crash.

Какво е Do-Catch?

Do-Catch — е конструкция в Swift, състояща се от блок do, вътре в който се изпълнява код, способен да хвърли грешка, и един или повече блокове catch, които обработват тази грешка. Блокът do съдържа извиквания на throwing-функции, маркирани с try, а catch блоковете съпоставят грешката по тип.

Swift имплементира модел на структурирана обработка на грешки, различен от изключенията в Objective-C. Тук грешката не е exception с развиване на стека, а стойност, съответстваща на протокола Error. Хвърлянето на грешка чрез throw предава контрола на най-близкия catch блок без разходи за развиване на стека.

Ключова характеристика на Swift — изричност на обработката. Компилаторът не позволява извикване на throwing-функция без try, do-catch или алтернативен механизъм (try?, try!). Това елиминира ситуации, при които грешка остава незабелязана.

Модел на грешките в Swift: протокол Error и throwing функции

Основата за обработка на грешки в Swift е протоколът Error. Всеки тип, съответстващ на този протокол, може да бъде хвърлен чрез throw. Обикновено грешките се дефинират като enum с потвърждение Error.

Протокол Error

Error — празен протокол (маркер). Не изисква имплементиране на методи, само потвърждение на съответствие. Компилаторът на Swift използва Error за статична проверка: функция с throws може да хвърли само стойност, съответстваща на Error.

Throwing функции

Функция, маркирана с throws, трябва да бъде извикана с try. Ако вътре в такава функция се случи throw, контролът се предава на извикващия do-catch. Ако грешката не бъде обработена никъде във веригата на извиквания, тя се предава на по-горно ниво.

swift
enum FileError: Error {
    case notFound
    case permissionDenied
    case corrupted(String)
}

func readFile(path: String) throws -> String {
    guard FileManager.default.fileExists(atPath: path) else {
        throw FileError.notFound
    }
    return try String(contentsOfFile: path)
}

Предаване на грешка с rethrows

rethrows — модификатор за функции, които приемат throwing-затваряне като параметър. Ако подаденото затваряне не хвърля грешка, rethrows функцията може да бъде извикана без try. Това позволява писане на генерични функции от по-висок ред, съвместими с throwing и non-throwing затваряния.

Синтаксис на Do-Catch: множество catch блокове

Swift поддържа множество catch блокове със съпоставяне по тип на грешката. Това прилича на when в Kotlin или switch по тип: първият подходящ catch се изпълнява, останалите се пропускат.

Съпоставяне по тип

Всеки catch блок може да съдържа шаблон за съпоставяне с конкретен тип грешка. Swift проверява блоковете по ред, така че по-специфичните типове трябва да бъдат преди общите. Ако никой шаблон не съвпада, се изпълнява catch блокът без шаблон.

Обработка на множество типове

Типова безопасност Do-Catch позволява обработка на всяка грешка поотделно или комбиниране на няколко типа в един блок чрез запетая в pattern-matching. Това елиминира обемистите if-else вериги, характерни за Objective-C, където анализът на грешката се извършваше чрез код за връщане или NSError с проверка на domain и code.

Swift също поддържа catch where — съпоставяне с допълнително условие. Например, може да се филтрират грешки по определен код или текст: catch URLError where error.code == .notConnectedToInternet. Това намалява броя на catch блоковете при запазване на точността на обработка.

За дебъгване на throwing функции Swift предоставя разликата между грешки и изключения. Грешката в Swift — е върната стойност, която не изисква развиване на стека. Изключенията в Objective-C (@try/@catch) работят на ниво runtime и се използват само за фатални повреди. Do-Catch обработва именно грешки, а не изключения, което го прави предвидим и производителен.

При разработка на библиотеки и SDK е важно да се помни, че throwing функциите са част от публичния договор. Типът на грешката не е специфициран в сигнатурата, затова документирайте какви грешки може да хвърли функцията в коментар или чрез enum, подобен на Result. Това помага на потребителите на API да пишат правилни catch блокове без да гледат имплементацията.

В контекста на асинхронното програмиране, Do-Catch с async/await решава проблема callback hell. Преди в Swift обработката на грешки в асинхронен код изискваше вложени затваряния с проверка Error? в completion handler. С async/await и Do-Catch асинхронният throwing код изглежда като синхронен, което опростява четенето и поддръжката на сложни вериги от заявки с обработка на грешки на всяка стъпка.

swift
do {
    let content = try readFile(path: "/data/config.json")
    process(content)
} catch FileError.notFound {
    createDefaultConfig()
} catch FileError.permissionDenied {
    requestAccess()
} catch FileError.corrupted(let detail) {
    logCorruption(detail)
} catch {
    print("Неизвестна грешка: \(error)")
}

Try-варианти: try, try?, try! и кога да ги използваме

Swift предлага три варианта за извикване на throwing функции: try, try? и try!. Всеки вариант решава конкретна задача и има своите ограничения.

try — стандартно извикване

try се използва вътре в do-блок и грешката се обработва в catch. Това е основният начин за извикване на throwing функции. Компилаторът изисква try да бъде в контекст, където грешката може да бъде прихваната.

try? — преобразуване в Optional

try? преобразува резултата на throwing функцията в Optional. Ако функцията хвърли грешка, try? връща nil, иначе — Optional с успешна стойност. Удобно за извиквания, където грешката може да бъде игнорирана, но е необходим индикатор за успех.

try! — принудително изпълнение

try! потиска обработката на грешки. Ако throwing функция хвърли грешка, приложението завършва с runtime грешка. Използвайте try! само когато сте абсолютно сигурни, че грешката е невъзможна — например при зареждане на вграден ресурс.

swift
// try? — игнорираме грешката, получаваме nil при повреда
if let data = try? Data(contentsOf: url) {
    processData(data)
}

// try! — само ако грешката е гарантирано невъзможна
let bundled = try! String(contentsOfFile: "Assets/default.txt")

// try — стандартният вариант в do-catch
do {
    let result = try performNetworkRequest()
    updateUI(result)
} catch {
    showError(error)
}

Do-Catch в iOS разработката: практически примери

Do-Catch се използва активно в iOS разработката за работа със системни API. Нека разгледаме типични сценарии: работа с файловата система, Core Data и мрежови заявки.

Файлова система

Много методи на FileManager са throwing. Do-Catch позволява коректно обработване на липсващ файл, зает ресурс или недостатъчни права.

Мрежови заявки с Codable

Парсването на JSON чрез JSONDecoder — throwing операция. Do-Catch прихваща грешки при декодиране и мрежови повреди отделно, предоставяйки на потребителя точен текст на грешката.

swift
struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Извикване с обработка
do {
    let user = try await fetchUser(id: 42)
    showUser(user)
} catch let error as URLError {
    showNetworkAlert(error)
} catch let error as DecodingError {
    showParseError(error)
} catch {
    showGenericError(error)
}

Типични грешки при работа с Do-Catch

Разработчиците, особено преминаващите от други езици, често допускат характерни грешки при използване на Do-Catch. Нека разгледаме най-честите.

  • Забравен try — извикване на throwing функция без try води до грешка при компилация. Swift не позволява неявно игнориране на грешки.
  • Празен catch — catch блок без обработка скрива грешката. Винаги логвайте или обработвайте грешката, дори ако изглежда невъзможна.
  • Catch без съпоставяне — един общ catch за всички грешки лишава от предимствата на типовата безопасност. Разделяйте обработката по типове чрез множество блокове.
  • Прекомерна употреба на try! — try! в production код е източник на неочаквани crash-ове. Използвайте само за вградени ресурси или тестове.

Често задавани въпроси

Как се различава Do-Catch в Swift от try-catch в други езици?

В Swift грешката е стойност, съответстваща на протокола Error, а не exception с развиване на стека. Throwing функциите трябва да бъдат изрично маркирани с throws, и компилаторът изисква try при извикване — това елиминира необработени грешки на етапа на компилация.

Може ли да се използва Do-Catch с async/await?

Да, async функциите също могат да бъдат throws, и do-catch работи за тях. Извикването на async throwing функция изисква try await вътре в do-блок. Това е стандартният начин за обработка на грешки в асинхронен Swift код.

Как да обработим грешка без do-catch?

Има алтернативи: try? преобразува грешката в nil, try! причинява crash при грешка, а rethrows позволява предаване на грешка от затваряне. Do-catch остава основният начин за изрична обработка.

Може ли една функция да хвърля различни типове грешки?

Swift не типизира хвърляните грешки — всяка throwing функция може да хвърли всеки тип, съответстващ на Error. Разделяйте обработката чрез съпоставяне в catch по конкретни типове грешки.

Струва ли си да обвиваме всички грешки в собствен персонализиран тип?

Да, това е най-добрата практика. Дефинирайте AppError като enum с потвърждение Error и конвертирайте системните грешки в домейн грешки. Това унифицира обработката и изолира слоя на приложението от системни детайли.

Обобщение

  • Do-Catch — конструкция на Swift за обработка на грешки със задължително маркиране throws на функциите и try при извикване.
  • Грешките в Swift — стойности, съответстващи на протокола Error, а не изключения с развиване на стека, което дава нулеви разходи при липса на повреда.
  • Множество catch позволяват отделна обработка на различни типове грешки с pattern-matching, аналогичен на switch.
  • try? преобразува грешката в nil за прости сценарии, try! — принудително извикване с риск от crash, използвано само за гарантирано успешни операции.
  • Do-Catch с async/await — стандартният начин за обработка на грешки в асинхронен Swift код с използване на try await.
  • Избягвайте празни catch и прекомерна употреба на try! — това скрива грешки и води до неочаквани повреди в production.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също