「协议」(protocol)声明一系列方法、属性、下标等用来约束其「遵循者」,进而保证「遵循者」能够完成限定的工作。「协议」本身不实现任何功能,它仅仅描述了「遵循者」的实现。「协议」能被类、结构体、枚举所遵循,若某个类型遵循某「协议」,则称该类型遵循(conform to)某协议。

协议的语法

协议的定义与类、结构体和枚举的定义非常相似,如下:

protocol SomeProtocol {
// 协议内容
}

在类/结构体/枚举的名称后加上协议名称,中间以冒号:分割即可实现协议;实现多个协议时,各协议之间用逗号,分隔,如下:

struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}

呵,继承也是使用:哦!当某个类含有父类的同时并实现了协议,应该把父类放在所有协议之前,如下:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}

属性要求

「协议」能够要求其遵循者必须含有一些特定名称和类型的「实例属性」和「类型属性」,也能够要求属性的读写权限(即gettable和settable),但它不要求属性是「存储型属性」还是「计算型属性」。

如果「协议」要求某个属性可写,但「其遵循者」将该属性定义为常量,那么认为该类型不满足「协议」的property requirements;另外一种情况,若「协议」要求某个属性可读(仅仅gettable),但某个「遵循者」定义该属性时允许设置(settable),那么认为该类型满足「协议」的property requirements。

一般而言,「协议」中声明的属性都是变量(即都用var修饰)。Gettable和settable属性用{ get set}修饰;Gettable属性由{ get }修饰,如下:

protocol SomeProtocol {
var mustBeSettable: Int { get set}
var doesNotNeedToBeSettable: Int { get }
}

也可以在「协议」中声明类型属性,在「协议」中声明「类型属性」和在类/结构体/枚举中定义「类型属性」不太一样。在结构体和枚举中,定义「类型属性」时使用关键字static修饰;在类中定义「类型属性」时可以使用static,也可以使用class,关于二者不同这里不赘述了,详见这里;但是对于protocol,Swift规定无论什么情况下都使用static修饰静态属性。但是在「协议」的「遵循者」类中定义时,可以根据需要使用staticclass

方法要求

「协议」能够要求其遵循者必须实现某些指定名称和类型的实例方法和类方法。协议方法的声明与普通方法声明相似,但它不需要方法body。

值得注意的是,协议方法支持变长参数(variadic parameter),但不支持默认参数(default parameter)。

和「属性要求」类似,「协议」要求无论在什么时候,声明「类型方法」时都是用关键字static修饰。

除此之外,「协议」中还可以声明mutable类型方法。

构造器要求

「协议」还可以要求遵循者必须定义某些指定的构造器。在「协议」中声明构造器和在类中声明构造器差不多,只是没有initializer body,如下:

protocol SomeProtocol {
init(someParameter: Int)
}

当类实现协议的构造器时

假设有某个协议SomeProtocol,类SomeClass遵循该协议,那么意味着SomeClass的子类也遵循该协议。为什么呢?因为若SomeProtocol中声明了一些属性和方法以及下标,则SomeClass必然会定义这些属性和方法以及下标,通过继承,SomeClass的子类也必然包括这些属性和方法以及下标,所以我们说:SomeClass的子类也遵循SomeProtocol。

然而,上面的这种说法不严密,实际情况更复杂一些,因为协议SomeProtocol中不光可以声明方法/属性/下标,还可以声明构造器但在Swift中,除了某些特殊情况外,构造器是不被子类继承的,所以SomeClass中虽然能够保证定义了protocol要求的构造器,但不能保证SomeClass的子类中也定义了protocol要求的构造器。

P.S:哪些特殊情况?看这里

所以这里有一个八阿哥(bug),SomeClass定义SomeProtocol中声明的构造器没问题,如何保证SomeClass的子类也定义SomeProtocol中声明的构造器呢?

关键字required可以解决这个问题。

Swift规定,SomeClass在定义SomeProtocol声明的构造器时,必须加上一个required修饰符作为前缀,如下:

class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}

required的作用是确保SomeClass的子类必须也得实现这个构造器,这里有更详细的描述。

当然,如果SomeProtocol的遵循者是一个final class(不能被继承的类),那么上面这种情况就不需要required来修饰initializer了。

还有一种复杂的情况,如果某个子类需要override父类中的designated initializer,同时该子类还遵循了某个协议,该协议也定义了一个与该designated initializer同名的构造器,那么子类在定义该initializer时需要同时使用requiredoverride修饰,如下:

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 initializer。

协议类型

协议本身不具备任何功能,但在实际使用中,你完全可以把它当做一个类型来使用。使用场景包括:

  • 作为函数/方法/构造器中的参数类型或返回类型;
  • 作为常量/变量/属性的类型;
  • 作为array/set/dictionary等容器中元素的类型;

举个栗子,定义一个类Dice(表桌游中的骰子),如下:

protocol RandomNumberGenerator {
func random() -> Double
} class Dice {
let sides: Int // 表示「骰子」有几个面
let generator: RandomNumberGenerator // 随机数生成器 // 构造器,RandomNumberGenerator是一个Protocol名
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
} // 摇动「骰子」
func roll() -> Int {
return Int(generator.random() * Double(sides)) +
}
}

Dice含有两个属性sides和generator,前者用来表示骰子有几个面,后者为骰子提供一个随机数生成器。由于后者为RandomNumberGenerator的协议类型,所以它能够被赋值为任意遵循该协议的类型。

roll方法用来模拟骰子的面值,它先用generator的random方法来创建一个[0-1]区间内的随机数种子,然后加工这个随机数种子生成骰子的面值。

如下所示,LinearCongruentialGenerator的实例作为随机数生成器传入Dice的构造器:

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
}
} var d6 = Dice(sides: , generator: LinearCongruentialGenerator())
for _ in ... {
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
*/

代理设计模式

「委托」(delegation)是一种「设计模式」,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型的实例。在OC中,委托随处可见。在Swift实际开发中,委托设计模式的实现和协议密不可分。

基于protocol的「委托设计模式」太常见了,这里就赘述了,若以后碰到新知识点再补充吧!

协议和扩展

扩展中让已知类型遵循新协议

Swift扩展中已经提到,可以在「扩展」中让某个已存在的类型遵循某个新的协议。这样,哪怕我们没有权限访问该类型的源代码,也可以在「扩展」中为该类型添加新属性、方法、下标。

举个栗子,在上文骰子类Dice的基础上,定义一个新的协议TextRepresentable,并定义一个Dice扩展,使得Dice遵循该协议,如下:

protocol TextRepresentable {
func asText() -> String
} extension Dice: TextRepresentable {
func asText() -> String {
return "A \(self.sides)-sided dice"
}
}

在扩展中添加协议成员

有的时候,某个类型从各个方面都符合某个协议的要求,只是没有明确说明它遵循该协议,可以在extension中指明,下面的结构体Hamster(仓鼠)完全遵循上面定义的TextRepresentable协议(该协议只声明了一个方法asText() -> String),但没有显式指明遵循该协议,通过extension可以处理一下:

struct Hamster {
var name: String
func asText() -> String {
return "A hamster named \(name)"
}
} extension Hamster: TextRepresentable {}

注意:虽然可以在extension中让某个已知类型遵循某个协议,但是对协议是比较挑剔的;必须保证该协议中没有定义「存储型属性」。

Protocol的继承

协议能够继承一到多个其他协议,其语法与类的继承相似,多个协议间用逗号,分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}

Class-Only Protocol

在Swift中还可以限制某个protocol仅能被类类型遵循,在定义「协议」的时候加上关键字class即可,如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// class-only protocol definition goes here
}

协议合成

一个协议可由多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为「协议合成」(protocol composition)。

「协议合成」经常在「协议」作为类型(譬如参数类型)时会使用到,举个栗子:

protocol Named {
var name: String { get }
} protocol Aged {
var age: String { 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: "张不坏", age: )
wishHappyBirthday(birthdayPerson)
// print "Happy birthday 张不坏 - you're 23!"

注意:协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。

检验协议的一致性

使用关键字is检验协议一致性,使用关键字as将协议类型向下转型(downcast)为其他协议类型:

  • is操作符用来判断某个实例是否遵循某个协议;
  • as?返回一个optional,当被检查的实例遵循某个协议时,返回该协议类型,否则返回nil;
  • as!强制转换,若成功,则返回协议类型,否则,抛出runtime错误;

协议的可选要求

OC中协议定义的属性和变量有required和optional(这和可选类型optional不是一个意思哦)之分,required类型资源(属性或者方法),「遵循者」必须要实现;optional类型资源,「遵循者」可选择性实现。

Swift也不例外,在Swift的「协议」中,它所提出的要求也可以是optional,具体的做法是在定义属性/方法/下标/构造器时加上关键字optional修饰即可。

Swift文档把这种在「协议」中声明的可选要求(属性、方法、下标等)称之为「optional protocol requirements」。

根据我的理解,Swift的设计理念是没有「optional protocol requirements」的,但是为了保持与OC兼容性,不得已支持;所以在Swift的「协议」中定义「optional protocol requirements」的前提是该协议被@objc修饰,关于@objc

  • @objc指示该协议暴露给OC,即可以为OC代码所用;
  • @objc修饰的协议仅仅可以被类类型遵循;

举个栗子,定义一个类Counter,它为「外部数据源」提供加法操作,这个「外部数据源」必须要遵循一个协议CounterDataSource,如下:

@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
} @objc class Counter {
var count =
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}

定义类ThreeSource,该类遵循协议CounterDataSource,并实现了其中一个「optional protocol requirement」 — 属性fixedIncrement

@objc class ThreeSource: CounterDataSource {
let fixedIncrement =
} var counter = Counter()
counter.dataSource = ThreeSource()
for _ in ... {
counter.increment()
println("\(counter.count) ")
}
// print:
// 3 6 9 12

作为对比,如下定义类TowardsZeroSource,该类也遵循协议CounterDataSource,只是实现了另一个「optional protocol requirement」 — 方法incrementForCount

class TowardsZeroSource: CounterDataSource {
func incrementForCount(count: Int) -> Int {
if count == {
return
} else if count < {
return
} else {
return -
}
}
} var counter = Counter()
counter.count = -
counter.dataSource = TowardsZeroSource()
for _ in ... {
counter.increment()
print("\(counter.count) ")
}
// print:
// -3 -2 -1 0 0

Swift协议的更多相关文章

  1. 6.Swift协议|扩展|访问权限|异常调试|类型转换|运算函数|ARC|类类型初试化器|值类型初始化器

    1. 协议(Protocol):与OC之间唯一不同的是Swift中的协议不管是属性还时方法全部是必须实现的 /** protocol*/ protocol FullNamed { /** 计算属性申明 ...

  2. Swift协议(Protocol)

    协议是为方法.属性等定义一套规范,没有具体的实现. 协议能够被类.结构体等具体实现(或遵守). protocol SomeProtocol { // protocoldefinition goes h ...

  3. 学习Swift -- 协议(上)

    协议(上) 协议是Swift非常重要的部分,协议规定了用来实现某一特定工作或者功能所必需的方法和属性.类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能.任意能够满足协议要求 ...

  4. Swift - 协议(protocol)

    1,Swift中协议类似于别的语言里的接口,协议里只做方法的声明,包括方法名.返回值.参数等信息,而没有具体的方法实现. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...

  5. swift 协议传值的实现

    首先呢说下结构 一个ViewController 一个ModelViewController 在ModelViewController中定义了一个协议 这个逻辑 从第一个界面进入第二个界面  从第二个 ...

  6. Swift协议+代理

    Swift语言开发中使用协议+代理的用法和oc中是一样的,只不过变得是语法.现在就进入swift的协议+代理. 先上个图,看看我们要实现的效果:  首先是第一个页面,然后点击到第二个页面,最后点击返回 ...

  7. Swift oc 混编 - oc导入Swift协议

    (默认已经设置好桥接头文件)1.在Swift文件中写好协议2.oc类文件中导入:"项目名-swift.h"格式的文件 即:#include "项目名-swift.h&qu ...

  8. Swift 协议

    /// 一般情况下,定义的协议都必须实现 protocol SomeProtocal { func doSomething() } /// 定义一个类,并且遵守协议 class Teacher:Som ...

  9. 学习Swift -- 协议(下)

    协议(下) 在拓展中添加协议成员 通过扩展使得Dice类型遵循了一个新的协议,这和Dice类型在定义的时候声明为遵循TextRepresentable协议的效果相同.在扩展的时候,协议名称写在类型名之 ...

随机推荐

  1. LVS-DR,real-server为windows 2008的配置

    LVS-DR,real-server为windows 2008的配置 部署邮件系统负载均衡,采用LVS-DR模式,调度器是一台centos 5.8,real-server是两台windows2008, ...

  2. android 怎样加速./mk snod打包

    mm命令高速编译一个模块之后,一般用adb push到手机看效果,假设环境不同意用adb push或模块不常常改.希望直接放到image里,则能够用./mk snod,这个命令只将system文件夹打 ...

  3. CPU调度算法

    批处理系统中的调度算法: *需要考虑的因素: 1. 吞吐量 2. cpu利用率 3. 周转时间 4. 公平性* 1.先来先服务: FCFS: 优点:实现简单 缺点:可能造成周转时间长 2.最短作业优先 ...

  4. Linux系统初始化流程

    POST-->BIOS(Boot Sequence)-->MBR(bootloader)-->Kernel(initrd)-->/sbin/init(/etc/inittab) ...

  5. python 基础 1.5 python数据类型(四)--字典

    一.python 数据类型--字典 1.用字符串存储信息,如:存储“姓名,身高,性别”: In [1]: info='Tom 170 M' //字符串存储信息 In [3]: info[0:3] // ...

  6. maven scope runtime

    https://blog.csdn.net/ningbohezhijunbl/article/details/25818069 There are 6 scopes available: compil ...

  7. 【BZOJ4238】电压 DFS树

    [BZOJ4238]电压 Description 你知道Just Odd Inventions社吗?这个公司的业务是“只不过是奇妙的发明(Just Odd Inventions)”.这里简称为JOI社 ...

  8. 基于live555实现的RTSPServer对底层进行性能优化的方法

    在博客<EasyIPCamera高性能摄像机RTSP服务器RTSPServer解决方案>我介绍了基于live555实现的一套RTSPServer功能组件,当时开发者经过几个月的调试,已经将 ...

  9. JS深入理解系列(一):编写高质量代码

    在for循环中,你可以循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象.通常的循环形式如下: // 次佳的循环for (var i = 0; i < m ...

  10. PECL的安装和使用

    下载并安装pear脚本 cd /usr/local/php/bin/ curl -o go-pear.php http://pear.php.net/go-pear.phar ./php go-pea ...