Swift系列十六 - 协议
2021-04-29 09:29
901 查看
协议在Swift中极其重要,可以说任何项目开发都会用到协议。
一、协议(Protocol)的定义
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)。
示例代码:
// 定义协议 protocol Drawable { var x: Int { get set } var y: Int { get } func draw() subscript(index: Int) -> Int { get set } }protocol Test1 { } protocol Test2 { } protocol Test3 { } // 遵守协议 class TestClass: Test1, Test2, Test3, Drawable { // do something... }
特点:
- 协议中定义方法时不能有默认参数值;
- 默认情况下,协议中定义的内容必须全部都实现。
二、协议中的属性
协议中定义属性时必须用
var关键字。
示例代码:
protocol Drawable { var x: Int { get set } var y: Int { get } func draw() subscript(index: Int) -> Int { get set } }
上面示例代码中协议属性
x和
y并不是计算属性的意思,它的意思仅仅是表达该属性是具备可读/可写/可读写功能。
协议属性的实现:
实现协议时的属性权限要【不小于】协议中定义的属性权限:
- 协议定义
get
、set
,用var存储属性 或
、get
set
计算属性去实现; - 协议定义
get
,用任何属性都可以实现。
场景一:
class Person: Drawable { // 可读写的存储属性 var x: Int = 0 // 只读的存储属性 let y: Int = 0 func draw() { print("Person draw") } subscript(index: Int) -> Int { set { } get { index } } }
场景二:
class Person: Drawable { // 可读写的计算属性 var x: Int { set { } get { 0 } } // 只读的计算属性 var y: Int { 0 } func draw() { print("Person draw") } subscript(index: Int) -> Int { set { } get { index } } }
三、static、class、mutating、init在协议中的使用
3.1. static、class
为了保证通用,协议中必须用
static定义类型方法,类型属性、类型下标。
示例代码:
protocol Drawable { static func draw() }
实现协议的时候既可以使用
static,也可以使用
class,取决于子类是否需要重写。
class Person1: Drawable { static func draw() { print("Person1 draw") } } class Person2: Drawable { class func draw() { print("Person2 draw") } }
3.2. mutating
只有将协议中的实例方法标记为
mutating,才允许结构体、枚举的具体实现修改自身内存。
类在实现方法时不加
mutating(加了会报错),枚举、结构体才需要加
mutating(不加会报错)。
示例代码:
protocol Drawable { mutating func draw() } class Size: Drawable { var width: Int = 0 func draw() { width = 10 } } struct Point: Drawable { var x: Int = 0 mutating func draw() { x = 10 } }
如果协议中没有加
mutating,不影响class修改属性值。但是值类型实现协议函数时也不能加mutating,否则报错。
3.3. init
- 协议中还可以定义初始化器
init
,非final
类实现时必须加上required
。
示例代码:
protocol Drawable { init(x: Int, y: Int) } class Point: Drawable { required init(x: Int, y: Int) { } } final class Size: Drawable { init(x: Int, y: Int) { } }
思考:为什么协议限制非
final类必须加上required?因为协议肯定希望定义的初始化器被遵守协议的类及其子类都能实现,所以需要加上required,遵守协议的子类也必须实现该协议。但是加上final的类是不能被继承的,所以也就没必要加required。
- 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加上
required
、override
。
示例代码:
protocol Animal { init(age: Int) } class Person { init(age: Int) { } } class Student: Person, Animal { required override init(age: Int) { super.init(age: age) } }
注意:子类重写父类的
required指定初始化器,子类不用加override,和是否遵守协议无关。上面的示例中required代表遵守协议,override代表重写父类。
init、init?、init!的使用:
- 协议中定义的
init?
、init!
,可以用init
、init?
、init!
去实现; - 协议中定义的
init
,可以用init
、init!
去实现。
示例代码:
protocol Animal { init() init?(age: Int) init!(height: Int) } class Person: Animal { required init() { } // required init!() { } required init?(age: Int) { } // required init!(age: Int) { } // required init(age: Int) { } required init!(height: Int) { } // required init?(height: Int) { } // required init(height: Int) { } }
注意:在继承关系中,可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的。但是实现协议初始化器时,可以用且只能用隐式解包的可失败初始化器。
四、协议的继承和组合
4.1. 协议继承
一个协议可以继承其他协议(也可以说是遵守)。
示例代码:
protocol Runnable { func run() } protocol Livable: Runnable { func breath() } class Person: Livable { func breath() { } func run() { } }
4.2. 协议组合
- 协议组合最多可以包含1个类类型
- 多个协议和类类型用
&
连接
示例代码:
// 定义协议和类 protocol Runnable { } protocol Livable: Runnable { } class Person: Livable { } // 接收Person或者其子类的实例 func fn0(obj: Person) { } // 接收遵守Livable协议的实例 func fn1(obj: Livable) { } // 接收同时遵守Livable、Runnable协议的实例 func fn2(obj: Livable & Runnable) { } // 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例 func fn3(obj: Person & Livable & Runnable) { }
上面示例代码中
fn2和
fn3就是协议组合。
fn3还可以使用下面的方式:
typealias RealPerson = Person & Livable & Runnable func fn4(obj: RealPerson) { }
五、常用协议
5.1. CaseIterable协议(枚举迭代器)
让枚举遵守
CaseIterable协议,可以实现遍历枚举值。
示例代码:
enum Season: CaseIterable { case spring, summer, autumn, winter } let seasons = Season.allCases print(type(of: seasons)) // 输出:Array<Season> print(seasons.count) // 输出:4 for season in seasons { print(season) } /* 输出: spring summer autumn winter */
CaseIterable提供了一个
allCases的类型属性,返回一个数组,数组包含了枚举的所有值。
扩展:
let seasons = Season.allCases等价于let seasons = [Season.spring, Season.summer, Season.autumn, Season.winter]
5.2. CustomStringConvertible/CustomDebugStringConvertible
遵守
CustomStringConvertible、
CustomDebugStringConvertible协议,都可以自定义实例的打印字符串。
示例代码:
class Person: CustomStringConvertible, CustomDebugStringConvertible { var age = 0 var description: String { "person_\(age)" } var debugDescription: String { "debug_person_\(age)" } } var p = Person() print(p) // 输出:person_0 debugPrint(p) // 输出:debug_person_0
print
调用的是CustomStringConvertible
协议的description
debugPrint
、po
调用的是CustomDebugStringConvertible
协议的debugDescription
相关文章推荐
- Swift学习笔记系列——(21)协议
- Swift学习笔记十六:协议
- Swift学习第七枪--协议(一)
- swift 面向协议学习思考
- iOS开发系列--Objective-C之协议、代码块、分类
- [C# 基础知识系列]专题十六:Linq介绍
- Swift学习之协议和代理
- 数学之美 系列十六(上) 不要把所有的鸡蛋放在一个篮子里 -- 谈谈最大熵模型
- [C#]网络编程系列专题二:HTTP协议详解
- Swift中的可选协议和方法的历史渊源
- C# 串口操作系列(3) -- 协议篇,二进制协议数据解析
- jQuery-1.9.1源码分析系列(十六)ajax——jsonp原理
- 敏捷开发一千零一问系列之十六:如何让开发人员学习产品?
- Java系列(十六)__Java常用类库(2)
- Swift学习:2.21 协议
- iOS开发系列--Objective-C之协议、代码块、分类
- WWCD 2015: Swift 里的Value Type 和面向协议
- Enterprise Library Step By Step系列(十六):使用AppSetting Application Block
- Spring Boot2 系列教程(十六)定时任务的两种实现方式
- ASP.NET Web Services 系列(4) - 支持协议HTTP POST,HTTP GET,SOAP