Swift学习第七枪--协议(一)

 协议(Protocols)

协议 定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为 遵循(conform) 这个协议。

- 协议的语法(Protocol Syntax)

- 对属性的规定(Property Requirements)

- 对方法的规定(Method Requirements)

- 对Mutating方法的规定(Mutating Method Requirements)

- 对构造器的规定(Initializer Requirements)

- 协议类型(Protocols as Types)

- 委托(代理)模式(Delegation)

- 在扩展中添加协议成员(Adding Protocol Conformance with an Extension)

- 通过扩展补充协议声明(Declaring Protocol Adoption with an Extension)

- 集合中的协议类型(Collections of Protocol Types)

- 协议的继承(Protocol Inheritance)

- 类专属协议(Class-Only Protocol)

- 协议合成(Protocol Composition)

- 检验协议的一致性(Checking for Protocol Conformance)

- 对可选协议的规定(Optional Protocol Requirements)

- 协议扩展(Protocol Extensions)

1.协议的语法

协议的定义方式与类,结构体,枚举的定义非常相似。

protocol SomeProtocol {

// 协议内容

}

要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号 : 分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号 , 分隔。

struct SomeStructure: FirstProtocol, AnotherProtocol {

// 结构体内容

}

如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {

// 类的内容

}

2.对属性的规定

协议可以规定其 遵循者 提供特定名称和类型的 实例属性(instance property) 或 类属性(type property) ,而不指定是 存储型属性(stored property) 还是 计算型属性(calculate property) 。此外还必须指明是只读的还是可读可写的。

如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。

协议中的通常用var来声明属性,在类型声明后加上 { set get } 来表示属性是可读可写的,只读属性则用 { get} 来表示。

protocol SomeProtocol {

var mustBeSettable : Int { get set }

var doesNotNeedToBeSettable: Int { get }

}

在协议中定义类属性(type property)时,总是使用 static 关键字作为前缀。当协议的遵循者是类时,可以使用 class 或 static 关键字来声明类属性,但是在协议的定义中,仍然要使用 static 关键字。

protocol AnotherProtocol {

static var someTypeProperty: Int { get set }

}

如下所示,这是一个含有一个实例属性要求的协议。

protocol FullyNamed {

var fullName: String { get }

}

FullyNamed 协议除了要求协议的遵循者提供fullName属性外,对协议对遵循者的类型并没有特别的要求。这个协议表示,任何遵循 FullyNamed 协议的类型,都具有一个可读的 String 类型实例属性 fullName 。

下面是一个遵循 FullyNamed 协议的简单结构体。

struct Person: FullyNamed{

var fullName: String

}

let john = Person(fullName: "John Appleseed")

//john.fullName 为 "John Appleseed"

这个例子中定义了一个叫做 Person 的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了 FullyNamed 协议。

Person 结构体的每一个实例都有一个叫做 fullName , String 类型的存储型属性。这正好满足了 FullyNamed 协议的要求,也就意味着,Person 结构体完整的 遵循 了协议。(如果协议要求未被完全满足,在编译时会报错)

下面是一个更为复杂的类,它采用并遵循了 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"

Starship类把 fullName 属性实现为只读的计算型属性。每一个 Starship 类的实例都有一个名为 name 的属性和一个名为 prefix 的可选属性。 当 prefix 存在时,将 prefix 插入到 name 之前来为Starship构建 fullName , prefix 不存在时,则将直接用 name 构建 fullName 。

3.对方法的规定 

协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相

同。但是在协议的方法定义中,不支持参数默认值。

正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当协议的遵循者是类的时候,虽然你可以在类的实现中使用 class 或者 static 来实现类方法,但是在协议中声明类方法,仍然要使用 static 关键字。

protocol SomeProtocol {

static func someTypeMethod()

}

下面的例子定义了含有一个实例方法的协议。

protocol RandomNumberGenerator {

func random() -> Double

}

RandomNumberGenerator 协议要求其遵循者必须拥有一个名为 random , 返回值类型为 Double 的实例方法。尽管这里并未指明,但是我们假设返回值在[0,1)区间内。

RandomNumberGenerator 协议并不在意每一个随机数是怎样生成的,它只强调这里有一个随机数生成器。

如下所示,下边的是一个遵循了 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()

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

// 输出 : "Here's a random number: 0.37464991998171"

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

// 输出 : "And another one: 0.729023776863283"

4.对Mutating方法的规定

有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将 mutating 关键字作为函数的前缀,写在 func 之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

如果你在协议中定义了一个方法旨在改变遵循该协议的实例,那么在协议定义时需要在方法前加 mutating 关键字。这使得结构和枚举遵循协议并满足此方法要求。

注意:

用类实现协议中的 mutating 方法时,不用写 mutating 关键字;用结构体,枚举实现协议中的 mutating 方法时,必须写 mutating 关键字。

如下所示, Togglable 协议含有名为 toggle 的实例方法。根据名称推测, toggle() 方法将通过改变实例属性,来切换遵循该协议的实例的状态。

toggle() 方法在定义的时候,使用 mutating 关键字标记,这表明当它被调用时该方法将会改变协议遵循者实例的状态。

protocol Togglable {

mutating func toggle()

}

当使用 枚举 或 结构体 来实现 Togglable 协议时,需要提供一个带有 mutating 前缀的 toggle 方法。

下面定义了一个名为 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

5.对构造器的规定 

协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:

protocol SomeProtocol {

init(someParameter: Int)

}

5.1 协议构造器规定在类中的实现

你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。在这两种情况下,你都必须给构造器实现上"required"修饰符:

class SomeClass: SomeProtocol {

required init(someParameter: Int) {

//构造器实现

}

}

使用 required 修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。

注意:

如果类已经被标记为 final ,那么不需要在协议构造器的实现中使用 required 修饰符。因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示 required 和 override 修饰符

protocol SomeProtocol {

init()

}

class SomeSuperClass {

init() {

// 构造器的实现

}

}

class SomeSubClass: SomeSuperClass, SomeProtocol {

// 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"

required override init() {

// 构造器实现

}

}

5.2可失败构造器的规定

可以通过给协议 Protocols 中添加可失败构造器 (页 0)来使遵循该协议的类型必须实现该可失败构造器。

如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败

构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器( init! )。

6. 协议类型 

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

协议可以像其他普通类型一样使用,使用场景:

- 作为函数、方法或构造器中的参数类型或返回值类型

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

- 作为数组、字典或其他容器中的元素类型

注意:

协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例( FullyNamed 和 RandomNumberGenerator )如下所示,这个示例中将协议当做类型来使用

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个面的骰子。 Dice 的实例含有 sides 和 generator 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器。

generator 属性的类型为 RandomNumberGenerator ,因此任何遵循了 RandomNumberGenerator 协议的类型的实例都可以赋值给generator ,除此之外,无其他要求。

Dice 类中也有一个构造器(initializer),用来进行初始化操作。构造器中含有一个名为 generator ,类型为 RandomNumberGenerator 的形参。在调用构造方法时创建 Dice 的实例时,可以传入任何遵循RandomNumberGenerator 协议的实例给generator。

Dice 类也提供了一个名为 roll 的实例方法用来模拟骰子的面值。它先使用 generator 的 random() 方法来创建一个[0,1)区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为generator遵循了 RandomNumberGenerator 协议,因而保证了 random 方法可以被调用。

下面的例子展示了如何使用 LinearCongruentialGenerator 的实例作为随机数生成器创建一个六面骰子:

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())

for _ in 1...5 {

print("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

总结:这篇就今天就写到协议类型,下篇从委托(代理)模式开始总结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容

  • 132.转换错误成可选值 通过转换错误成一个可选值,你可以使用 try? 来处理错误。当执行try?表达式时,如果...
    无沣阅读 1,180评论 0 3
  • 本章将会介绍 协议语法属性要求方法要求(Method Requirements)Mutating 方法要求构造器要...
    寒桥阅读 387评论 0 3
  • 定义: 协议定义了一个蓝图,规定了用来实现某一特定的任务或者功能的方法、属性,或其他需要的东西。类、结构体、枚举都...
    geekLiu阅读 1,383评论 0 1
  • 代理又称为协议,委托,protocol ,是iOS开发中经常用到的设计模式。刚学习iOS的时候不是特别理解代...
    小芳姑娘2012阅读 423评论 0 1
  • “不要骗我,我书读的少。”“可是真没有骗你,我书真读的不多。” 我毕业于中文系,中国文化博大精深,现在我现有的知识...
    逆风不解阅读 749评论 21 18