Расширения позволяют добавить новую функциональность к существующему объектному типу (классу, структуре или перечислению), а также к протоколу. Более того, вы можете расширять типы данных, доступа к исходным кодам которых у вас нет (например, типы, предоставляемые фреймворками, или фундаментальные для Swift типы данных).
Перечислим возможности расширений:
• добавление вычисляемых свойств экземпляра и вычисляемых свойств типа (static);
• определение методов экземпляра и методов типа;
• определение новых инициализаторов, сабскриптов и вложенных типов;
• обеспечение соответствия существующего типа протоколу.
Расширения могут добавлять новый функционал к типу, но не могут изменять существующий. Суть расширения состоит исключительно в наращивании возможностей, но не в их изменении.
Синтаксис
extension ИмяРасширяемогоТипа {
// описание новой функциональности для расширяемого типа
}
Для объявления расширения используется ключевое слово extension, после которого указывается имя расширяемого типа данных. Именно к указанному типу применяются все описанные в теле расширения возможности.
Новая функциональность, добавляемая расширением, становится доступной всем экземплярам расширяемого объектного типа вне зависимости от того, где эти экземпляры объявлены.
Расширения могут добавлять вычисляемые свойства экземпляра и вычисляемые свойства типа в определенный тип данных. Рассмотрим пример расширения функционала типа Double, создав в нем ряд новых вычисляемых свойств и обеспечив тип Double возможностью работы с единицами длины (листинг 32.1).
Листинг 32.1
extension Double {
var km: Double { return self * 1000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("Один фут — это \(oneInch) метра")
// выводит "Один фут — это 0.0254 метра"
let threeFeet = 3.ft
print("Три фута — это \(threeFeet) метра")
// выводит "Три фута — это 0.914399970739201 метра"
Созданные вычисляемые свойства позволяют использовать дробные числа как конкретные единицы измерения длины. Добавленные новые свойства могут применяться для параметров и литералов типа Double.
В данном примере подразумевается, что значение 1.0 типа Double отражает величину один метр. Именно поэтому свойство m возвращает значение self.
Другие свойства требуют некоторых преобразований перед возвращением значений. Один километр — это то же самое, что 1000 метров, поэтому при запросе свойства km возвращается результат выражения self * 1000.
Чтобы после определения новых вычисляемых свойств использовать всю их мощь, требуется создавать и геттеры, и сеттеры.
ПРИМЕЧАНИЕ Расширения могут добавлять только новые вычисляемые свойства. При попытке добавить хранимые свойства или наблюдателей свойств происходит ошибка.
Расширения могут добавлять инициализаторы к существующему типу. Таким образом, вы можете расширить существующие типы, например, для обработки экземпляров ваших собственных типов в качестве входных аргументов.
ПРИМЕЧАНИЕ Для классов расширения могут добавлять только новые вспомогательные инициализаторы. Попытка добавить назначенный инициализатор или деинициализатор ведет к ошибке.
В качестве примера напишем инициализатор для типа Double (листинг 32.2). В этом примере создается структура, описывающая линию на плоскости. Необходимо реализовать инициализатор, принимающий в качестве входного аргумента экземпляр линии и устанавливающий значение, соответствующее длине линии.
Листинг 32.2
import Foundation
// сущность "линия"
struct Line{
var pointOne: (Double, Double)
var pointTwo: (Double, Double)
}
// расширения для Double
extension Double {
init(line: Line){
self = sqrt(pow((line.pointTwo.0 - line.pointOne.0),2) +
pow((line.pointTwo.1 - line.pointOne.1),2))
}
}
var myLine = Line(pointOne: (10,10), pointTwo: (14,10))
var lineLength = Double(line: myLine) // 4
Библиотека Foundation обеспечивает доступ к математическим функциям sqrt(_:) и pow(_:_:) (соответственно квадратный корень и возведение в степень), которые требуются для вычисления длины линии на плоскости.
Структура Line описывает сущность «линия», в свойствах которой указываются координаты точек ее начала и конца. Созданный в расширении инициализатор принимает на входе экземпляр класса Line и на основе значений его свойств вычисляет требуемое значение.
При разработке нового инициализатора в расширении будьте крайне внимательны к тому, чтобы к завершению инициализации каждое из свойств имело определенное значение.
Следующей рассматриваемой функцией расширений является создание новых методов в расширяемом типе данных. Рассмотрим пример (листинг 32.3). В этом примере путем расширения типа Int мы добавляем метод repetitions, принимающий на входе замыкание типа () -> (). Данный метод предназначен для того, чтобы выполнять переданное замыкание столько раз, сколько указывает собственное значение целого числа.
Листинг 32.3
extension Int {
func repetitions(task: () -> ()) {
for _ in 0..<self {
task()
}
}
}
3.repetitions{
print("Swift")
}
Консоль
Swift
Swift
Swift
Для изменения свойств перечислений и структур реализуемыми расширением методами необходимо не забывать использовать модификатор mutating. В следующем примере реализуется метод square(), который возводит в квадрат собственное значение экземпляра. Так как тип Int является структурой, то для изменения собственного значения экземпляра необходимо использовать ключевое слово mutating (листинг 32.4).
Листинг 32.4
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square() // 9
Помимо свойств, методов и инициализаторов, расширения позволяют создавать новые сабскрипты.
Создаваемое в листинге 32.5 расширение типа Int реализует новый сабскрипт, который позволяет получить определенную цифру собственного значения экземпляра. В сабскрипте указывается номер позиции числа, которое необходимо вернуть.
Листинг 32.5
extension Int {
subscript( digitIndex: Int ) -> Int {
var base = 1
var index = digitIndex
while index > 0 {
base *= 10
index -= 1
}
return (self / base) % 10
}
}
746381295[0] // 5
746381295[1] // 9
Если у числа отсутствует цифра с запрошенным индексом, возвращается 0, что не нарушает логику работы.