Расширения
Квадрат уже работает фигурой, и тут выясняется, что нужен ещё его периметр. Можно вернуться к объявлению Square и дописать свойство. А если тип чужой, например Int из стандартной библиотеки? Его объявление вы поправить не можете.
Вернёмся к вакансиям и представим курсы повышения квалификации. Сотрудник приходит на них, учится новому делу и возвращается тем же человеком, но с новым умением. Переписывать его анкету для этого не нужно.
В 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Давайте разберём программу по строкам:
extension Intоткрывает расширение типаInt. Всё, что внутри фигурных скобок, получат все целые числа.var isEven: Bool— вычисляемое свойство.selfздесь — само число, у которого спросили свойство.func squared()— новый метод: число умножает само себя.n.isEvenиn.squared()вызываются так, будто они были вIntс самого начала. Даже у числа4, записанного прямо в коде, теперь естьisEven.
Здесь картинка с курсами неточна. Курсы проходит один сотрудник, а расширение учит весь тип сразу. Новое умение получает каждое число программы, в том числе созданное выше строки с extension.
Расширение своего типа
Свои типы расширяют так же. Часто так делят код: в объявлении типа — его данные, а в расширении — то, что тип умеет с ними делать.
struct Square {
let side: Double
}
extension Square {
var perimeter: Double { side * 4 }
}
print(Square(side: 3).perimeter) // 12.0Внутри расширения видны свойства из объявления: perimeter считает по side, как обычное вычисляемое свойство.
Соответствие протоколу в расширении
Значок тоже можно получить на курсах. Если тип объявлен без протокола, соответствие добавляют расширением: после имени типа пишут двоеточие и протокол, а внутри выполняют его требования.
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.0Square объявили без : Shape, а теперь квадрат соответствует протоколу и попадает в массив [any Shape]. Так же своему протоколу может соответствовать и чужой тип: extension Int: Shape с методом area() внутри не меняет объявление Int, но выдаёт ему значок.
Что можно добавить
Этот вопрос часто задают на собеседовании: что можно добавить расширением, а что нельзя? Из того, что вы уже знаете, расширение может добавить:
- вычисляемые свойства;
- методы;
- свойства и методы типа:
static letиstatic func; - структуре — свой
init, и почленный инициализатор при этом не пропадает; - соответствие протоколу.
А вот обычное хранимое свойство, которое у каждого экземпляра своё, расширением не добавить. Это частая ошибка с расширениями, и её разберём отдельно.
Частая ошибка
Расширением попробовали добавить квадрату цвет:
extension Square {
var color = "red" // ✗
}error: extensions must not contain stored propertiesПрограмма не компилируется: расширение не может добавить хранимое свойство экземпляра. Из таких свойств складывается само значение типа, и их список задаёт объявление. Например, значение Int на компьютерах и смартфонах занимает 64 бита, и весь код, который работает с числами, на это рассчитывает. А свойство типа static let расширение добавить может: оно одно на весь тип и в значения не входит.
Расширение добавляет умения, но не меняет, из чего сделано значение: курсы учат новому делу, но рост сотрудника не меняют. Исправление — добавить var color = "red" в объявление Square, рядом с side.
Главное
- Расширение добавляет умения уже объявленному типу, даже чужому, как
Int, и их получают все значения этого типа. - Расширение может добавить вычисляемые свойства, методы, свойства и методы типа,
initструктуры и соответствие протоколу. - Обычное хранимое свойство расширение добавить не может: из таких свойств складывается значение, и их задаёт только объявление типа.