Spec-Zone .ru
спецификации, руководства, описания, API

Библиотека разработчика XCode

Разработчик

Swift язык программирования

iBook
На этой странице

Протоколы

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

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

Синтаксис протокола

Вы определяете протоколы очень похожим способом к классам, структурам и перечислениям:

  • 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