Протоколы
Протокол определяет проект методов, свойств и других требований, удовлетворяющих определенной задаче или части функциональности. Протокол фактически не обеспечивает реализацию ни для одного из этих требований — он только описывает то, на что будет похожа реализация. Протокол может тогда быть принят классом, структурой или перечислением для обеспечения фактической реализации тех требований. Любой тип, удовлетворяющий требования протокола, как говорят, соответствует тому протоколу.
Протоколы могут потребовать, чтобы соответствующие типы имели определенные свойства экземпляра, методы экземпляра, введите методы, операторов и нижние индексы.
Синтаксис протокола
Вы определяете протоколы очень похожим способом к классам, структурам и перечислениям:
protocol SomeProtocol {
// protocol definition goes here
}
Пользовательские типы утверждают, что принимают определенный протокол путем размещения имени протокола после имени типа, разделенного двоеточием, как часть их определения. Многократные протоколы могут быть перечислены и разделяются запятыми:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
Если класс имеет суперкласс, перечислите суперимя класса перед любыми протоколами, которые это принимает, сопровождаемый запятой:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
Требования свойства
Протокол может потребовать, чтобы любой тип приспосабливания обеспечил свойство экземпляра или свойство типа с определенным именем и типом. Протокол не указывает, должно ли свойство быть сохраненным свойством или вычисленным свойством — это только указывает требуемое имя свойства и тип. Протокол также указывает, должно ли каждое свойство быть доступным или доступным и устанавливаемым.
Если протокол требует, чтобы свойство было доступно и устанавливаемо, то требование свойства не может быть выполнено постоянным сохраненным свойством или вычисленным свойством только для чтения. Если протокол только требует, чтобы свойство было доступно, требование может быть удовлетворено любым видом свойства, и это допустимо для свойства, чтобы быть также устанавливаемым, если это полезно для Вашего собственного кода.
Требования свойства всегда объявляются как переменные свойства, снабженные префиксом var
ключевое слово. Доступные и устанавливаемые свойства обозначены путем записи { get set }
после того, как их описание типа и доступные свойства обозначены путем записи { get }
.
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
Всегда снабжайте префиксом требования свойства типа static
ключевое слово, когда Вы определяете их в протоколе. Это правило принадлежит даже при том, что требования свойства типа могут быть снабжены префиксом class
или static
ключевое слово, когда реализовано классом:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
Вот пример протокола с единственным требованием свойства экземпляра:
protocol FullyNamed {
var fullName: String { get }
}
FullyNamed
протокол требует, чтобы соответствующий тип обеспечил полностью определенное имя. Протокол не указывает ничто больше о природе соответствующего типа — это только указывает, что тип должен быть в состоянии обеспечить полное имя для себя. Состояния протокола, что любой FullyNamed
тип должен иметь доступное вызванное свойство экземпляра fullName
, который имеет тип String
.
Вот пример простой структуры, принимающей и соответствующей FullyNamed
протокол:
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
Этот пример определяет вызванную структуру Person
, который представляет определенное именованное лицо. Это утверждает, что принимает FullyNamed
протокол как часть первой строки ее определения.
Каждый экземпляр Person
имеет единственное сохраненное вызванное свойство fullName
, который имеет тип String
. Это соответствует единственное требование FullyNamed
протокол и средние значения это Person
правильно соответствовал протоколу. (Swift сообщает об ошибке во время компиляции, если не выполняется требование протокола.)
Вот более сложный класс, также принимающий и соответствующий FullyNamed
протокол:
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"
Этот класс реализует fullName
требование свойства как вычисленное свойство только для чтения для космического корабля. Каждый Starship
экземпляр класса хранит обязательное name
и дополнительное prefix
. fullName
свойство использует prefix
оцените, если это существует и предварительно ожидает его к началу name
создать полное имя для космического корабля.
Требования метода
Протоколы могут потребовать определенных методов экземпляра и ввести методы, которые будут реализованы путем приспосабливания типам. Эти методы записаны как часть определения протокола точно таким же образом что касается нормального экземпляра и вводят методы, но без изогнутых фигурных скобок или организации метода. Параметры Variadic позволяются согласно тем же правилам что касается нормальных методов. Значения по умолчанию, однако, не могут быть указаны для параметров метода в определении протокола.
Как с требованиями свойства типа, Вы всегда снабжаете префиксом требования метода типа static
ключевое слово, когда они определяются в протоколе. Это - истина даже при том, что требования метода типа снабжаются префиксом class
или static
ключевое слово, когда реализовано классом:
protocol SomeProtocol {
static func someTypeMethod()
}
Следующий пример определяет протокол с помощью единственного требования метода экземпляра:
protocol RandomNumberGenerator {
func random() -> Double
}
Этот протокол, RandomNumberGenerator
, требует, чтобы любой тип приспосабливания вызвал метод экземпляра random
, который возвращает a Double
оцените каждый раз, когда это вызывают. Несмотря на то, что это не указано как часть протокола, предполагается, что это значение будет числом от 0.0
до (но не включая) 1.0
.
RandomNumberGenerator
протокол не делает предположений о том, как каждое случайное число будет сгенерировано — это просто требует, чтобы генератор обеспечил стандартный способ генерировать новое случайное число.
Вот реализация класса, принимающего и соответствующего RandomNumberGenerator
протокол. Этот класс реализует алгоритм генератора псевдослучайного числа, известный как линейный congruential генератор:
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
println("Here's a random number: \(generator.random())")
// prints "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// prints "And another one: 0.729023776863283"
Видоизменение требований метода
Иногда необходимо для метода изменить (или видоизмениться) экземпляр, которому это принадлежит. Например, методы на типах значения (т.е. структуры и перечисления) Вы помещаете mutating
ключевое слово перед методом func
ключевое слово, чтобы указать, что методу позволяют изменить экземпляр, это принадлежит и/или любые свойства того экземпляра. Этот процесс описан в Изменении Типов Значения из Методов экземпляра.
Если Вы определяете требование метода экземпляра протокола, предназначающееся для видоизменения экземпляров любого типа, принимающего протокол, отметьте метод с mutating
ключевое слово как часть определения протокола. Это позволяет структурам и перечислениям принять протокол и удовлетворить то требование метода.
Пример ниже определяет вызванный протокол Togglable
, который определяет единственное вызванное требование метода экземпляра toggle
. Поскольку его имя предлагает, toggle()
метод предназначается, чтобы переключить или инвертировать состояние любого типа приспосабливания, обычно путем изменения свойства того типа.
toggle()
метод отмечен с mutating
ключевое слово как часть Togglable
определение протокола, чтобы указать, что метод, как ожидают, видоизменит состояние соответствующего экземпляра, когда это вызовут:
protocol Togglable {
mutating func toggle()
}
Если Вы реализуете Togglable
протокол для структуры или перечисления, той структуры или перечисления может соответствовать протоколу путем обеспечения реализации toggle()
метод, также отмеченный как mutating
.
Пример ниже определяет вызванное перечисление OnOffSwitch
. Это перечисление переключается между двумя состояниями, обозначенными случаями перечисления On
и Off
. Перечисление toggle
реализация отмечена как mutating
, соответствовать Togglable
требования протокола:
enum OnOffSwitch: Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
// lightSwitch is now equal to .On
Требования инициализатора
Протоколы могут потребовать, чтобы определенные инициализаторы были реализованы путем приспосабливания типам. Вы пишете эти инициализаторы как часть определения протокола точно таким же образом что касается нормальных инициализаторов, но без изогнутых фигурных скобок или организации инициализатора:
protocol SomeProtocol {
init(someParameter: Int)
}
Реализации класса требований инициализатора протокола
Можно реализовать требование инициализатора протокола к соответствующему классу или как определяемый инициализатор или как инициализатор удобства. В обоих случаях необходимо отметить реализацию инициализатора с required
модификатор:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
Использование required
модификатор гарантирует, чтобы Вы обеспечили явную или наследованную реализацию требования инициализатора ко всем подклассам соответствующего класса, такого, что они также соответствуют протоколу.
Для получения дополнительной информации о требуемых инициализаторах посмотрите Требуемые Инициализаторы.
Если подкласс переопределяет определяемый инициализатор от суперкласса, и также реализует соответствующее требование инициализатора из протокола, отметьте реализацию инициализатора с обоими required
и override
модификаторы:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
Требования инициализатора Failable
Протоколы могут определить failable требования инициализатора для приспосабливания типам, как определено в Инициализаторах Failable.
failable требование инициализатора может быть удовлетворено failable или nonfailable инициализатором на соответствующем типе. nonfailable требование инициализатора может быть удовлетворено nonfailable инициализатором или неявно развернутым failable инициализатором.
Протоколы как типы
Протоколы фактически не реализуют функциональности сами. Тем не менее, любой протокол, который Вы создаете, станет абсолютным типом для использования в Вашем коде.
Поскольку это - тип, можно использовать протокол во многих местах, где другие типы позволяются, включая:
Поскольку тип параметра или возврат вводят в функции, методе или инициализаторе
Как тип константы, переменной, или свойство
Как тип элементов в массиве, словаре или другом контейнере
Вот пример протокола, используемого в качестве типа:
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
Этот пример определяет новый вызванный класс Dice
, который представляет игру в кости n-sided для использования в настольной игре. Dice
экземплярам вызвали целочисленное свойство sides
, который представляет, сколько сторон они имеют, и вызванное свойство generator
, который обеспечивает генератор случайных чисел, от которого можно создать ценности броска костей.
generator
свойство имеет тип RandomNumberGenerator
. Поэтому можно установить его в экземпляр любого типа, принимающего RandomNumberGenerator
протокол. Ничто иное не требуется экземпляра, который Вы присваиваете этому свойству, за исключением того, что экземпляр должен принять RandomNumberGenerator
протокол.
Dice
также имеет инициализатор, для установки его начального состояния. Этому инициализатору вызвали параметр generator
, который имеет также тип RandomNumberGenerator
. Можно передать значение любого приспосабливания, вводят к этому параметру при инициализации нового Dice
экземпляр.
Dice
обеспечивает один метод экземпляра, roll
, который возвращает целочисленное значение между 1 и число сторон на игре в кости. Это вызовы метода генератор random()
метод для создания нового случайного числа между 0.0
и 1.0
, и использование это случайное число для создания ценности броска костей в корректном диапазоне. Поскольку generator
как известно, принимает RandomNumberGenerator
, это, как гарантируют, будет иметь a random()
метод для вызова.
Вот то, как Dice
класс может использоваться для создания шестисторонней игры в кости с a LinearCongruentialGenerator
экземпляр как его генератор случайных чисел:
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
println("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
Делегация
Делегация является шаблоном разработки, позволяющим классу или структуре передать (или делегат) часть своей ответственности перед экземпляром другого типа. Этот шаблон разработки реализован путем определения протокола, инкапсулирующего делегированную ответственность, такую, что соответствующий тип (известный как делегат), как гарантируют, обеспечит делегированную функциональность. Делегация может использоваться, чтобы реагировать на определенное действие или получить данные из внешнего источника, не будучи должен знать базовый тип того источника.
Пример ниже определяет два протокола для использования с основанными на игре в кости настольными играми:
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
DiceGame
протокол является протоколом, который может быть принят любой игрой, включающей игру в кости. DiceGameDelegate
протокол может быть принят любым типом для отслеживания прогресса a DiceGame
.
Вот версия игры Змей и Лестничных структур, первоначально представленной в Потоке управления. Эта версия адаптируется для использования a Dice
экземпляр для его бросков костей; принять DiceGame
протокол; и уведомлять a DiceGameDelegate
о его прогрессе:
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
Для описания геймплея Змей и Лестничных структур посмотрите раздел Break главы Потока управления.
Эта версия игры обернута как вызванный класс SnakesAndLadders
, который принимает DiceGame
протокол. Это обеспечивает доступное dice
свойство и a play()
метод для приспосабливания протоколу. ( dice
свойство объявляется как постоянное свойство, потому что оно не должно изменяться после инициализации, и протокол только требует, чтобы это было доступно.)
Установка игровой доски Змей и Лестничных структур имеет место в классе init()
инициализатор. Вся игровая логика перемещена в протокол play
метод, использующий протокол, потребовал dice
свойство для обеспечения его значений броска костей.
Обратите внимание на то, что delegate
свойство определяется как дополнительное DiceGameDelegate
, потому что делегат не требуется, чтобы играть в игру. Поскольку это имеет дополнительный тип, delegate
свойство автоматически установлено в начальное значение nil
. После того игра instantiator имеет опцию установить свойство в подходящего делегата.
DiceGameDelegate
обеспечивает три метода для отслеживания прогресса игры. Эти три метода были включены в игровую логику в play()
метод выше, и вызывают, когда новая игра запускается, новый поворот начинается, или игровые концы.
Поскольку delegate
свойство является дополнительным DiceGameDelegate
, play()
метод использует дополнительное объединение в цепочку каждый раз, когда это вызывает метод на делегате. Если delegate
свойство является нолем, эти вызовы делегата перестали работать корректно и без ошибки. Если delegate
свойство является ненолем, методы делегата вызывают и передают SnakesAndLadders
экземпляр в качестве параметра.
Этот следующий пример показывает вызванный класс DiceGameTracker
, который принимает DiceGameDelegate
протокол:
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
println("Started a new game of Snakes and Ladders")
}
println("The game is using a \(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
++numberOfTurns
println("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
println("The game lasted for \(numberOfTurns) turns")
}
}
DiceGameTracker
реализации все три метода, требуемые DiceGameDelegate
. Это использует эти методы для отслеживания число оборотов, которые приняла игра. Это сбрасывает a numberOfTurns
свойство для обнуления, когда игра запускается, постепенно увеличивает ее каждый раз, когда новый поворот начинается и распечатывает общее количество поворотов, как только закончилась игра.
Реализация gameDidStart
показанный выше использования game
параметр для печати некоторой вводной информации об игре, собирающейся играться. game
параметр имеет тип DiceGame
, нет SnakesAndLadders
, и так gameDidStart
может получить доступ и использовать только методы и свойства, реализованные как часть DiceGame
протокол. Однако метод все еще в состоянии использовать преобразование типа для запросов типа базового экземпляра. В этом примере это проверяет ли game
фактически экземпляр SnakesAndLadders
негласно, и печать надлежащее сообщение раз так.
gameDidStart
также получает доступ dice
свойство переданного game
параметр. Поскольку game
как известно, соответствует DiceGame
протокол, это, как гарантируют, будет иметь a dice
свойство, и таким образом, gameDidStart(_:)
метод в состоянии получить доступ и распечатать игру в кости sides
свойство, независимо от того, в какую игру играют.
Вот то, как DiceGameTracker
взгляды в действии:
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns
Добавление соответствия протокола с расширением
Даже если у Вас нет доступа к исходному коду для существующего типа, можно расширить существующий тип, чтобы принять и соответствовать новому протоколу. Расширения могут добавить новые свойства, методы и нижние индексы к существующему типу, и поэтому в состоянии добавить любые требования, чтобы протокол мог потребовать. Для больше о расширениях, посмотрите Расширения.
Например, этот протокол, вызванный TextRepresentable
, может быть реализован любым типом, имеющим путь, который будет представлен как текст. Это могло бы быть описанием себя или текстовой версией его текущего состояния:
protocol TextRepresentable {
func asText() -> String
}
Dice
класс от ранее может быть расширен, чтобы принять и соответствовать TextRepresentable
:
extension Dice: TextRepresentable {
func asText() -> String {
return "A \(sides)-sided dice"
}
}
Это расширение принимает новый протокол точно таким же образом как будто Dice
обеспечил его в его исходной реализации. Имя протокола предоставлено после имени типа, разделенного двоеточием, и реализация всех требований протокола предоставлена в изогнутых фигурных скобках расширения.
Любой Dice
экземпляр может теперь быть обработан как TextRepresentable
:
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
println(d12.asText())
// prints "A 12-sided dice"
Точно так же SnakesAndLadders
игровой класс может быть расширен, чтобы принять и соответствовать TextRepresentable
протокол:
extension SnakesAndLadders: TextRepresentable {
func asText() -> String {
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
}
println(game.asText())
// prints "A game of Snakes and Ladders with 25 squares"
Объявление принятия протокола с расширением
Если тип уже соответствует всем требованиям протокола, но еще не утвердил, что принимает тот протокол, можно заставить его принять протокол с пустым расширением:
struct Hamster {
var name: String
func asText() -> String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
Экземпляры Hamster
может теперь использоваться везде, где TextRepresentable
требуемый тип:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
println(somethingTextRepresentable.asText())
// prints "A hamster named Simon"
Наборы типов протокола
Протокол может использоваться в качестве типа, который будет сохранен в наборе, таком как массив или словарь, как упомянуто в Протоколах как Типы. Этот пример создает массив TextRepresentable
вещи:
let things: [TextRepresentable] = [game, d12, simonTheHamster]
Теперь возможно выполнить итерации по элементам в массиве и распечатать текстовое представление каждого элемента:
for thing in things {
println(thing.asText())
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
Обратите внимание на то, что thing
постоянный имеет тип TextRepresentable
. Это не имеет типа Dice
, или DiceGame
, или Hamster
, даже если фактический экземпляр негласно имеет один из тех типов. Тем не менее, потому что это имеет тип TextRepresentable
, и что-либо, что является TextRepresentable
как известно, имеет asText()
метод, безопасно вызвать thing.asText
каждый раз через цикл.
Наследование протокола
Протокол может наследовать один или несколько других протоколов и может добавить дальнейшие требования поверх требований, которые он наследовал. Синтаксис для наследования протокола подобен синтаксису для наследования классов, но с опцией перечислить многократные наследованные протоколы, разделенные запятыми:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
Вот пример протокола, наследовавшегося TextRepresentable
протоколируйте сверху:
protocol PrettyTextRepresentable: TextRepresentable {
func asPrettyText() -> String
}
Этот пример определяет новый протокол, PrettyTextRepresentable
, который наследовался от TextRepresentable
. Что-либо, что принимает PrettyTextRepresentable
должен удовлетворить все требования, осуществленные TextRepresentable
, плюс дополнительные требования, осуществленные PrettyTextRepresentable
. В этом примере, PrettyTextRepresentable
добавляет единственное требование для обеспечения вызванного метода экземпляра asPrettyText
это возвращает a String
.
SnakesAndLadders
класс может быть расширен, чтобы принять и соответствовать PrettyTextRepresentable
:
extension SnakesAndLadders: PrettyTextRepresentable {
func asPrettyText() -> String {
var output = asText() + ":\n"
for index in 1...finalSquare {
switch board[index] {
case let ladder where ladder > 0:
output += "▲ "
case let snake where snake < 0:
output += "▼ "
default:
output += "○ "
}
}
return output
}
}
Это расширение утверждает, что принимает PrettyTextRepresentable
протокол и обеспечивает реализацию asPrettyText()
метод для SnakesAndLadders
ввести. Что-либо, что является PrettyTextRepresentable
должен также быть TextRepresentable
, и так asPrettyText
реализация запускается путем вызова asText()
метод от TextRepresentable
протокол для начала выводимой строки. Это добавляет двоеточие и разрыв строки, и использует это в качестве запуска его симпатичного текстового представления. Это тогда выполняет итерации через массив квадратов платы и добавляет геометрическую фигуру для представления содержания каждого квадрата:
Если значение квадрата больше, чем
0
, это - основа лестничной структуры и представлено▲
.Если значение квадрата является меньше, чем
0
, это - голова змеи и представлено▼
.Иначе, значение квадрата
0
, и это - «свободный» квадрат, представленный○
.
Реализация метода может теперь использоваться для печати симпатичного текстового описания любого SnakesAndLadders
экземпляр:
println(game.asPrettyText())
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
Протоколы только для класса
Можно ограничить принятие протокола типами классов (и не структуры или перечисления) путем добавления class
ключевое слово к списку наследования протокола. class
ключевое слово должно всегда казаться первым в списке наследования протокола перед любыми наследованными протоколами:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// class-only protocol definition goes here
}
В примере выше, SomeClassOnlyProtocol
может только быть принят типами классов. Это - ошибка времени компиляции для записи структуры или определения перечисления, пытающегося принять SomeClassOnlyProtocol
.
Состав протокола
Может быть полезно потребовать, чтобы тип соответствовал многократным протоколам сразу. Можно объединить многократные протоколы в единственное требование с составом протокола. Составы протокола имеют форму protocol<SomeProtocol, AnotherProtocol>
. Можно перечислить как многие протоколы в паре угловых скобок (<>
) как Вам нужно, разделенный запятыми.
Вот пример, комбинирующий два вызванные протокола Named
и Aged
в требование состава отдельного протокола к параметру функции:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(birthdayPerson)
// prints "Happy birthday Malcolm - you're 21!"
Этот пример определяет вызванный протокол Named
, с единственным требованием для доступного String
свойство вызывают name
. Это также определяет вызванный протокол Aged
, с единственным требованием для доступного Int
свойство вызывают age
. Оба из этих протоколов приняты вызванной структурой Person
.
Пример также определяет вызванную функцию wishHappyBirthday
, который берет единственный вызванный параметр celebrator
. Тип этого параметра protocol<Named, Aged>
, что означает “любой тип, соответствующий обоим Named
и Aged
протоколы”. Не имеет значения, какой определенный тип передается функции, пока это соответствует обоим из требуемых протоколов.
Пример тогда создает новое Person
экземпляр вызывают birthdayPerson
и передачи этот новый экземпляр к wishHappyBirthday
функция. Поскольку Person
соответствует обоим протоколам, это - допустимый вызов, и wishHappyBirthday
функция в состоянии распечатать свое приветствие дня рождения.
Проверка соответствие протокола
Можно использовать is
и as
операторы описали в Преобразовании типа, чтобы проверить на соответствие протокола и бросить к определенному протоколу. Проверка и бросок к протоколу следуют точно за тем же синтаксисом как проверяющий на и бросающий к типу:
is
оператор возвращаетсяtrue
если экземпляр соответствует протоколу и возвратамfalse
если это не делает.as?
версия нисходящего оператора возвращает дополнительное значение типа протокола, и это значениеnil
если экземпляр не соответствует тому протоколу.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
массив инициализируется с литералом массивов, содержащим a Circle
экземпляр с радиусом 2 модулей; a Country
экземпляр инициализируется с площадью поверхности Соединенного Королевства в квадратных километрах; и Animal
экземпляр с четырьмя участками.
objects
массив может теперь быть выполнен с помощью итераций, и каждый объект в массиве может быть проверен, чтобы видеть, соответствует ли это HasArea
протокол:
for object in objects {
if let objectWithArea = object as? HasArea {
println("Area is \(objectWithArea.area)")
} else {
println("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
к свойству можно получить доступ и распечатать безопасным с точки зрения типов способом.
Обратите внимание на то, что основные объекты не изменяются процессом кастинга. Они продолжают быть a Circle
, a Country
и Animal
. Однако в точке, что они сохранены в objectWithArea
постоянный, они, как только известно, имеют тип HasArea
, и поэтому только их area
к свойству можно получить доступ.
Дополнительные требования протокола
Можно определить дополнительные требования для протоколов, Эти требования не должны быть реализованы типами, соответствующими протоколу. Дополнительные требования снабжаются префиксом optional
модификатор как часть определения протокола.
Дополнительное требование протокола можно вызвать с дополнительным объединением в цепочку, для учета возможности, что требование не было реализовано типом, соответствующим протоколу. Для получения информации о дополнительном объединении в цепочку посмотрите Дополнительное Объединение в цепочку.
Вы проверяете на реализацию дополнительного требования путем записи вопросительного знака после имени требования, когда это вызывают, такой как someOptionalMethod?(someArgument)
. Дополнительные требования свойства и дополнительные требования метода, возвращающие значение, будут всегда возвращать дополнительное значение надлежащего типа, когда к ним получат доступ или вызывают, для отражения факта, что не могло быть реализовано дополнительное требование.
Следующий пример определяет считающий целое число вызванный класс Counter
, который использует внешний источник данных для обеспечения его инкрементной суммы. Этот источник данных определяется CounterDataSource
протокол, имеющий два дополнительных требования:
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
}
CounterDataSource
протокол определяет дополнительное вызванное требование метода incrementForCount
и вызывают дополнительное требование свойства fixedIncrement
. Эти требования определяют два различных пути к источникам данных для обеспечения ассигновать инкрементной суммы для a Counter
экземпляр.
Counter
класс, определенный ниже, имеет дополнительное dataSource
свойство типа CounterDataSource?
:
@objc class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
Counter
класс хранит свою текущую стоимость в переменном вызванном свойстве count
. Counter
класс также определяет вызванный метод increment
, который постепенно увеличивается count
свойство каждый раз метод вызывают.
increment()
метод сначала пытается получить инкрементную сумму путем поиска реализации incrementForCount(_:)
метод на его источнике данных. increment()
метод использует дополнительное объединение в цепочку, чтобы попытаться вызвать incrementForCount(_:)
, и передает ток count
оцените как отдельный аргумент метода.
Отметьте два уровня дополнительного объединения в цепочку в действии здесь. Во-первых, это возможно это dataSource
может быть nil
, и так dataSource
имеет вопросительный знак после его имени для указания этого incrementForCount
должен только быть вызван если dataSource
неноль. Во-вторых, даже если dataSource
действительно существует, нет никакой гарантии, что это реализует incrementForCount
, потому что это - дополнительное требование. Это то, почему incrementForCount
также записан с вопросительным знаком после его имени.
Поскольку вызов к incrementForCount
может перестать работать по любой из этих двух причин, вызов возвращает дополнительное Int
значение. Это - истина даже при том, что incrementForCount
определяется как возврат обязательного Int
значение в определении CounterDataSource
.
После вызова incrementForCount
, дополнительное Int
то, что это возвращается, развернуто в вызванную константу amount
, использование дополнительной привязки. Если дополнительное Int
действительно содержит значение — т.е. если делегат и метод и существуют, и метод возвратил значение — развернутый amount
добавляется на сохраненное count
свойство и приращение завершены.
Если не возможно получить значение от incrementForCount(_:)
метод — также, потому что dataSource
ноль, или потому что источник данных не реализует incrementForCount
— тогда increment()
метод пытается получить значение от источника данных fixedIncrement
свойство вместо этого. fixedIncrement
свойство является также дополнительным требованием, и таким образом, его имя также написано с помощью дополнительного объединения в цепочку с вопросительным знаком на конце, чтобы указать, что попытка получить доступ к значению свойства может перестать работать. Как прежде, возвращенное значение является дополнительным Int
значение, даже при том, что fixedIncrement
определяется как обязательное Int
свойство как часть CounterDataSource
определение протокола.
Вот простое CounterDataSource
реализация, куда источник данных возвращает постоянное значение 3
каждый раз это запрашивается. Это делает это путем реализации дополнительного fixedIncrement
требование свойства:
class ThreeSource: CounterDataSource {
let fixedIncrement = 3
}
Можно использовать экземпляр ThreeSource
как источник данных для нового Counter
экземпляр:
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
println(counter.count)
}
// 3
// 6
// 9
// 12
Код выше создает новое Counter
экземпляр; устанавливает его источник данных, чтобы быть новым ThreeSource
экземпляр; и вызывает счетчик increment()
метод четыре раза. Как ожидалось, счетчик count
к трем каждым разам увеличивается свойство increment()
вызывается.
Вот более сложный вызванный источник данных TowardsZeroSource
, который делает a Counter
экземпляр подсчитывает или вниз по направлению к нулю от его тока count
значение:
class TowardsZeroSource: CounterDataSource {
func incrementForCount(count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
TowardsZeroSource
класс реализует дополнительное incrementForCount(_:)
метод от CounterDataSource
протокол и использование count
значение аргумента для разработки, который направление включить. Если count
уже нуль, возвраты метода 0
указать, что никакой дальнейший подсчет не должен иметь место.
Можно использовать экземпляр TowardsZeroSource
с существующим Counter
экземпляр для подсчета от -4
обнулять. Как только счетчик достигает нуля, больше подсчета не имеет место:
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
println(counter.count)
}
// -3
// -2
// -1
// 0
// 0