协议在Swift中极其重要,可以说任何项目开发都会用到协议。
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)。
示例代码:
// 定义协议
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
定义类型方法,类型属性、类型下标。
示例代码:
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")
}
}
只有将协议中的实例方法标记为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
,否则报错。
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) { }
}
注意:在继承关系中,可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的。但是实现协议初始化器时,可以用且只能用隐式解包的可失败初始化器。
一个协议可以继承其他协议(也可以说是遵守)。
示例代码:
protocol Runnable {
func run()
}
protocol Livable: Runnable {
func breath()
}
class Person: Livable {
func breath() {
}
func run() {
}
}
&
连接示例代码:
// 定义协议和类
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) { }
让枚举遵守CaseIterable
协议,可以实现遍历枚举值。
示例代码:
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases
print(type(of: seasons))
// 输出:Array
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]
遵守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