Расширения в Swift: как их можно использовать?

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

Что такое расширение?

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

Давайте посмотрим на пример. Представьте, что другой разработчик определил класс в своем коде, и у вас нет к нему доступа.

class Airplane {
    var altitude: Double = 0
 
    func setAltitude(feet: Double) {
        altitude = feet
    }
}

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

extension Airplane {
    func setAltitude(meter: Double) {
        altitude = meter * 3.28084
    }
}

Теперь вы можете использовать функцию setAltitude(meter:) в экземпляре класса Airplane как обычную функцию:

let boeing = Airplane()
boeing.setAltitude(meter: 12000)
print(boeing.altitude) 
// 39370.08

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

Вот что могут делать расширения в Swift:

  • Добавлять функции и вычисляемые свойства.
  • Добавлять статистические константы.
  • Создавать новые инициализаторы.
  • Определять сабскрипты с помощью функции subscript().
  • Определять новые вложенные типы.
  • Добавлять соответствие протоколу.
  • Добавить реализацию по умолчанию с помощью расширений протокола.

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

Продолжая предыдущий пример, вы не можете написать:

extension Airplane {
    var speed: Int = 0
}

Добавление нового свойства в класс коренным образом изменило бы структуру этого класса. И у нас уже есть другой отличный способ изменить структуру класса — использовать подклассы.

Давайте посмотрим на практические варианты использования расширений:

  • Мы можем разделять и группировать наш код.
  • Добавлять соответствие протоколу.
  • Добавлять вычисляемые свойства экземпляра или вычисляемые свойства типа.
  • Добавлять расширения протокола.

Разделение и группировка кода

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

class Airplane {
    var speed: Double = 0
    var altitude: Double = 0
    var bearing: Double = 0
}

Затем мы создаем различные расширения. Во-первых, для полета на самолете:

extension Airplane {
    func changeAltitude(altitude: Double) {
        self.altitude = altitude
    }
 
    func changeBearing(degrees: Double) {
        self.bearing = degrees
    }
}

Затем мы определяем функции для взлета и посадки:

extension Airplane {
    func takeOff() {
    }
 
    func land() {
    }
}

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

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

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

Добавление соответствия протокола с помощью расширений

Вы можете использовать расширения для соответствия протоколу:

class DetailViewController: UIViewController {
}
 
extension DetailViewController: CLLocationManagerDelegate {
}

В приведенном выше коде с помощью расширения класс DetailViewController теперь соответствует протоколу CLLocationManagerDelegate. Затем вы можете продолжить добавление кода для этого делегата в расширение, отделяя его от базового класса.

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

Статистические константы

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

extension Notification.Name {
    static let statusUpdated = Notification.Name("status_updated")
}

Приведенный выше код расширяет тип Notification.Name статической константой statusUpdated. Эта константа класса доступна в любом месте типа, что позволяет использовать ее вместе с NotificationCenter.

NotificationCenter.default.post(name: .statusUpdated, object: nil)

Функция post(name:object:) принимает параметр Notification.Name. Мы определили константу statusUpdated для Notification.Name, поэтому Swift автоматически выводит ее тип, и вы можете использовать более короткий синтаксис — .statusUpdated.

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

Вы можете определить подтип в расширении:

extension UserDefaults {
    struct Keys {
        static let useSync  = "use_sync"
        static let lastSync = "last_sync"
    }
}

Теперь вы можете использовать его следующим образом:

UserDefaults.default.set(true, forKey: UserDefaults.Keys.useSync)

У этого подхода есть два преимущества:

  • Подтип Keys доступен только для типа UserDefaults. Это означает, что вы не будете иметь произвольную структуру, определенную на глобальном уровне в вашем коде.
  • Использование констант означает, что у вас будет меньше шансов сделать ошибку при вводе текста «use_sync» для ключа UserDefaults. Вы можете использовать эту концепцию везде, где можно использовать статические ключи.

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

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

К примеру, до версии Swift 4.2 у массивов не было функции shuffled() для рандомизации. Поэтому мы могли добавить эту вспомогательную функцию непосредственно к типу Array:

extension Array {
    func shuffled() {
    }
}

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

class Circle {
    var radius: Double = 0
}
 
extension Circle {
    var circumference: Double {
        return radius * .pi * 2
    }
}

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

let circle = Circle()
circle.radius = 10
print(circle.circumference) 
// 62.83185307179586

Расширения протоколов

В отличие от соответствия протоколу, расширения протокола позволяют напрямую предоставлять реализацию протоколов по умолчанию.

Рассмотрим простой протокол:

protocol Edible {
    func eat()
}

Протокол Edible определяет функцию eat(), так что любой класс, который хочет соответствовать протоколу Edible, должен реализовать функцию eat():

class Fish: Edible  {
    func eat() {
        print("Я ем рыбу.")
    }
}

С расширениями протокола мы можете расширить протокол, включив в него реализацию по умолчанию:

extension Edible {
    func eat() {
        print("Я ем.")
    }
}

С помощью этого расширения протокола любой класс, который принимает протокол Edible, может использовать реализацию функции eat() по умолчанию:

class Apple: Edible {
}
 
let apple = Apple()
apple.eat()
// Я ем.
Читайте также:
Добавить комментарий

Ваш адрес email не будет опубликован.