Класс может наследовать методы, свойства и другие характеристики от другого класса. Когда один класс наследует от другого, наследующий класс называется подклассом , а класс, от которого он наследуется, называется его суперклассом . Наследование - это фундаментальное поведение, которое отличает классы от других типов в Swift.

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

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

Определение базового класса

Любой класс, который не наследуется от другого класса, называется базовым классом.

ЗАМЕТКА

Классы Swift не наследуются от универсального базового класса. Классы, которые вы определяете без указания суперкласса, автоматически становятся базовыми для вас.

В приведенном ниже примере определяется базовый класс с именем Vehicle. Этот базовый класс определяет хранимое свойство с именем currentSpeedпо умолчанию 0.0(выводит тип свойства Double). Значение currentSpeedсвойства используется вычисляемым Stringсвойством только для чтения, вызываемым descriptionдля создания описания транспортного средства.

VehicleБазовый класс также определяет метод , называемый makeNoise. Этот метод на самом деле ничего не делает для базового Vehicleэкземпляра, но будет настроен подклассами Vehicleпозже:

class Vehicle {

var currentSpeed = 0.0

var description: String {

return "traveling at \(currentSpeed) miles per hour"

}

func makeNoise() {

// do nothing - an arbitrary vehicle doesn't necessarily make a noise

}

}

Вы создаете новый экземпляр Vehicleс синтаксисом инициализатора , который записывается как имя типа, за которым следуют пустые скобки:

let someVehicle = Vehicle()

Создав новый Vehicleэкземпляр, вы можете получить доступ к его descriptionсвойству, чтобы распечатать понятное человеку описание текущей скорости автомобиля:

print("Vehicle: \(someVehicle.description)")

// Vehicle: traveling at 0.0 miles per hour

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

Наследование

Подклассы - это основание нового класса на существующем классе. Подкласс наследует характеристики от существующего класса, который затем можно уточнить. Вы также можете добавить новые характеристики в подкласс.

Чтобы указать, что у подкласса есть суперкласс, запишите имя подкласса перед именем суперкласса, разделив их двоеточием:

class SomeSubclass: SomeSuperclass {

// subclass definition goes here

}

В следующем примере определяется подкласс с именем Bicycleсуперкласс Vehicle:

class Bicycle: Vehicle {

var hasBasket = false

}

Новый Bicycleкласс автоматически получает все характеристики Vehicle, такие как его currentSpeedи descriptionсвойства и его makeNoise()метод.

В дополнение к признакам, которые он наследует, Bicycleкласс определяет новое сохраненное свойство hasBasketсо значением по умолчанию false(выводя тип Boolдля свойства).

По умолчанию любой новый Bicycleэкземпляр, который вы создаете, не будет иметь корзины. Вы можете установить hasBasketсвойство trueдля конкретного Bicycleэкземпляра после его создания:

let bicycle = Bicycle()

bicycle.hasBasket = true

Вы также можете изменить унаследованное currentSpeedсвойство Bicycleэкземпляра и запросить унаследованное descriptionсвойство экземпляра :

bicycle.currentSpeed = 15.0

print("Bicycle: \(bicycle.description)")

// Bicycle: traveling at 15.0 miles per hour

Подклассы могут быть сами подклассами. В следующем примере создается подкласс Bicycleдля двухместного велосипеда, известного как «тандем»:

class Tandem: Bicycle {

var currentNumberOfPassengers = 0

}

Tandemнаследует все свойства и методы от Bicycle, что, в свою очередь, наследует все свойства и методы от VehicleTandemПодкласс также добавляет новое хранимое свойство currentNumberOfPassengers, со значением по умолчанию 0.

Если вы создаете экземпляр Tandem, вы можете работать с любым из его новых и унаследованных свойств и запрашивать descriptionсвойство « только для чтения», от которого оно наследует Vehicle:

let tandem = Tandem()

tandem.hasBasket = true

tandem.currentNumberOfPassengers = 2

tandem.currentSpeed = 22.0

print("Tandem: \(tandem.description)")

// Tandem: traveling at 22.0 miles per hour

Переопределение

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

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

overrideКлючевое слово также побуждает Swift компилятор , чтобы проверить , что суперкласс вашего Переопределение класса (или один из его родителей) имеет декларацию , которая соответствует одному вы предоставили для переопределения. Эта проверка гарантирует, что ваше главное определение верно.

Доступ к методам, свойствам и подпискам суперкласса

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

Где это уместно, вы получаете доступ к суперклассовой версии метода, свойства или индекса, используя superпрефикс:

  • Переопределенный именованный метод someMethod()может вызывать версию суперкласса someMethod()путем вызова super.someMethod()внутри реализации переопределенного метода.
  • Перезаписанное свойство называется somePropertyможет получить доступ к версии суперкласса somePropertyкак в super.somePropertyрамках реализации переопределения getter или setter.
  • Переопределенный индекс для someIndexдоступа может получить доступ к версии суперкласса того же индекса, что и super[someIndex]из реализации переопределенного индекса.

Переопределяющие методы

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

Следующий пример определяет новый подкласс Vehicleпод названием Train, который переопределяет makeNoise()метод , который Trainнаследует от Vehicle:

class Train: Vehicle {

override func makeNoise() {

print("Choo Choo")

}

}

Если вы создадите новый экземпляр класса Trainи вызовете его makeNoise()метод, вы увидите, что Trainвызывается версия метода подкласса:

let train = Train()

train.makeNoise()

// Prints "Choo Choo"

Переопределение свойств

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

Переопределение методов получения и установки свойств

Вы можете предоставить собственный метод получения (и, при необходимости, установку) для переопределения любого унаследованного свойства, независимо от того, реализовано ли унаследованное свойство как хранимое или вычисляемое свойство в источнике. Хранимая или вычисленная природа унаследованного свойства не известна подклассу - он только знает, что унаследованное свойство имеет определенное имя и тип. Вы всегда должны указывать как имя, так и тип свойства, которое вы переопределяете, чтобы компилятор мог проверить, соответствует ли ваше переопределение свойству суперкласса с тем же именем и типом.

Вы можете представить унаследованное свойство только для чтения как свойство для чтения и записи, указав в переопределении свойства подкласса как метод получения, так и метод установки. Однако вы не можете представить унаследованное свойство чтения-записи как свойство только для чтения.

ЗАМЕТКА

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

В следующем примере определяется новый класс с именем Car, который является подклассом VehicleCarКласс вводит новое хранимое свойство gear, с целым значением по умолчанию 1CarКласс также переопределяет descriptionсвойство наследуемого от того Vehicle, чтобы обеспечить пользовательское описание , которое включает в себя текущий механизм:

class Car: Vehicle {

var gear = 1

override var description: String {

return super.description + " in gear \(gear)"

}

}

Переопределение descriptionсвойства начинается с вызова super.description, который возвращает свойство Vehicleкласса description. Затем Carверсия класса descriptionдобавляет дополнительный текст в конец этого описания, чтобы предоставить информацию о текущем снаряжении.

Если вы создадите экземпляр Carкласса и установите его gearи currentSpeedсвойства, вы увидите, что его descriptionсвойство возвращает адаптированное описание, определенное в Carклассе:

let car = Car()

car.currentSpeed = 25.0

car.gear = 3

print("Car: \(car.description)")

// Car: traveling at 25.0 miles per hour in gear 3

Переопределение наблюдателей за недвижимостью

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

ЗАМЕТКА

Вы не можете добавлять наблюдатели свойств в унаследованные постоянные сохраненные свойства или унаследованные вычисляемые свойства только для чтения. Значение этих свойств не может быть установлено, и поэтому нецелесообразно предоставлять willSetили didSetреализацию как часть переопределения.

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

В следующем примере определяется новый класс с именем AutomaticCar, который является подклассом CarAutomaticCarКласс представляет собой автомобиль с автоматической коробкой передач, которая автоматически выбирает соответствующий механизм для использования на основе текущей скорости:

class AutomaticCar: Car {

override var currentSpeed: Double {

didSet {

gear = Int(currentSpeed / 10.0) + 1

}

}

}

Всякий раз, когда вы устанавливаете currentSpeedсвойство AutomaticCarэкземпляра, didSetнаблюдатель свойства устанавливает для gearсвойства экземпляра соответствующий выбор передач для новой скорости. В частности, наблюдатель свойства выбирает передачу, представляющую собой новое currentSpeedзначение, деленное на 10, округленное до ближайшего целого числа плюс 1. Скорость 35.0производит шестерню 4:

let automatic = AutomaticCar()

automatic.currentSpeed = 35.0

print("AutomaticCar: \(automatic.description)")

// AutomaticCar: traveling at 35.0 miles per hour in gear 4

Предотвращение переопределений

Вы можете предотвратить переопределение метода, свойства или индекса, пометив его как final . Делайте это, написав finalмодификатор перед тем интродьюсером по ключевому слову метод, свойства или подстрочного (например, если , , и ).final varfinal funcfinal class funcfinal subscript

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

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