24 KiB
协议
<<<<<<< HEAD
Protocol(协议)定义了用于完成某项任务或功能的方法,属性等,它不具备任何功能的细节实现,只用来统一方法,属性等的名称和其类型.(译者注: 其他语言中也把 Portocol 称为 Interface(接口) ).协议可以被类,枚举,结构体采纳并实现,任意满足了协议要求的类,枚举,结构体被称之为协议遵循者.
协议可以要求其遵循者必须具备的某些特定的属性,方法,操作符,下标.
协议的语法
协议的定义和类,结构体,枚举的定义非常相似:
Protocol(协议) 定义了用于完成某项任务或功能的 方法,属性等 ,它不具备任何功能的细节实现,只用来统一方法,属性等 的名称和其类型.(译者注: 其他语言中也把 Portocol 称为 Interface(接口) ).协议可以被 类,枚举,结构体 采纳并实现,任意满足了协议要求的类,枚举,结构体被称之为 协议遵循者.
协议可以要求 协议遵循者 必须拥有的某些指定的 属性,方法,操作符,下标 .
协议的语法
协议 的定义和 类,结构体,枚举 的定义非常相似:
protocol SomeProtocol {
// 此处书写协议的内容
}
<<<<<<< HEAD
在类型名称后加上协议名称 ,并用冒号:分隔,从而实现协议;当实现多个协议时,各协议之间用逗号,分隔.
自定义类型在 类型名称 后加上 协议名称 ,并用冒号(:)分隔,从而采纳协议.实现多个协议时,用逗号(,)分隔.
struct SomeStructure: FirstProtocol, AnotherProtocol{
// 此处书写结构体的定义
}
<<<<<<< HEAD 当某个类实现了协议,并含有父类时,应当把父类名放在所有的协议名称之前
当某个类实现了协议,并含有父类时,应把父类名放在所有的协议名称之前
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol{
// 此处书写类的定义
}
属性要求
<<<<<<< HEAD
协议能够要求其遵循者必须拥有特定名称和类型的实例属性(instance property)或类属性 (type property),也可以指定协议中的属性的settable 和gettable,但它并不要求属性是存储型属性(stored property)还是计算型属性(calculate property).
当协议要求其中的某个属性为gettable时,即使实现了它的setter也不会出错. (译者注:此小节术语较多,可参阅属性章节).
属性通常被声明为变量,通过前置var关键字. 在属性声明后写上{ get set }指定属性为可读写的.{ get }用来描述属性为可读的.
协议能够要求其遵循者必须拥有 特定名称和类型 的实例属性(instance property) 或 类属性 (type property),也可以指定协议中的属性的settable 和 gettable ,但它并不要求属性是 存储型属性(stored property) 还是 计算型属性(calculate property).
当协议要求其中的某个属性为 gettable 时,即使实现了它的 getter 也不会出错. (译者注:此小节术语较多,可参阅属性章节)
属性通常被声明为变量,通过前置 var 关键字. 在属性声明后写上 { get set } 指定属性为可读写的. { get } 用来描述属性为可读的.
protocol SomeProtocol {
var musBeSettable : Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
<<<<<<< HEAD
当协议用来被类实现时,使用class关键字来说明该属性为类成员 ; 当协议被结构体或枚举实现时,则使用static关键字来说明
当协议被类实现时,使用 class 关键字来说明该属性为类成员 ; 当协议被结构体或枚举实现时,则使用 static 关键字
protocol AnotherProtocol {
class var someTypeProperty: Int { get set }
}
<<<<<<< HEAD 下边的协议包含了一个实例属性.
下边的协议包含了一个实例属性
protocol FullyNamed {
var fullName: string { get }
}
<<<<<<< HEAD
FullyNamed 定义了一个拥有 fullName 属性的协议. 该协议要求其 遵循者 必须拥有一个名为 fullName, 类型为 String 的可读属性.
下例是一个遵循了 FullyNamed 协议的简单结构体
FullyNamed 定义了一个拥有 fullName 属性的协议. 该协议要求其 遵循者 必须拥有一个名为 fullName, 类型为 String 的可读属性.
下边有一个遵循了 FullyNamed 的简单结构体
struct Person: FullyNamed{
var fullName: String
}
let john = Person(fullName: "John Appleseed")
//john.fullName 为 "John Appleseed"
<<<<<<< HEAD
定义一个名为Person并实现了FullyNamed协议的结构体. 每一个Person实例都拥有一个String类型,名为fullName的存储型属性,它满足了FullyNamed协议的要求,也就是说 Person完整的遵循了该协议.(如果协议未被完整遵循,Swift编译时会报出错误).
下例是一个遵循了FullyNamed协议的类:
定义一个名为 Person 并实现了 FullyNamed 协议的结构体. 每一个 Person 实例都拥有一个 String 类型,名为 fullName 的 存储型属性,它满足了FullyNamed协议的唯一要求,也意味着 Person 完整的遵循了该协议.(如果协议未被完整遵循,Swift编译时会报出错误).
下边是一个更为复杂的类,该类也实现并遵循了FullyNamed协议:
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: Stirng? = nil ) {
self.anme = name
self.prefix = prefix
}
var fullName: String {
return (prefix ? prefix ! + " " : " ") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName == "USS Enterprise"
<<<<<<< HEAD
该类将fullName实现为计算型只读属性.它的每一个实例都有一个名为name的必备属性和一个名为prefix的可选属性. 当prefix存在时,将prefix插入到name之前来为Starship构建fullName
方法要求
协议可以要求其遵循者必备某些特定的实例方法和类方法. 这些方法作为协议的一部分,像普通的方法一样写在协议体中,但却不需要方法体.而且,协议中的方法同样支持可变参数.
笔记: 协议中的
方法的语法同普通方法一样,但是不支持默认参数.
在协议中定义类方法与类属性一样,只需在方法前加上class关键字; 当协议用于被枚举或结构体遵循时,类方法的关键字需要换为static:
该类将 fullName 实现为计算型只读属性. Starship 类的每一个实例都有一个名为 name 的必备属性和一个名为 prefix 的可选属性. 当 prefix 存在时,将 prefix 插入到 name 之前来为 statship 构建 fullName
方法要求
协议 可以要求其 遵循者 必备某些实例方法和类方法. 这些方法作为协议的一部分,除了不需要方法体外,像普通的方法一样写在协议体中.此外,协议中的方法支持可变参数.
笔记: 协议中的
方法的语法同普通方法一样,但是不支持默认参数.
在协议中定义类方法与类属性一样,只需在方法前加上 class 关键字; 当协议用于被枚举或结构体遵循时,类方法的关键字需要换为static:
protocol SomeProtocol {
class func someTypeMethod()
}
下边是拥有一个实例方法的协议的例子
protocol RandomNumberGenerator {
func random() -> Double
}
<<<<<<< HEAD
RandomNumberGenerator 协议要求其遵循者必须拥有一个名为random, 返回值类型为Double 的实例方法. (这里假设随机数在 [0,1] 之间). 该协议只为生成随机数提供了一个统一的函数名称,而不去做具体的实现工作.
这里有一个名为LinearCongruentialGenerator且遵循了RandomNumberGenerator协议的类. 该类实现了名为 linear congruential generator(线性同余生成器) 的假随机数算法
RandomNumberGenerator 协议要求其遵循者必须拥有一个名为random, 返回值类型为Double 的示例方法. (尽管协议中并未明确指定,在这假设随机数在 [0,1] 之间). 该协议只为生成随机数提供了一个统一的函数名称,而不去做具体的实现工作.
这里有一个名为 LinearCongruentialGenerator 并且 遵循了 RandomNumberGenerator 协议 的类. 该类实现了名为 linear congruential generator 的假随机数算法
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())")
// 输出 : "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// 输出 : "And another one: 0.729023776863283"
突变方法要求
<<<<<<< HEAD
有时不得不在方法中改变实例的所属的类型.在基于Value Type的实例方法(译者注:指结构体和枚举中的方法)的func前加上mutating关键字来表明该方法允许改变该实例和其属性的所属类型. 这一突变过程在 Modifyting Value Types from Within Instance Methods 一节中有详细描述.
如果你打算在协议中定义一个能够改变实例所属类型的实例方法,只需要在方法前加上mutating关键字.使得结构体和枚举遵循该协议.(译者注:类中的变量为 Reference Type ,可以轻易的改变实例及其属性的类型 . 而结构体和枚举中的变量都为 Value Type, 因此需要加上mutating关键字才能更改它们的所属类型)
当协议的实例方法标记为
mutating时,在结构体或枚举的实现该方法时中,mutating关键字是不必可少的;当使用类遵循该协议时,则不需要为这个实例方法前加mutating关键字.
下例定义了一个名为Togglable,含有一个toggle方法的协议.通过名称猜测,toggle方法应该是用来 切换或恢复 某个属性的状态使用的.toggle方法前含有mutating关键字,用以标识其可以更改其遵循者的实例及其属性的所属类型.
有时不得不在方法中改变实例的所属的类型.在基于Value Type的实例方法( 译者注:指结构体和枚举中的方法)的 func 前加上 mutating 关键字 来表明该方法允许改变该实例和属性的所属类型. 这一过程在 Modifyting Value Types from Within Instance Methods 一节中有详细描述.
如果你打算在协议中定义一个能够改变实例所属类型的实例方法,只需要在方法前加上 mutating 关键字.使得结构体和枚举遵循该协议.(译者注:类中的变量为 Reference Type ; 结构体和枚举中的变量都为 Value Type, 因此有时需要更改它们的所属类型)
若把协议的实例方法标记为
mutating,mutating关键字只对结构体和枚举中的实例方法有效;当使用类遵循该协议时,不需要为这个实例方法前加mutating关键字.
下边的这个例子定义了一个名为 Togglable,含有一个 toggle 方法的协议.通过名字猜测,toggle 方法应该是用来 切换或恢复 某个状态使用的.toggle 方法前含有 mutating 关键字,用以标识其可以更改协议遵循者的实例所属类型
protocol Togglable {
mutating func toggle()
}
<<<<<<< HEAD
如果你使用枚举或结构体来实现Togglabl协议时,必须在枚举或接头体的toggle方法前加上mutating关键字.
下例定义了一个名为OnOffSwitch的枚举. 这个枚举可以切换On,Off两种状态. 该枚举中的 toggle含有mutating标记,用以匹配Togglable协议的方法要求:
如果你使用枚举或结构体来实现Togglabl 协议,那么在枚举或结构体中也需要在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 现在的值为 .On
协议作为类型
<<<<<<< HEAD
尽管协议本身不实现任何功能,但你可以将它当做类型来使用.
包括:
=======
尽管协议本身不实现任何功能,但你可以将它当做类型来使用
因此,你可以在很多允许使用其他类型的地方来使用协议类型,
包括:
- 作为函数,方法或构造器中的参数类型,返回值类型
- 作为常量,变量,属性的类型
- 作为数组,字典或其他容器中的元素类型
<<<<<<< HEAD
Note: 协议是一种类型,因此你应该向其他类型那样(Int,Double,String),使用驼峰式写法来书写协议 ======= Note: 因为协议是一种类型,因此你应该向其他类型那样(Int,Double,String),使用驼峰式写法来书写协议
这里有一个使用协议类型的例子:
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
<<<<<<< HEAD return Int(generator.random() * Double(sides)) +1 } }
这里定义了一个名为 Dice的类,用来代表桌游中的N个面的骰子.
Dice拥有名为sides和generator的两个属性,前者用来表示骰子有几个面,后者用来为骰子提供一个随机数的生成器
generator是一个RandomNumberGenerator协议类型的属性.因此,你可以为它赋值任何遵循该协议的类型.
Dice也拥有一个构造器(initializer)用来设置它的初始状态.构造器中含有一个名为generator,类型为RandomNumberGenerator的形参.你可以在此传入任意遵循RandomNumberGenerator协议的类型.
Dice拥有一个名为roll,用以返回骰子面值的实例方法.该方法先调用generator的random方法来创建一个 [0-1] 之间的随机数,然后使用这个随机数来生成骰子的面值. 这里的generator 被声明为采纳了RandomNumberGenerator协议,用以确保random方法能够被调用
下例展示了一个使用LinearCongruentialGenerator实例作为随机数生成器的六面骰子
return Int(generator.random * Double(sides)) +1
}
}
这里定义了一个名为 Dice的类,用来代表大富翁游戏的中的 N 个面的骰子.
Dice 的实例拥有名为sides和generator的两个属性,前者用来表示骰子有几个面,后者用来为骰子提供一个随机数的生成器
generator 是一个 RandomNumberGenerator 协议类型的属性.因此,你可以为它赋值任何遵循该协议的类型.
Dice 也拥有一个构造器(initializer)用来设置它的初始状态.构造器中含有一个名为generator ,类型为 RandomNumberGenerator 的形参.你可以在此传入任意遵循RandomNumberGenerator协议的类型.
Dice 拥有一个名为roll ,用以返回 1 到 骰子的面数 实例方法. 该方法调用generator 的 random 方法来创建一个 [0-1] 之间的随机数,然后使用这个随机数来生成骰子范围内的值. 这里的generator 被声明为 采纳了 RandomNumberGenerator 协议,用以确保random 能够被调用
下例展示了一个使用 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
委托(代理)
<<<<<<< HEAD
委托是一种设计模式(译者注:想起了那年 UITableViewDelegate 中的奔跑,那是我逝去的Objective-C...),它允许类或结构体将一些需要它们负责的功能或任务交由(委托)给其他的类型.
代理设计模式的实现很简单,首先定义一个协议来封装那些需要被委托的功能的函数和方法, 然后确保其遵循者拥有这些被委托的函数和方法.
委托模式可以用来响应特定的动作,或接收外部数据源提供的数据而无需要知道外部数据源的类型.
委托是一种设计模式(译者注:想起了那年 UITableViewDelegate 中的奔跑,那是我逝去的青春...),它允许类或结构体将一些需要它们负责的功能或任务交由(委托)给其他的类型.
代理设计模式的实现很简单,首先定义一个协议来封装那些需要被委托的功能的函数和方法; 然后确保其遵循者拥有这些被委托的函数和方法.
委托模式可以用来相应特定的动作,或接收外部数据源提供的数据而无需要知道外部数据源的类型.
下边这个例子展示了两个基于骰子游戏的两个协议:
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)
}
<<<<<<< HEAD
DiceGame协议可以被任何包含骰子的游戏采纳.DiceGameDelegate协议可以用来追踪DiceGame的游戏过程
DiceGame协议可以被任何包含骰子的游戏采纳. DiceGameDelegate 协议可以用来追踪DiceGame 的游戏过程
下边是一个 Snakes and Ladders 游戏的新版本(Control Flow含有关于该游戏的介绍).新版本使用Dice中的骰子,实现DiceGame和DiceGameDelegate协议
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dic = 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; borad[09] = +09; board[10] = +02
borad[14] = -10; board[19] = -11; borad[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)
}
}
<<<<<<< HEAD
更详细的Shakes and Ladders游戏描述,请在 Control Flow -> Break 章节查看.
这个版本的游戏被包装到了名为SnakeAndLadders并实现了DiceGame协议的类中. 该类含有一个可读的dice属性和一个play方法用来遵循协议.
游戏的初始化设置(setup)被为类的构造器(init())来实现.所有的游戏逻辑被转移到了协议方法play中.
更详细的 Shakes and Ladders 游戏描述,请在 Control Flow -> Break 章节查看.
这个版本的游戏被包装到了名为 SnakeAndLadders 并实现了DiceGame 协议的类中. 该类含有一个可读的 dice 属性和一个 play 方法用来遵循协议.
游戏的初始化设置(setup)被为类的构造器(init())来实现.所有的游戏逻辑被转移到了协议方法play 中.
注意:delegate 被定义为遵循DiceGameDelegate协议的可选属性,因为委托并不是该游戏的必备条件.
因为delegate 是可选的,它的默认值为nil
DicegameDelegate 提供了三个方法用来追踪游戏过程.被放置于游戏的逻辑中,即play()方法内.分别在游戏开始时,新一轮开始时,游戏结束时被调用.
<<<<<<< HEAD
因为 delegate 是一个遵循 DiceGameDelegate 的可选属性,因此在 play() 方法中使用了可选链来调用委托方法. 如果delegate 属性为 nil , 则委托调用优雅的,不含错误的失败.如果delegate不为nil, 则这些委托方法被调用,并且把SnakesAndLadders的这个实例当做参数一并传递
下边的这个例子展示了一个名为DiceGameTracker,实现DiceGameDelegate协议的类
因为 delegate 是一个遵循 DiceGameDelegate 的可选属性,因此在 play() 方法中使用了可选链来调用委托方法. 如果delegate 属性为 nil , 则委托调用优雅的,不含错误的失败.如果delegate 不为nil, 则这些委托方法被调用,并且把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")
}
}
<<<<<<< HEAD
DiceGameTracker实现了DiceGameDelegate协议中要求的全部方法.用来记录游戏已经进行的轮数. 当游戏开始时,numberOfTurns属性被赋值为0; 在每新一轮中递加; 游戏结束后,输出打印游戏的总轮数.
gameDidStart使用game参数来打印游戏的一些介绍信息.game的类型是DiceGame而不是 SnakeAndLadders, 因此gameDidStart只能访问和使用DiceGame协议中的成员. 但是仍然可以使用类型转换来访问其实例. 在gameDidStart中,当game是SnakesAndLadders的实例时,会打印出适当的信息. 因为game是被视为遵循了DiceGame协议的属性,也就是说它拥有dice属性,所以gameDidStart方法可以访问和打印dice的sides属性,而无需知道这是一场什么游戏....
这是DiceGameTracker的运行实例:
DiceGameTracker 实现了 DiceGameDelegate 协议中要求的全部方法.用来记录游戏已经进行的轮数. 当游戏开始时, numberOfTurns 属性被赋值为0; 在每新一轮中递加; 游戏结束后,输出打印游戏的总轮数.
gameDidStart 使用 game 参数来打印游戏的一些介绍信息. game 的类型是DiceGame而不是 SnakeAndLadders, 因此gameDidStart 只能访问和使用DiceGame协议中的成员. 但是仍然可以使用类型转换来访问其实例. 在gameDidStart中,当game是SnakesAndLadders 的实例时,会打印出适当的信息. 因为game 是被视为遵循了DiceGame协议的属性,也就是说它拥有dice 属性,所以gameDidStart方法可以访问和打印dice 的 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”