Перейти к содержанию
Educora
Продвинутый22 мин8 / 14

Протоколы, ошибки и SwiftUI

Задавай типам общие правила и новые возможности с помощью протоколов и расширений, обрабатывай ошибки через `throws` и `do-try-catch`, а затем посмотри, как строится первый экран на SwiftUI.

Проверь себя
В этом уроке ты узнаешь
  • Объявлять протокол, создавать соответствующие ему типы и добавлять методы с помощью расширений
  • Выбрасывать и перехватывать ошибки с помощью throws, throw, try и do-catch
  • Читать простое представление SwiftUI и объяснять роль @State

Представь электрическую розетку: зарядка телефона, чайник, лампа — совсем разные устройства, но их вилки сделаны по одному правилу, поэтому подходят к любой розетке. В Swift такое правило называется протоколом. В этом уроке ты изучишь протоколы и обработку ошибок, а в конце познакомишься с экраном на SwiftUI, который объединяет всё, что ты уже знаешь.

Протоколы и расширения

Определение
Протокол

Набор правил, описывающий, какие свойства и методы должны быть у типа. В самом протоколе кода нет; каждый тип, который ему соответствует (по-английски conform), сам выполняет эти требования.

Ниже протокол Shape требует, чтобы у каждой фигуры были имя и площадь. Rectangle и Square — совершенно разные структуры, но обе соответствуют протоколу, поэтому их можно хранить в одном массиве. А расширение (extension) протокола даёт всем соответствующим типам готовый метод summary().

Swift
protocol Shape {
    var name: String { get }
    func area() -> Double
}

extension Shape {
    func summary() -> String {
        "\(name): area \(area())"
    }
}

struct Rectangle: Shape {
    let width, height: Double
    var name: String { "Rectangle" }
    func area() -> Double { width * height }
}

struct Square: Shape {
    let side: Double
    var name: String { "Square" }
    func area() -> Double { side * side }
}

let shapes: [any Shape] = [Rectangle(width: 4, height: 2.5), Square(side: 3)]
for shape in shapes {
    print(shape.summary())
}
Ожидаемый результат
Rectangle: area 10.0
Square: area 9.0

С помощью расширения можно добавлять новые свойства и методы к существующим типам — даже к встроенным Int и String. Внутри расширения self — это само значение.

Swift
extension Int {
    var isEven: Bool {
        self % 2 == 0
    }

    func times(_ action: () -> Void) {
        for _ in 0..<self {
            action()
        }
    }
}

print(7.isEven)
print(10.isEven)
3.times {
    print("Swift!")
}
Ожидаемый результат
false
true
Swift!
Swift!
Swift!

Обработка ошибок

Некоторые операции могут не удаться: на счёте не хватает денег, файл не найден, пропал интернет. В Swift такая функция помечается словом throws, а когда что-то идёт не так, она выбрасывает ошибку через throw. Ошибка обычно — перечисление, соответствующее протоколу Error. При вызове такой функции перед ней пишут try, а ошибку перехватывают в блоке do-catch.

Swift
enum WithdrawError: Error {
    case invalidAmount
    case insufficientFunds(needed: Int)
}

func withdraw(_ amount: Int, from balance: Int) throws -> Int {
    guard amount > 0 else {
        throw WithdrawError.invalidAmount
    }
    guard amount <= balance else {
        throw WithdrawError.insufficientFunds(needed: amount - balance)
    }
    return balance - amount
}

do {
    let left = try withdraw(30, from: 100)
    print("Left: \(left) AZN")
    _ = try withdraw(200, from: left)
    print("This line is skipped")
} catch WithdrawError.insufficientFunds(let needed) {
    print("Not enough money, need \(needed) AZN more")
} catch {
    print("Other error: \(error)")
}
Ожидаемый результат
Left: 70 AZN
Not enough money, need 130 AZN more

Проследи, как выполняется программа: первый вызов успешен, и остаётся 70 манатов. Второй вызов выбрасывает ошибку, поэтому остаток блока do пропускается и управление переходит в подходящий catch. Последний catch ловит все остальные ошибки, и внутри него ошибка автоматически называется error.

Первый экран на SwiftUI

SwiftUI — инструмент Apple для создания интерфейсов. В нём ты не рисуешь экран шаг за шагом, а просто описываешь, как он должен выглядеть — это называется декларативным подходом. Код ниже — экран, счётчик которого растёт при каждом нажатии на кнопку. Запустить его можно только в Xcode или Swift Playgrounds, но каждую строку ты уже можешь понять.

Swift
import SwiftUI

struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 16) {
            Text("Taps: \(count)")
                .font(.largeTitle)
            Button("Tap me") {
                count += 1
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
В Xcode этот код создаёт экран iPhone; в консоль он ничего не выводит.
  • struct ContentView: View — экран — обычная структура, соответствующая протоколу View. Протокол требует лишь одного: свойства body.
  • var body: some View — вычисляемое свойство, которое возвращает то, что показывается на экране.
  • VStack располагает элементы вертикально, Text показывает текст, а замыкание в конце Button выполняется при нажатии.
  • Модификаторы вроде .font(...) и .padding() меняют внешний вид и записываются цепочкой через точку.
  • #Preview показывает живой предпросмотр экрана на холсте (canvas) Xcode.
Определение
@State

Изменяемое значение, которое хранит представление. Когда значение @State меняется, SwiftUI автоматически пересчитывает body и обновляет экран — менять Text вручную не нужно.

  1. 1
    Создай проект

    В Xcode выбери File › New › Project…, возьми шаблон App в разделе iOS и убедись, что в качестве интерфейса выбран SwiftUI.

  2. 2
    Напиши код

    Открой файл ContentView.swift и введи туда код выше.

  3. 3
    Посмотри предпросмотр

    Экран сразу появится на холсте справа; нажми кнопку и проверь, что счётчик растёт.

  4. 4
    Запусти в симуляторе

    Выбери модель iPhone вверху и нажми Cmd+R — приложение откроется в симуляторе iPhone.

собрать и запустить приложениеCmd+R
только собрать (проверить ошибки)Cmd+B
показать или скрыть холст (предпросмотр)Cmd+Option+Return
очистить папку сборкиCmd+Shift+K

Главное

  • Протокол описывает, что должно быть у типа; соответствующие типы сами выполняют требования.
  • extension добавляет новые методы к существующим типам — даже к Int — и к протоколам.
  • Функция с throws выбрасывает ошибки через throw; её вызывают с try и перехватывают ошибки в do-catch.
  • try? превращает ошибку в nil, а try! при ошибке роняет программу.
  • SwiftUI описывает экран как структуру, соответствующую View; при изменении @State экран обновляется автоматически.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что должен сделать тип, чтобы соответствовать протоколу?