Документация

Расширения

Расширения

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

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

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

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

Заметка

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

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

Расширение объявляется с помощью ключевого слова extension:

extension SomeType {
    // описываем новую функциональность для типа SomeType
}

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

extension SomeType: SomeProtocol, AnotherProtocol {
    // реализация требования протокола тут
}

Описание добавления соответствия протоколу таким образом описано в разделе Добавление реализации протокола через расширение.

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

Заметка

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

Вычисляемые свойства в расширениях

Расширения могут добавлять вычисляемые свойства экземпляра и вычисляемые свойства типа к существующим типам. В примере мы добавляем пять вычисляемых свойств экземпляра во встроенный тип 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("Один дюйм - это \(oneInch) метра")
// Выведет "Один дюйм- это 0.0254 метра"
let threeFeet = 3.ft
print("Три фута - это \(threeFeet) метра")
// Выведет "Три фута - это 0.914399970739201 метра"

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

В этом примере, значение 1.0 типа Double отображает “один метр”. Это причина, по которой m возвращает self, что равно 1.m, то есть посчитать Double от числа 1.0.

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

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

let aMarathon = 42.km + 195.m
print("Марафон имеет длину \(aMarathon) метров")
// Выведет "Марафон имеет длину 42195.0 метров"

Заметка

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

Инициализаторы в расширениях

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

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

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

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

Пример ниже определяет структуру 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 (2.5, 2.5) и его размер (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.

Пример ниже добавляет новый изменяющийся (mutating) метод square для типа Int, который возводит в квадрат исходное значение:

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// теперь переменная someInt имеет значение 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]
// возвращает 5
746381295[1]
// возвращает 9
746381295[2]
// возвращает 2
746381295[8]
// возвращает 7

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

746381295[9]
// возвращает 0, как если бы вы запросили вот так:
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 описывает значение, которое отображает данное целое число. В частности оно определяет является ли число положительным, отрицательным или нулем.

Так же этот пример добавляет новое вычисляемое свойство kind к типу Int, которое возвращает соответствующий член перечисления 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])
// Выведет "+ + - 0 - 0 + "

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

Заметка

Как нам уже известно что number.kind имеет тип Int.Kind. Значит все значения членов Int.Kind могут быть записаны в короткой форме внутри конструкции switch, как .negative, а не Int.Kind.negative.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: