Расширения добавляют новые функциональные возможности к существующему классу, структуре, перечислению или типу протокола. Это включает в себя возможность расширять типы, для которых у вас нет доступа к исходному исходному коду (известный как ретроактивное моделирование ). Расширения аналогичны категориям в Objective-C. (В отличие от категорий Objective C, расширения Swift не имеют имен.)

Расширения в Swift могут:

  • Добавить свойства вычисляемого экземпляра и свойства вычисляемого типа
  • Определите методы экземпляра и методы типа
  • Предоставить новые инициализаторы
  • Определить подписки
  • Определите и используйте новые вложенные типы
  • Привести существующий тип в соответствие с протоколом

В Swift вы можете даже расширить протокол, чтобы обеспечить реализацию его требований или добавить дополнительные функциональные возможности, которыми могут воспользоваться соответствующие типы. 

ЗАМЕТКА

Расширения могут добавлять новые функциональные возможности к типу, но они не могут переопределять существующие функциональные возможности.

Синтаксис расширения

Объявите расширения с extensionключевым словом:

extension SomeType {

// new functionality to add to SomeType goes here

}

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

extension SomeType: SomeProtocol, AnotherProtocol {

// implementation of protocol requirements goes here

}

Расширение может использоваться для расширения существующего универсального типа. Вы также можете расширить универсальный тип для условного добавления функциональности.

ЗАМЕТКА

Если вы определяете расширение для добавления новой функциональности к существующему типу, новая функциональность будет доступна во всех существующих экземплярах этого типа, даже если они были созданы до того, как было определено расширение.

Вычисленные свойства

Расширения могут добавлять свойства вычисляемых экземпляров и свойства вычисляемых типов к существующим типам. В этом примере добавляется пять свойств вычисляемого экземпляра к встроенному Doubleтипу Swift , чтобы обеспечить базовую поддержку работы с единицами расстояния:

extension Double {

var km: Double { return self * 1_000.0 }

var m: Double { return self }

var cm: Double { return self / 100.0 }

var mm: Double { return self / 1_000.0 }

var ft: Double { return self / 3.28084 }

}

let oneInch = 25.4.mm

print("One inch is \(oneInch) meters")

// Prints "One inch is 0.0254 meters"

let threeFeet = 3.ft

print("Three feet is \(threeFeet) meters")

// Prints "Three feet is 0.914399970739201 meters"

Эти вычисленные свойства выражают, что Doubleзначение следует рассматривать как определенную единицу длины. Хотя они реализованы как вычисляемые свойства, имена этих свойств могут быть добавлены к литеральному значению с плавающей точкой с синтаксисом точки, чтобы использовать это литеральное значение для выполнения преобразований расстояния.

В этом примере Doubleзначение 1.0считается «представляют собой один метр». Вот почему mвычисляемое свойство возвращает self1.mсчитается, что выражение вычисляет Doubleзначение 1.0.

Другие единицы требуют, чтобы некоторая конверсия была выражена как значение, измеренное в метрах. Один километр равен 1000 метров, поэтому kmвычисленное свойство умножает значение на, 1_000.00чтобы преобразовать его в число, выраженное в метрах. Точно так же в метре есть 3,28084 фута, и поэтому ftвычисленное свойство делит базовое Doubleзначение на 3.28084, чтобы преобразовать его из футов в метры.

Эти свойства являются вычисляемыми свойствами только для чтения, поэтому getдля краткости они выражаются без ключевого слова. Их возвращаемое значение имеет тип Doubleи может использоваться в математических вычислениях везде, где Doubleпринимается:

let aMarathon = 42.km + 195.m

print("A marathon is \(aMarathon) meters long")

// Prints "A marathon is 42195.0 meters long"

ЗАМЕТКА

Расширения могут добавлять новые вычисляемые свойства, но они не могут добавлять сохраненные свойства или добавлять наблюдатели свойств к существующим свойствам.

Инициализаторы

Расширения могут добавлять новые инициализаторы к существующим типам. Это позволяет вам расширять другие типы, чтобы принимать ваши собственные пользовательские типы в качестве параметров инициализатора, или предоставлять дополнительные параметры инициализации, которые не были включены как часть первоначальной реализации типа.

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

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

Если вы используете расширение для добавления инициализатора к структуре, которая была объявлена ​​в другом модуле, новый инициализатор не сможет получить доступ, selfпока не вызовет инициализатор из определяющего модуля.

В приведенном ниже примере определяется пользовательская Rectструктура для представления геометрического прямоугольника. В этом примере также определены две вспомогательные структуры, которые называются Sizeи Point, обе из которых предоставляют значения по умолчанию 0.0для всех своих свойств:

struct Size {

var width = 0.0, height = 0.0

}

struct Point {

var x = 0.0, y = 0.0

}

struct Rect {

var origin = Point()

var size = Size()

}

Поскольку Rectструктура предоставляет значения по умолчанию для всех своих свойств, она автоматически получает инициализатор по умолчанию и инициализатор по элементам. Эти инициализаторы могут быть использованы для создания новых Rectэкземпляров:

let defaultRect = Rect()

let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),

size: Size(width: 5.0, height: 5.0))

Вы можете расширить Rectструктуру, чтобы обеспечить дополнительный инициализатор, который принимает определенную центральную точку и размер:

extension Rect {

init(center: Point, size: Size) {

let originX = center.x - (size.width / 2)

let originY = center.y - (size.height / 2)

self.init(origin: Point(x: originX, y: originY), size: size)

}

}

Этот новый инициализатор начинает с вычисления подходящей исходной точки на основе предоставленной centerточки и sizeзначения. Затем инициализатор вызывает автоматический элементный инициализатор структуры init(origin:size:), который сохраняет новые значения источника и размера в соответствующих свойствах:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),

size: Size(width: 3.0, height: 3.0))

// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

ЗАМЕТКА

Если вы предоставляете новый инициализатор с расширением, вы по-прежнему отвечаете за то, чтобы каждый экземпляр полностью инициализировался после завершения инициализатора.

Методы

Расширения могут добавлять новые методы экземпляров и методы типов к существующим типам. В следующем примере добавляется новый метод экземпляра, вызываемый repetitionsдля Intтипа:

extension Int {

func repetitions(task: () -> Void) {

for _ in 0..<self {

task()

}

}

}

repetitions(task:)Метод принимает один аргумент типа , который указывает функцию , которая не имеет параметров и не возвращает значение.() -> Void

После определения этого расширения вы можете вызвать repetitions(task:)метод для любого целого числа, чтобы выполнить задачу много раз:

3.repetitions {

print("Hello!")

}

// Hello!

// Hello!

// Hello!

Методы мутации экземпляров

Методы экземпляра добавляют с расширением можно также модифицировать (или мутировать ) сам экземпляр. Методы структуры и перечисления, которые модифицируют, selfили его свойства должны помечать метод экземпляра как mutating, точно так же как мутирующие методы из оригинальной реализации.

В приведенном ниже примере добавлен новый метод мутации, вызываемый squareдля Intтипа Swift , который возводит в квадрат исходное значение:

extension Int {

mutating func square() {

self = self * self

}

}

var someInt = 3

someInt.square()

// someInt is now 9

Нижние индексы

Расширения могут добавлять новые подписки к существующему типу. В этом примере добавляется целочисленный индекс к встроенному Intтипу Swift . Этот нижний индекс [n]возвращает десятичные цифры nсправа от номера:

  • 123456789[0] возвращается 9
  • 123456789[1] возвращается 8

…и так далее:

extension Int {

subscript(digitIndex: Int) -> Int {

var decimalBase = 1

for _ in 0..<digitIndex {

decimalBase *= 10

}

return (self / decimalBase) % 10

}

}

746381295[0]

// returns 5

746381295[1]

// returns 9

746381295[2]

// returns 2

746381295[8]

// returns 7

Если у Intзначения недостаточно цифр для запрошенного индекса, реализация индекса возвращается 0, как если бы число было дополнено нулями слева:

746381295[9]

// returns 0, as if you had requested:

0746381295[9]

Вложенные типы

Расширения могут добавлять новые вложенные типы к существующим классам, структурам и перечислениям:

extension Int {

enum Kind {

case negative, zero, positive

}

var kind: Kind {

switch self {

case 0:

return .zero

case let x where x > 0:

return .positive

default:

return .negative

}

}

}

Этот пример добавляет новое вложенное перечисление в Int. Это перечисление, называемое Kind, выражает вид числа, которое представляет конкретное целое число. В частности, он выражает, является ли число отрицательным, нулевым или положительным.

В этом примере также добавляется новое свойство вычисляемого экземпляра для объекта Intnamed kind, который возвращает соответствующий Kindрегистр перечисления для этого целого числа.

Вложенное перечисление теперь можно использовать с любым Intзначением:

func printIntegerKinds(_ numbers: [Int]) {

for number in numbers {

switch number.kind {

case .negative:

print("- ", terminator: "")

case .zero:

print("0 ", terminator: "")

case .positive:

print("+ ", terminator: "")

}

}

print("")

}

printIntegerKinds([3, 19, -27, 0, -6, 0, 7])

// Prints "+ + - 0 - 0 + "

Эта функция printIntegerKinds(_:)принимает входной массив Intзначений и перебирает эти значения по очереди. Для каждого целого числа в массиве функция рассматривает kindвычисленное свойство для этого целого числа и печатает соответствующее описание.

ЗАМЕТКА

number.kindуже известно, чтобы иметь тип Int.Kind. Из-за этого все Int.Kindзначения регистра могут быть записаны в сокращенной форме внутри switchоператора, например, .negativeвместо Int.Kind.negative.