1. 协议的概念:
1) 其实就是Java中的接口,Swift的遵守协议就是Java中的实现接口,如果放在C++中就是纯虚类的概念,即协议就是一种高度抽象的抽象类,里面值规定了方法、属性等内容,但没有提供任何实现,所有遵守该协议的类、结构体或枚举都必须实现协议中规定的内容,只不过C++没有接口而只能通过继承虚基类来实现其中的内容而已;
2) 协议是面向接口编程的必不可少的机制,其要求定义与实现分离,在Java和Swift中都可以将协议作为数据类型(就和其它普通的数据类型Int、Array等)暴露给使用者,而使用者不用关心具体的实现细节;
其实这也是通过协议实现多态的一种方式,即用一个协议类型的引用指向实现该协议的实例,通过该引用调用协议中要求的方法等便能实现多态,并且可以使用is、as等操作符进行向下转换!
3) 协议的定义和遵守:
protocol 协议名 { // 协议的定义 协议的内容 } 类型 类型名: 协议1, 协议2... { // 遵守协议 实现协议中的内容 } 类类型 类类型名: 父类名, 协议1, 协议2... { // 类在继承的同时遵守协议 相关内容 }可见比Java接口的语法还要简洁很多!
4) 协议可以作用的对象:包括类、结构体和枚举;
5) 协议中可以定义的内容:计算属性(实例/静态)、方法(实例/静态)和下标;
2. 协议属性:
1) 只能在协议中定义计算属性,但是遵守协议的时候却可以实现成存储属性!这是因为只要实现协议要求的就行了,也就是说协议只规定遵守者必须做的事却并没有规定遵守者不能做的事,请看下例:
protocol A { var a: Int { get set } // 定义一个可读可写的计算属性 class var b: Int { get } // 不管遵守者是引用型的还是值类型的静态属性都是用class修饰! } class B: A { var a = 20 // 虽然a不是计算属性,但a同样是可读可写的!因此没问题! class var b: Int { get { return 10 } set { // 虽然协议规定b是只读的,但只要遵守实现只读就行了,可写并没有规定,因此可以定义! println("haha") } } } enum C: A { var a: Int { // 由于枚举不能定义实例存储属性,所以必须实现getter和setter get { return 10 } set { println("haha") } } static var b: Int { // 结构体和枚举实现静态属性要使用static!不管协议中使用class定义的! return 99 } }由上例可见,协议中只能提供抽象接口而不能定义这些功能的具体实现,具体实现由遵守协议者实现,并且协议中所有内容都必须实现,少一个都会报错!
为体现抽象性,只写get或set,两者都有则中间用空格隔开即可,这就表示遵守者必须实现getter或setter;
!!注意,不管是协议属性还是协议方法,在协议中静态的都是用class来修饰,而遵守者根据自己是引用类型还是值类型来区分自己使用的关键字,如果是引用类型还是使用class来修饰,如果是值类型就使用static来修饰!
2) 由此可见,虽然协议中只能规定计算属性,但是遵守者可以根据协议中计算属性的意义来定义存储属性,这样遵守者存储属性和计算属性都能定义,协议的功能就完备了!
3. 协议方法(包括下标):
1) 目前协议方法还支持变长参数,但不支持默认值参数!
2) 具体实现(包括下标属性,其实也是实现其getter和setter):
protocol A { class func f1(number n: Int, varList s: String...) class func f2() func f3(Name s: String) -> String func f4(a: Int, b: Int) -> Int subscript(index: Int) -> Int { get set } } class B: A { // 类用class class func f1(number n: Int, varList s: String...) { for i in 0..<n { println(s[0]) } } class func f2() { println("haha") } func f3(Name s: String) -> String { return s } func f4(a: Int, b: Int) -> Int { return a + b } subscript(index: Int) -> Int { get { return 99 } set { println("haha") } } } struct C: A { // 结构体用static static func f1(number n: Int, varList s: String...) { for i in 0..<n { println(s[0]) } } static func f2() { println("haha") } func f3(Name s: String) -> String { return s } func f4(a: Int, b: Int) -> Int { return a + b } subscript(index: Int) -> Int { get { return 99 } set { println("haha") } } }3) 关于变异的协议方法:由于结构体和枚举有时需要变异方法,协议也能提供变异功能,但变异只对结构体和枚举有效,因此如果在协议中规定变异方法,而该变异方法只对结构体和枚举有效,类类型实现它是可以不加mutating修饰,因为类本身就是可以修改自身数据的,因此这也算遵守协议!
protocol A { // 因此协议中的mutating仅仅是为结构体和枚举而准备的 mutating func change() // 在协议中需要用mutating声明变异 } struct B: A { var a = 20 mutating func change() { // 在结构体中必须用mutating修饰,以为这是专门为值类型准备的!不加会编译报错! a = 70 } } class C: A { var a = 20 func change() { // 对于类不需要mutating修饰,因为其本身也可以修饰,因此还是遵守了协议! a = 20 } } enum D: A { case X, Y, Z mutating func change() { // 枚举和结构体情况一样 self = .Z } }
1) 将协议作为类型使用可以实现多态,和Java的接口用法一样,协议由于没有实现部分因此无法实例化,但它最大的作用就是用协议类型的引用指向遵守协议的实例从而实现多态!这也是之前讲过的面向接口编程的核心,将实现和定义分离,提高安全性、扩展性和可复用性;
2) 由于它和其它任何类型一样,因此也协议名需要首字母大写的驼峰,同时该类型也可以作为函数参数类型、返回值类型等来使用;
3) 一个简单的多态的例子:
protocol A { func show() } class B: A { var a = 10 func show() { println("B") } } class C: A { var b = 20 func show() { println("C") } } var list: [A] = [B(), C(), B(), B(), C(), C(), C(), B()] for item in list { item.show() } // B C B B C C C B for item in list { if let obj_b = item as? B { println(obj_b.a) } else if let obj_c = item as? C { println(obj_c.b) } } // 10 20 10 10 20 20 20 10
5. 委托:
1) 是一种利用协议类型的一种设计模式;
2) 有时某个类型需要使用某种协议中规定的功能,但如果直接遵守该协议,可能会因为协议中还有其它大量自己用不到的功能而烦恼,因为那些大量功能不会用到并且还必须都得实现,很多情况下这些不实现的功能会给一个空的定义体,但是这种行为会非常浪费开发资源和时间,因此就出现了一种设计模式,那就是不直接遵守该协议,而是让遵守了该协议的其它类的实例来代替它完成相应的工作!
3) 请看下面的例子:
protocol Fix { func FixWheel() func FixPannel() func FixEngine() } class Fixer: Fix { // 修理工具有所有的修理技巧 func FixWheel() { println("fix wheel") } func FixPannel() { println("fix pannel") } func FixEngine() { println("fix engine") } } class CarRacer { // 但是作为赛车手就只要会修轮子和引擎就行了,其余不需要 let fix_delegate: Fix // 这是就可以将这两个技能委托给专业维修工来实现 // 其实只要请那些遵守维修协议的任何一个人都能替他完成这两个任务 init(fix_delegate: Fix) { self.fix_delegate = fix_delegate } // 这里做了两个适配器 func FixEngine() { fix_delegate.FixEngine() } func FixWheel() { fix_delegate.FixWheel() } } var racer = CarRacer(fix_delegate: Fixer()) // 可以使用适配器 racer.FixWheel() racer.FixEngine() // 也完全可以交由委托人来完成 racer.fix_delegate.FixWheel() racer.fix_delegate.FixEngine()
6. 协议的继承:
1) 和Java的接口继承的使用方法几乎一模一样;
2) 可以从一个协议继承得到一个子协议,由于没有构造器等,因此继承仅仅就是获得父类(祖先)的全部规定的内容而已,因此可以看做是协议中的extension,它的作用仅仅就是:协议和 = 协议1 + 协议2 + 协议3...
定义如下:协议继承的语法和类继承的语法一样
protocol A { var a: Int { get } } protocol B: A { var b: Int { get } } protocol C { var c: Int { get } } protocol D: B, C { var d: Int { get } } class K: D { // 就是所有协议的和 var a = 1 var b = 2 var c = 3 var d = 4 }
7. 合成协议:
1) 这个问题主要是基于协议类型的,通常定义一个协议类型的引用的时候都只能用一个协议名,比如var prot: ProtocolName,也就是说只能基于一个协议建立一个协议类型的引用,但是有时需要一个引用能指向一个同时遵守多个协议的实例,比如对于class A: Prot1, Prot2, Port3的实例就无法找出一个协议类型的引用可以指向它;
2) Swift提供了合成协议的解决方案,可以临时定义一种协议类型的引用来指向同时遵守多个协议的实例,这种协议类型即为合成协议,语法为protocol<协议1, 协议2...>,但这种临时定义的类型是具有作用域的,它并没有产生一种新的类型(即和class 类型名 { })定义的类型不一样,过了作用域就失效了!基本上就是一种即用即定义的用法;
3) 一个简单的示例:
protocol Gun { func shoot() } protocol Radar { func scan() } protocol Ship { func go() } class WarShip: Gun, Radar, Ship { func shoot() { println("发射") } func scan() { println("扫描") } func go() { println("开动") } } var warship: protocol<Gun, Radar, Ship> = WarShip() // 合成协议类型 warship.shoot()
8. 可选协议:
1) 可选协议是指含有可选成员的协议,即如果一个类遵守该协议,则这些可选成员无需实现;
2) 可选协议的定义方法就是使用关键字@objc,该标记既表示该协议可选的外也表示该协议暴露给Objective-C代码(这在以后会讲),而在定义可选成员的时候需要使用关键字optional;
3) 注意!只有类类型才能遵守可选协议,结构体和枚举类型不能遵守可选协议;
4) 由于一个对象是否实现了协议中的成员往往是未知的,因此在访问这些成员的时候就需要使用可选链,对于成员属性只要在末尾加?即可,但是对于成员方法则需要在方法名后加?,同时该?也位于参数括号前;
5) 请看一下示例:
@objc protocol A { // 使用optional关键字定义可选成员 optional func f() -> String? optional var a: Int { get } } class B: A { func f() -> String? { // 覆盖时无需加optional关键字 return "haha" } } var pa: A = B() // 如果是用可选协议类型的引用指向一个实例,则访问其可选成员的时候需要加? // 但是如果是类本身的引用指向该类的实例,则访问协议中的可选成员无需加? // 因为对于一个类来说,该类中实现了那些方法和属性都是一目了然的! // 但是对于一个协议类型的引用,由于协议本身就隐藏了具体的实现 // 所以不知道遵守该协议的对象是否真正实现了那些成员,因此需要使用可选链来访问 // 如果是var b = B()那么访问的时候就必须是if let str = b.f()而不能是b.f?() // 因为在B中f()是被清清楚楚实现的! if let str = pa.f?() { println(str) } if let value = pa.a? { println("haha") } // 没有输出
9. 用is和as校验一个对象是否遵守了某个协议:
目前这种校验方法只适用于用@objc声明的可选协议,因此只适用于类而不适用于结构体和枚举类型
@objc protocol A { } @objc protocol B { } class C: A { } class D: B { } var arr = [C(), D(), C(), D(), 32.2, "hahah", 77] for item in arr { if item is A { // 检查一个对象是否遵守了某个协议 println("A") } else if let b = item as? B { // 将某个对象的类型转化成某个协议类型 println("B") } } // A B A B