您的位置:首页 > 移动开发 > Swift

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

  1. 协议中还可以定义初始化器
    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

  1. 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加上
    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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: