swift协议_Swift Protocols

分享到:

协议定义了方法、属性的大纲以及其他适用于特殊任务或功能块的要求。协议实际上不实现这些要求-只对实现的表现形式做出描述。然后,协议可被类、结构体或枚举采用以实现这些要求。满足协议要求的任何类型都被认为符合该协议。

协议可以要求符合规定的类型具有特定的实例属性、实例方法、类型方法、运算符以及下标。

 

协议语法

 

采用与类、结构体和枚举非常相似的方式来定义协议:

 

protocol SomeProtocol {

// protocol definition goes here

}

 

自定义类型声明通过将协议名称放在类型名称后面,并用冒号隔开,采用一种特定的协议作为其定义的一部分。可以列出多种协议,各协议之间用逗号隔开:

 

struct SomeStructure: FirstProtocol, AnotherProtocol {

// structure definition goes here

}

 

如果类还有一个超类,在采用任何协议前列出超类的名称,后接a

 

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {

// class definition goes here

}

 

 

性能要求

 

协议可以要求任何符合要求的类型提供实例属性或有特定名称和类型的类型属性。协议并不指定属性是否应该是存储属性还是计算属性---其仅指定所需的属性名称与类型。协议还指定每一属性是否必须为可得到或是可得到且可设定。

如果协议要求某一属性可得到且可设定,则该属性要求不能通过常量存储属性或只读计算属性实现。如果协议只要求某一属性可得到,则该要求可通过任何类型的属性满足,如果它对你的代码有效,那是因为它还是可设定的属性,所以是有效的。

属性要求总是声明为变量属性,并带有var关键词前缀。可得到且可设定属性通过在其类型声明后写上{ get set }来表示,而可得到属性通过写上{ get }来表示。

 

protocol SomeProtocol {

var mustBeSettable: Int { get set }

var doesNotNeedToBeSettable: Int { get }

}

 

当你在某一协议中定义类型属性要求时,应始终为其加类关键字前缀。当通过结构体或枚举执行时,即使类型属性要求带有静态关键字前缀,也是真的:

 

protocol AnotherProtocol {

class 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 ? prefix! + " " : "") + name

ncc1701 = Starship(name: "Enterprise", prefix: "USS")

ncc1701.fullName is "USS Enterprise"

 

对某一星舰来说,该类将fullName属性要求作为计算只读属性来执行。星舰类实例中都存储了一个强制性名称和一个可选前缀。如果存在前缀值,fullName属性则使用前缀值,并将其插至名称的开始部分,以便为starship创建一个全称。

 

方法要求

 

协议可以要求符合要求的类型实现特定的实例方法和类型方法。这些方法以与正常实例以及类型方法完全相同的方式写入协议定义的一部分,但是没有大括号或方法体。允许存在variadic参数,但受同样的关于正常方法规则的制约。

 

注:协议可以将相同的语法用作正常方法,但是不允许其为方法参数指定默认值。

 

与类型属性要求一样,当在协议中定义类型方法要求时,应始终为其加上类关键字前缀。当通过结构体或枚举执行时,即使类型方法要求带有静态关键字前缀,也是一样的:

 

protocol SomeProtocol {

class func someTypeMethod()

}

 

下面的例子定义了一个协议与单个实例方法要求:

 

protocol RandomNumberGenerator {

func random() -> Double

}

 

RandomNumberGenerator协议,要求任何符合要求的类型都应有名为random的实例方法,而在调用时其会返回Double值。(尽管其并没有被指定为协议的一部分,但我们假定该值是0.0到1.0(包括1.0)之间的某个数。)

RandomNumberGenerator协议对每一随机数是如何生成的不作任何假设 — 其仅仅要求生成器为生成新的随机数提供标准的方式。

以下是一个关于采用并符合RandomNumberGenerator协议的类的实施的例子。这个类的实施使用了一个名为线性同余发生器的虚拟随机数生成算法:

 

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

}

generator = LinearCongruentialGenerator()

("Here's a random number: \(generator.random())")

prints "Here's a random number: 0.37464991998171"

("And another one: \(generator.random())")

prints "And another one: 0.729023776863283"

 

 

变异方法的要求

 

有时候某一方式有必要修改(或变异)它所属的实例。对于数值类型的实例方法来说(也就是,结构体与枚举),你可以将变异的关键字放在方法的func关键字之前,以表明允许该方法修改其所属的实例和/或该实例的任何属性。这个过程在“内部实例方法部分的修改值类型”中有所描述。

如果你要定义一个旨在改变采用该协议的任何类型的实例协议实例方法要求时,将改变的关键词标记为协议定义的一部分即可。这使结构体及枚举能够采用协议并且满足该方法的要求。

 

注:如果你将某个协议实例方法要求标记为变异,在为某一类编写该实施方法时,你就没必要编写改变关键字了。该变异关键字只用于结构体和列举。

 

下面的例子定义了Togglable协议,它定义了名为toggle的单个实例方法要求。正如其名,toggle方法旨在切换或转化任何符合要求的类型的状态,通常是通过修改该类型的某一个属性的方法。

toggle方法被使用变异关键字标记为Togglable协议定义的一部分,表明在调用该方法时,它应该改变符合要求的实例的状态:

 

protocol Togglable {

mutating func toggle()

}

 

如果你为某个结构或枚举执行Togglable协议,该结构体或枚举可通过实施同样标记为变异的toggle方法而符合该协议。

下面的例子定义了一个名为OnOffSwitch的枚举。该枚举在两种状态之间切换,由枚举实例的On及Off表示。枚举实施toggle被标记为变异,以满足Togglable协议的要求:

 

enum OnOffSwitch: Togglable {

case Off, On

mutating func toggle() {

switch self {

case Off:

self = On

case On:

self = Off

}

lightSwitch = OnOffSwitch.Off

lightSwitch.toggle()

lightSwitch is now equal to .On

 

 

作为类型的协议

 

事实上,协议本身并不会执行任何功能。 尽管如此,要在你的代码中使用,你创建的任何协议都会成为成熟的类型。

因为它是一种类型,你可以在很多允许使用其他类型的地方使用某个协议,包括:

As a parameter type or return type in a function, method, or initializerz

作为函数、方法或初始化函数中的参数类型或返回类型

As the type of a constant, variable, or property

作为常量、变量或属性的类型

As the type of items in an array, dictionary, or other container

作为阵列、字典或其他容器项的类型

 

注:因为协议是以大写字母作为名称开头的类型(例如:FullyNamed和RandomNumberGenerator),用以匹配Swift中其他类型(例如,Int,String以及Double)的名称。

 

下面是一个协议用作类型的例子:

 

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的新类,它代表在棋盘游戏中使用的一个多面骰子。Dice实例有调用面数的整数属性,用以表示有多少面数,还有能调用生成器的属性,其可以提供随机数字生成器以创建骰子滚动值。

生成器属性的类型为RandomNumberGenerator.因此,你可以将其设置为选用RandomNumberGenerator协议的任何类型的实例。除必须采用RandomNumberGenerator协议外,你赋给该属性的实例不需要别的东西。

骰子也有一个初始化函数,用以设置它的初始状态。该初始化函数有一个能调用生成器的参数,且其也有RandomNumberGenerator类型的性质。当初始化新的Dice实例时,你可以将任何符合条件的类型的值输入到此参数中。

Dice提供了一种实例方法,即滚动,其能返回介于1以及骰子上点数之间的整数值该方法调用生成器的随机方法以创建介于0.0与1.0之间的新的随机数,并且使用该随机数创建正确范围内的骰子滚动值。我们都知道生成器应采用RandomNumberGenerator,保证其有随机方法可调用。

以下解释怎样才能使用Dice类创建六面骰子,该骰子具有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协议可被追踪DiceGame进程的任何类型采用。

以下是最初引入到控制流程中的游戏的一个版本。该版本适合于使用Dice实例作为其骰子滚动值;适合于采用DiceGame协议;并且适合于告知DiceGameDeegate其进程:

 

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)

 

关于游戏的描述,请参阅控制流程章节的跳出循坏部分。

这个版本的游戏被包装成名为SnakesAndLadders的类,且其采用DiceGame协议。它提供了可返回的骰子属性以及游戏方式以符合该协议。(骰子属性被声明为常数属性,因为在初始化后其并不需要改变,并且协议仅要求其可得到。)

的棋盘设置在类的int()初始化器中进行。

所有游戏逻辑被加入到协议的游戏方法中,其使用协议所需要的骰子属性提供其骰子滚动值。

请注委托属性被定义为可选的DiceGameDeegate,因为要想玩游戏并不一定需要委托。因为它是可选的类型,所以委托属性自动设置为空的初始值。此后,游戏实例化器可以选择将属性设置成合适的委托。

DiceGameDelegate提供了三种方法,用于跟踪一个游戏的进程。这三种方法已被纳入上述游戏方法的游戏逻辑,并且在开始新游戏、开始新一轮或游戏结束时被调用。

因为委托属性是可选的DiceGameDeegate,所以每次在调用委托方法时,游戏方法会使用可选链接。如果委托属性是空的,这些委托调用会优雅地失败且没有错误。如果deegate属性不为零时,

委托方法作为一个参数被调用,并被传给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所需的所有三种方法。并使用这些方法来记录游戏所进行的轮数。游戏开始时,其将numberOfTurns属性重置为0;每当新的一轮开始时,增加其值;并且在游戏结束时,输出游戏的总轮数。

如上所示,gameDdStart的实现使用参数输出某些关于游戏即将开始的介绍信息。该游戏具有DiceGame类型而不是SnakesAndLadders类型,因此gameDdStart只能访问和使用作为DiceGame协议的一部分而实现的方法和属性。然而,该方法仍然能够使用类型转换查询基础实例的类型。

在这个例子中,其检查了游戏是否是SnakesAndLadders的一个实际实例;如果是的话,会输出相应的信息。

gameDdStart还访问了已通过游戏参数的骰子属性。因为我们知道游戏符合DiceGame协议,则保证其会有投资属性,因此,无论玩的是哪一种游戏,gameDidStart方法能够访问并输出骰子的面数属性。

下面内容具体介绍了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

 

 

使用扩展功能添加协议一致性

 

即使并没有访问现有类型的源代码的权限,你也可以将其扩展至采用并符合新的协议。扩展可以为现有类型添加新属性、方法以及下标,因此其可以添加协议可能需要的任何要求。欲了解更多有关扩展功能的信息,请参见“扩展功能”部分的内容。

 

注:当在一个扩展里的实例类型中添加该符合性时,类型的现有实例会自动采用并符合协议。

例如,该协议,即TextRepresentabe,能够被任何用文字表示的类型实现。这可能是对其自身的描述,或者是其当前状态的文本版本:

 

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 {}

 

在任何情况下,如果所需的类型为TextRepresentabe,都可以使用Hamster的实例:

 

let simonTheHamster = Hamster(name: "Simon")

let somethingTextRepresentable: TextRepresentable = simonTheHamster

println(somethingTextRepresentable.asText())

// prints "A hamster named Simon"

 

注:类型不会仅仅通过满足其要求来自动采用一个协议。他们必须始终明确声明采纳该协议。

协议类型集合

 

如“协议就是类型”章节中所述,协议可被用作存储在某个集合(例如数组或词典)中的类型。此示例创建了TextRepresentabe事物的一个数组:

 

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

}

 

这个例子定义了一个新协议,PrettyTextRepresentabe,其继承自TextRepresentable。采用PrettyTextRepresentabfe的任何东西都必须满足TextRepresentable所实施的所有要求,加上PrettyTextRepresentabe所实施的附加要求。在这个例子中,PrettyTextRepresentable添加一个单一要求以提供一个调用asPrettyText返回String的实例方法。

SnakeAndLadders类可被扩展为采用且符合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

 

该扩展声明其采用了PrettyTextRepresentabe协议,并且为SnakesAndLadders类型提供了asPrettyText方法的实现。属于PrettyTextRepresentabe的任何东西也属于TextRepresentable,因此,asPrettyText实现开始于从TextRepresentabe协议中调用asText方法以开始输出字符串。它会添加一个冒号和一个换行符,并将其作为优美文本表达式的开始。然后,其迭代棋盘方框中的所有数组,并且为每一方框加上一个表情符:

 

If the square’s value is greater than 0, it is the base of a ladder, and is represented by ▲.

If the square’s value is less than 0, it is the head of a snake, and is represented by ▼.

Otherwise, the square’s value is 0, and it is a “free” square, represented by ○.

 

如果方格值大于0,那它就是梯基,由▲表示。

如果方格值小于0,那它就是蛇头,由▼表示。

否则,方格值为0,那它就是游离方格,由○表示。

现在方法实现可用于输出任何SnakesAndLadders实例的优美的文本描述:

 

println(game.asPrettyText())

// A game of Snakes and Ladders with 25 squares:

// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

 

 

协议组合

 

要求一个类型同时符合多种协议是很有用的。通过协议组合,你可以将多个协议组合成单一要求。

协议组合的形式有protocok<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

wishHappyBirthday(celebrator: protocol<Named, Aged>) {

println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")

birthdayPerson = Person(name: "Malcolm", age: 21)

wishHappyBirthday(birthdayPerson)

prints "Happy birthday Malcolm - you're 21!"

 

这个例子使用名为Name的可返回String属性的单一要求定义了名为Named的协议。这个例子还使用名为age的可返回Int属性的单一要求定义了名为Aged的协议。这两种协议是通过结构体calledPerson被采用的。

这个例子还使用名为celebrator的单个参数定义了名为wishHappyBirthday的函数。该参数的类型是protocol<Named,Aged>,意即“符合Named以及Aged协议的所有类型。”只要符合这两个要求协议,传给函数的具体类型是什么并不重要。

随后,该例子创建了一个名为birthdayPerson的新的Person实例并将这个新实例传递给wishHappyBirthday函数。因为Person符合这两个协议,所以这是有效的调用,并且wishHappyBirthday函数可以输出其生日祝福。

注:协议组合未定义一个新的、永久性的协议类型。相反,它们定义一个临时的本地协议,该协议含有组合中所有协议的要求。

 

检查协议一致性

 

你可以使用在“类型转换”中所述的is和as运算符检查协议的符合性,并且转换某个特定的协议。根据与检查并转换类型完全一样的语法检查并转换某个协议:

如果实例符合协议,那么运算符返回true;否则,返回false。

向下转换运算符的as?版本会返回协议类型的可选值,如果实例不符合协议那么这个值为null。

向下转换运算符的as版本会强制向下转换协议类型,如果转换不成功,就会发送运行错误。

 

这个例子使用area的可返回Double属性的单一属性要求定义了一个HasArea的协议。

 

@objc protocol HasArea {

var area: Double { get }

}

 

注:只有在协议被标记为@objc属性的情况下,才能检查协议的符合性,可参见上述HasArea协议。该属性表明该协议可能会接触到Objective-C代码,这在《Using Swift with Cocoa and Objective-C》中有详细描述。尽管你并没有同时操作Objective-C,但是如果检查协议的符合性,还是需要将协议标记为@objc属性。 还要注@objc协议只能被类采用,而不能被结构体或枚举采用。如果你将你的协议标记为@objc以检查其一致性,那么你只能够将此协议应用于类的类型。

 

这里有两个类: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属性要求作为计算属性执行。Country类直接执行area的要求,作为一种存储属性。这两个类都正确地符合HasArea协议。

此处有一个名为Anima但不符合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)

]

 

对象数组被数组常值中包含半径为两个单位的Circle实例初始化;Country实例被英国的表面面积(单位:千米),以及有四条腿的Animal实例初始化。

现在,可以迭代对象数组,并且可以检查数组中的每个对象以判断其是否符合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属性可以类型安全的方式来访问并输出。

需要注的是,相关的对象没有因转换过程而发生改变。他们仍然是一个Circle、Country和Animal。但是,在它们被存储在objectWithArea常数的情况下,我们只知道它们是HasArea类型,所以只能访问它们的area属性。

任择议定书的要求

 

你可以为协议定义任选要求,符合该协议的类型执行并不需要执行这些类型。任选要求以@optona关键字做前缀并作为其定义的一部分。

任择议定书的要求可被可选链接调用,用来解释该要求不能被某个符合该协议的类型实现的可能性。欲了解有关可选链接的信息,请参阅“可选链接”部分。

当被调用时,你可以通过在要求的名称后编写一个问号来检查该可选要求的实现,例如,someOptonaiMethod?(someArgument)。

当访问或调用可选属性要求以及返回某个数值的可选方法要求时,它们将始终返回适当类型的一个可选值,以表示可选要求可能不会实现。

注:只有在你的协议标有@objc属性时,任择议定书要求才会被指定。即使你没有同时操作Objective-C,如果你想要指定可选要求你还是需要为协议标记@objc属性。 还要注@objc协议只能被类采用,而不能被结构体或枚举采用。如果将协议标记为@objc以检查指定可选要求,那么你只能将此协议应用于类别类型。

下面的例子定义了一个调用Counter的整数计算类,它使用外部数据源提供其增加数量。该数据源通过counterDataSource协议定义,该协议有两个可选要求:

 

@objc protocol CounterDataSource {

@optional func incrementForCount(count: Int) -> Int

@optional var fixedIncrement: Int { get }

}

 

counterDataSource协议定义了一个incrementForCount的可选方法要求以及一个fixedIncrement的可选属性要求。这些要求为数据源定义了不同的方式,以便为Counter实例提供适当的增加数量。

注:严格来说,你可以在不执行任一协议要求时,编写一个符合CounterDataSource的自定义类。毕竟,它们都是可选的。尽管在技术上来讲是允许的,但这不会有助于形成一个很好的数据源。

 

Counter类,定义如下,具有CounterDataSource?类型可选的dataSource属性:

 

@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属性。

增量方法首先试图通过在其数据源中寻找incrementForCount方法的实现来检索一个增量。increment方法是用可选链接试图调用incrementForCount,并且将当前的count值作为方法的参数传输。

注此处有两个级别可选链接在起作用。 首先,dataSource有可能为nil,因此dataSource在其名称之后有一个问号,用来说明只有在dataSource不是空的时候incrementForCount才能够被调用。其次,即使dataSource确实存在,也不能保证它会执行incrementForCount,因为它是一个可选要求。 这就是为什么incrementForCount在编写时还需要在其名称后面加一个问号。

因为这两个原因的某一个都可能会导致incrementForCount调用失败,所以调用返回一个可选的Int值。即使在CounterDataSource 的定义中incrementForCount被定义为返回一个非可选Int值,这也是真的。

调用incrementForCount后,使用可选约束,其返回的可选Int会解包成一个数量的常量。如果可选Int确实包含一个数值—也就是说,如果指令与方法同时存在,并且该方法可返回一个数值—解包amount会添加到储存的count属性上,并且增量完成。

如果不可能从incrementForCount方法中检索一个数值—或者是因为dataSource是空的或者是因为数据源没有实现incrementForCount---那么increment方法则会试图从数据源的fixedIncrement属性中检索一个数值。fixedIncrement属性也是一个可选要求,因此其名称也使用可选链接编写并且以问号结束,以表明不能访问该属性值。像以前一样,返回的数值是可选Int值,即使在CounterDataSource 协议的定义中fixedIncrement被定义为非可选Int属性。

以下是一个简单的执行CounterDataSource的例子,其中在每次查询时数据源都返回常量值3。它通过执行可选fixedIncrement实现着一点。

 

class ThreeSource: CounterDataSource {

let fixedIncrement = 3

}

 

在新的Counter实例中你可以使用ThreeSource实例作为数据源:

 

var counter = Counter()

counter.dataSource = ThreeSource()

for _ in 1...4 {

counter.increment()

println(counter.count)

}

// 3

// 6

// 9

 

以上代码创建了一个新的Counter实例;将其数据源设置为新的ThreeSource实例;并且调用四次计数器的increment方法。同预计的一样,在每次调用increment时,计数器的count属性会增加3。

以下是一个更复杂的数据源TowardszeroSource,它能使一个Counter实例从其当前数值向上或向下计数到零。

TowardszeroSource类在counterDataSource协议中使用可选incrementForCount方法,并且使用count参数值算出应从哪个方向开始计数。如果count已经是零,该方法则返回0以表明无需再进行进一步的计数。

你可以使用现有Counter实例中的一个TowardszeroSource实例从4计数到零。当计数器达到零时,不会发生更多的计数:

 

class TowardsZeroSource: CounterDataSource {

func incrementForCount(count: Int) -> Int {

if count == 0 {

return 0

} else if count < 0 {

return 1

} else {

return -1

}

 

昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC