is
и as
, как это описано в {Приведении типов} для проверки на соответствие протоколу и для приведения к конкретному протоколу. Проверка на и приведение к проколу следует точно такому же синтаксису, как и приведение к типу:
HasArea
с единственным требованием свойства для получаемого свойства Double
с названием area
:
protocol HasArea {
var area: Double { get }
}
Circle
и Country
, оба соответствуют протоколу HasArea
:
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
Класс Circle
реализует требование свойства area
в качестве вычисляемого свойства, основываясь на хранимом свойстве radius
. Класс Country
реализует требование area
напрямую как хранимое свойство. Оба класса корректно соответствуют протоколу HasArea
.
Animal
, который не соответствует протоколу HasArea
:
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
Circle
, Country
и Animal
не имеют общего базового класса. Но все они являются классами, а значит объекты всех трёх типов могут быть использованы для инициализации массива, что хранит значения типа AnyObject
:
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
Массив objects
инициализирован литералом массива, содержащим объект типа Circle
с радиусом в 2 единицы, объект типа Country
, инициализаржванным площадью поверхности Великобритании в квадратным километрах и объектом типа Animal
с четырьмя ногами.
objects
теперь может быть проинтегрирован, а каждый объект в массиве может быть проверен, чтобы увидеть, соответствует ли он протоколу HasArea
:
for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
Когда объект в массиве соответствует протоколу HasArea
, опциональное значение возвращается оператором as?
и распакуется через опциональное связывание в константу с названием objectWithArea
. Константа objectWithArea
, как известно, имеет тип HasArea
, а значит свойство area
может быть получено и распечатано типобезопасным образом.
Circle
, Country
и Animal
. Однако, пока они хранятся в константе objectWithArea
, они имеют только тип HasArea
, а значит только их свойство area
может быть использовано.
optional
в качестве части определения протокола. Ввиду доступности опциональных требований Вы можете писать код, взаимодействующий с Objective-C. И протокол и опциональное требование должны быть промаркированы атрибутом @objc. Заметьте, что протоколы @objc могут быть адаптированы только класса, которые наследуют от классом Objective-C или других классов @objc. Они не могут быть адаптированы структурами или перечислениями.
(Int) -> String
становится методом типа ((Int) -> String)?
. Заметьте, что вся функция целиком оборачивается в опционал, а не только возвращаемое значение метода.
someOptionalMethod?(someArgument)
.
Counter
, который использует внешний источник данных для предоставления значения увеличения. Этот источник данных определяется протоколом CounterDataSource
, который имеет два опциональных требования:
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
Протокол CounterDataSource
определяет опциональное требование метода с названием increment(forCount:)
и опциональное требование свойства с названием fixedIncrement
. Эти требования определяют два различных способа для источников данных предоставить необходимое значение увеличения для объекта Counter
.
CounterDataSource
без всякой реализации требований протокола вообще. В конце концов, они оба опциональны. И хотя технически такое разрешено, это не сделает очень хороший источник данных.
Counter
, определённый ниже, имеет опциональное свойство dataSource
типа СounterDataSource?
:
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
Класс Counter
хранит своё текущее значение в переменном свойстве с названием count
. Класс Counter
также определяет метод с названием increment
, который увеличивает значение свойства count всякий раз при вызове метода.
increment()
сперва пытается получить значение инкремента через реализацию метод increment(forCount:)
. Метод increment()
испольузет опциональную цепочку для попыткы вызова increment(forCount:)
и передаёь текущее значение count
в качестве единственного аргумента метода.
dataSource
может быть nil
, так что dataSource
имеет знак вопроса после своего имени, что означает, что increment(forCount:)
следует быть вызванным, только если dataSource
не равен nil
. Во-вторых, даже если dataSource
существует, то нет гарантий, что он реализует increment(forCount:)
, так как это опциональное требование. Здесь возможность того, что increment(forCount:)
может быть не реализован так же орабаыватеся опциональной последовательностью. Вызов increment(forCount:)
произойдёт, только если increment(forCount:)
существует, что значит, что он не равен nil
. Поэтому increment(forCount:)
так же записывается с вопросительным знаком после своего имени.
increment(forCount:)
может провалиться по одной из этих двух причин, то вызов возвращает оптимальное значение Int
. Это верно, даже если increment(forCount:)
определён как возвращающий неопциональное Int
значение в определении CounterDataSourse
. И хотя здесь две операции опциональной цепочки, одна после другой, результат всё равно обёрнут в один опционал.
increment(forCount:)
опциональное значение Int
, которое он вернёт, будет распаковано в константу с названием amount
, используя опциональное связывание. Если опционал Int
содержит значение: что значит, что делегат и метода оба существуют, и метод возвращает значение, то распакованное значение amount
добавляет в хранимое свойства count
и увеличение завершается.
increment(forCount:)
метода, или потому что dataSource
равен nil
, или так как источник данных не реализует increment(forCount:)
, тогда метод increment()
пытается извлечь значение из свойства fixedIncrement
источника данных. Свойство fixedIncrement
также является опциональным требованием, так что его значение тоже является опциональным Int
, даже хотя fixedIncrement
определён как неопциональное свойство Int
в качестве части определения протокола CounterDataSource
.
CounterDataSource
, где источник данных возвращает константное значение 3 всякий раз при его запросе. Он делает это путём реализации опционального требования свойства fixedIncrement
:
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
ThreeSource
в качестве источника данных для нового экземпляра класса Counter
:
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
Код выше создаёт новый объект типа Counter
, задаёт его источник данных в новый объект типа ThreeSource
и вызывает метод счётчика increment()
четыре раза. Как и ожидалось, свойство count
счётчика увеличивает на три всякий раз при вызове incement()
.
TowardsZeroSource
, который заставляет объект Counter
считать к или от нуля на основе текущего значения count
:
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
Класс TowardsZeroSource
реализует опциональный метод increment(forCount:)
протокола CounterDataSource
и использует значение аргумента count
для выяснения того, в каком направлении осуществлять счёт. Если count
уже равен 0, то метод вернёт 0 для указания на то, что никакого дальнейшего отсчёта не требуется.
TowardsZeroSource
с существующим объектом Counter
для отсчёта от -4 до нуля. Как только счётчик достигнет нуля, никакого дальнейшего счёта происходить не будет:
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
// -3
// -2
// -1
// 0
// 0