Перейти к содержимому
Теория · Протоколы и обобщённый код · урок 3 из 9

Расширения

Квадрат уже работает фигурой, и тут выясняется, что нужен ещё его периметр. Можно вернуться к объявлению Square и дописать свойство. А если тип чужой, например Int из стандартной библиотеки? Его объявление вы поправить не можете.

Вернёмся к вакансиям и представим курсы повышения квалификации. Сотрудник приходит на них, учится новому делу и возвращается тем же человеком, но с новым умением. Переписывать его анкету для этого не нужно.

В Swift роль таких курсов играет расширение: оно добавляет умения уже объявленному типу, даже тому, который писали не вы.

Расширение готового типа

Научим целые числа двум новым вещам:

SWIFT
extension Int {
    var isEven: Bool { self % 2 == 0 }
    func squared() -> Int { self * self }
}

let n = 7
print(n.isEven, n.squared()) // false 49
print(4.isEven) // true

Давайте разберём программу по строкам:

  1. extension Int открывает расширение типа Int. Всё, что внутри фигурных скобок, получат все целые числа.
  2. var isEven: Bool — вычисляемое свойство. self здесь — само число, у которого спросили свойство.
  3. func squared() — новый метод: число умножает само себя.
  4. n.isEven и n.squared() вызываются так, будто они были в Int с самого начала. Даже у числа 4, записанного прямо в коде, теперь есть isEven.

Здесь картинка с курсами неточна. Курсы проходит один сотрудник, а расширение учит весь тип сразу. Новое умение получает каждое число программы, в том числе созданное выше строки с extension.

Расширение своего типа

Свои типы расширяют так же. Часто так делят код: в объявлении типа — его данные, а в расширении — то, что тип умеет с ними делать.

SWIFT
struct Square {
    let side: Double
}

extension Square {
    var perimeter: Double { side * 4 }
}

print(Square(side: 3).perimeter) // 12.0

Внутри расширения видны свойства из объявления: perimeter считает по side, как обычное вычисляемое свойство.

Соответствие протоколу в расширении

Значок тоже можно получить на курсах. Если тип объявлен без протокола, соответствие добавляют расширением: после имени типа пишут двоеточие и протокол, а внутри выполняют его требования.

SWIFT
protocol Shape {
    func area() -> Double
}

extension Square: Shape {
    func area() -> Double { side * side }
}

let shapes: [any Shape] = [Square(side: 3)]
print(shapes[0].area()) // 9.0

Square объявили без : Shape, а теперь квадрат соответствует протоколу и попадает в массив [any Shape]. Так же своему протоколу может соответствовать и чужой тип: extension Int: Shape с методом area() внутри не меняет объявление Int, но выдаёт ему значок.

Что можно добавить

Этот вопрос часто задают на собеседовании: что можно добавить расширением, а что нельзя? Из того, что вы уже знаете, расширение может добавить:

  • вычисляемые свойства;
  • методы;
  • свойства и методы типа: static let и static func;
  • структуре — свой init, и почленный инициализатор при этом не пропадает;
  • соответствие протоколу.

А вот обычное хранимое свойство, которое у каждого экземпляра своё, расширением не добавить. Это частая ошибка с расширениями, и её разберём отдельно.

Частая ошибка

Расширением попробовали добавить квадрату цвет:

SWIFT
extension Square {
    var color = "red" // ✗
}
TEXT
error: extensions must not contain stored properties

Программа не компилируется: расширение не может добавить хранимое свойство экземпляра. Из таких свойств складывается само значение типа, и их список задаёт объявление. Например, значение Int на компьютерах и смартфонах занимает 64 бита, и весь код, который работает с числами, на это рассчитывает. А свойство типа static let расширение добавить может: оно одно на весь тип и в значения не входит.

Расширение добавляет умения, но не меняет, из чего сделано значение: курсы учат новому делу, но рост сотрудника не меняют. Исправление — добавить var color = "red" в объявление Square, рядом с side.

Главное

  • Расширение добавляет умения уже объявленному типу, даже чужому, как Int, и их получают все значения этого типа.
  • Расширение может добавить вычисляемые свойства, методы, свойства и методы типа, init структуры и соответствие протоколу.
  • Обычное хранимое свойство расширение добавить не может: из таких свойств складывается значение, и их задаёт только объявление типа.