День 13 — Протоколы и расширения
Protocol описывает способности типа, не диктуя, как они реализованы. Extension добавляет возможности существующему типу. Вместе они позволяют писать гибкий код без обязательного наследования.
Protocol как контракт
protocol Vehicle {
var name: String { get }
var currentPassengers: Int { get set }
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}{ get } требует чтение property, { get set } — чтение и запись. Protocol не хранит данные и не содержит обычной реализации; он перечисляет требования.
Соответствие protocol
struct Car: Vehicle {
let name = "Автомобиль"
var currentPassengers = 1
func estimateTime(for distance: Int) -> Int {
distance / 80
}
func travel(distance: Int) {
print("Едем \(distance) км")
}
}После : Vehicle компилятор проверяет все требования. Если забыть method или выбрать неверный тип property, код не соберётся.
Функция принимает protocol
Код может работать с любой реализацией общей способности.
func commute(distance: Int, using vehicle: any Vehicle) {
if vehicle.estimateTime(for: distance) > 2 {
print("Долго добираться")
} else {
vehicle.travel(distance: distance)
}
}
var car = Car()
commute(distance: 120, using: car)any Vehicle означает значение некоторого типа, который соответствует Vehicle. Если важен конкретный тип и производительность, Swift также поддерживает generics с some Vehicle, но для начала важнее понять сам контракт.
Несколько protocols
Тип может соответствовать нескольким protocols.
protocol Named {
var name: String { get }
}
protocol Identifiable {
var id: Int { get }
}
struct User: Named, Identifiable {
let name: String
let id: Int
}Protocol composition для параметра записывается как Named & Identifiable.
Extensions
Extension добавляет computed properties и methods существующему типу.
extension String {
var trimmed: String {
trimmingCharacters(in: .whitespacesAndNewlines)
}
mutating func trim() {
self = trimmed
}
}
var title = " Swift "
print(title.trimmed)
title.trim()Extension не может добавить stored property, потому что это изменило бы размер уже существующего типа.
Улучшение читаемости
extension Collection {
var isNotEmpty: Bool {
!isEmpty
}
}
let names = ["Анна"]
if names.isNotEmpty {
print("Список заполнен")
}Расширение Collection работает с Array, Set и другими коллекциями.
Protocol extensions
Extension protocol может предоставить реализацию по умолчанию.
protocol Person {
var name: String { get }
func sayHello()
}
extension Person {
func sayHello() {
print("Привет, я \(name)")
}
}
struct Employee: Person {
let name: String
}
let employee = Employee(name: "Лена")
employee.sayHello()Employee получает готовое поведение, но может написать собственный sayHello().
Это отличается от inheritance: один struct может соответствовать нескольким protocols, а общая реализация не создаёт общей ссылочной идентичности.
Opaque return types: some
Запись some Protocol скрывает конкретный тип результата, но гарантирует один стабильный тип, соответствующий protocol.
func makeVehicle() -> some Vehicle {
Car()
}SwiftUI использует some View, потому что точные типы составных интерфейсов бывают огромными, а вызывающему коду достаточно знать, что результат — View.
Зачем protocols нужны в SwiftUI?
struct ContentView: View {
var body: some View {
Text("Hello")
}
}View — protocol из SwiftUI. Запись struct ContentView: View означает: «ContentView обязуется выполнить требования protocol View». Основное требование — property body, возвращающая другой View.
Ты уже знаешь сам механизм соответствия. В SwiftUI новым является конкретный protocol View и набор готовых types, которые ему соответствуют.
Посмотреть разбор строки в Project 1 — WeSplit →
Где это используется?
Protocols становится частью настоящего интерфейса уже в первых проектах.
- Project 1 — WeSplit →Приложение для разделения счёта между людьми и расчёта чаевых.
- Project 2 — Guess the Flag →Игра-викторина: выбери правильный флаг и получи обратную связь.
- Project 3 — Views and Modifiers →Разбор composition, custom modifiers и того, как SwiftUI строит интерфейс.
Checkpoint
Условие
Создайте protocol Building с properties количества комнат, стоимости и имени агента, а также method для печати описания. Реализуйте его в types House и Office.
Что нужно знать
- требования protocol;
- соответствие нескольких structs одному protocol;
- implementation method;
- function, принимающая
any Building.
Показать подсказку
Protocol может требовать только чтение properties через { get }. Общую реализацию описания можно поместить в protocol extension.
Показать решение
protocol Building {
var rooms: Int { get }
var cost: Int { get }
var agent: String { get }
func printSummary()
}
extension Building {
func printSummary() {
print("Комнат: \(rooms), цена: \(cost), агент: \(agent)")
}
}
struct House: Building {
let rooms: Int
let cost: Int
let agent: String
}
struct Office: Building {
let rooms: Int
let cost: Int
let agent: String
}
House(rooms: 4, cost: 12_000_000, agent: "Анна").printSummary()Что запомнить
Protocol формулирует «что умеет тип», extension добавляет «как это можно сделать». Их сочетание — одна из центральных идей Swift.
Глубже: any, some и generic
Когда any, some или generic
any Vehicleхранит значение неизвестного conforming type во время выполнения;some Vehicleскрывает один конкретный type, выбранный функцией;<T: Vehicle>сохраняет конкретный type вызывающего кода и позволяет связать несколько параметров.
func compare<T: Vehicle>(_ first: T, _ second: T) -> Bool {
first.name == second.name
}Полный разбор constraints, associated types и existential containers находится в Day 18.
Комментарии
Вопросы, находки и подсказки по этой странице.
Войди, чтобы задать вопрос или помочь другим.