协议 可以理解为约定,这个概念在现代生活中处处可见,比如插座和电源接头,生产插座和电源接头的厂家千千万,但是无论买哪一家的,它们都能对上接口,因为它们都是按照统一的约定生产的,如果你在美国买一个电源接口拿回国用,就不能直接用,因为美国的标准跟国内不一样
再比如U盘,生产U盘的厂家千千万,生产电脑的厂家也千千万,但是它们的USB接口永远都是一样的,只要生产电脑的厂家和生产U盘的厂家都按照统一标准来生产,那么我随便买一个U盘都能用
电脑本身也是由各种厂家提供的芯片组合起来成为一台完整的计算机,比如说内存条,生产内存条的厂家必须按照统一标准去生产,生产主板的厂家也必须按照统一标准来提供内存条插槽,这样我们就可以想换内存条就换内存条,只要买一个插上就行了,这样不仅对用户有好处,对于整个社会的生产也起到了至关重要的作用:合作分工
回到编程中,协议就是一种约定,标准,一个 类型 如果要遵循某个协议,就必须提供协议要求的东西,下面我们用USB来做一个示范
protocol USB {
var width: Double { get } //接口有多宽
var height: Double { get } //接口由多高
var numberOfWires: Int { get } //有几根线
func readData() -> String //读取数据
func writeData(_ data: String) //写入数据
}
这就是一个协议的定义,其实就是规定要有哪些属性,哪些方法,这些属性和函数都只有一个形式,并没有具体的实现,也就是说协议只提供标准,实现部分需要遵循该协议的类型自己实现。下面我们来定义一个U盘的类型,这个U盘类型遵循 USB 协议
class UDisk: USB {
private var data = "" //假设这是U盘存储的数据
var width: Double = 40
var height: Double = 120
var numberOfWires: Int = 4
func readData() -> String {
print("读取U盘的数据")
return data
}
func writeData(_ data: String) {
print("写入新的数据")
self.data.append(data)
}
}
我们定义了一个 UDisk 的类,他跟我们之前定义类的方式完全一样,没有任何不同,只不过这个类里面必须有 USB 协议所要求的东西。顺便提一下,类名称的冒号后面跟的不一定是父类,也可能是协议,所以看到冒号不一定表示继承,要看冒号后面跟的是类还是协议,如果一个类既有继承又要遵循协议,那么继承写在最前面,协议跟在后面,并用逗号分隔开,一个类只能继承自一个类,但是可以遵循多个协议
再定义一个 USB 插孔类
class USBSocket {
private var usbDevice: USB? //插入这个USB接口的设备
func insert(usbDevice: USB) { //insert 函数用来插入USB设备
if usbDevice.width != 120 || usbDevice.height != 40 {
//如果usb设备的长宽不符合标准,函数结束,表示这个设备接不上
return
}
//长宽符合标准,设备成功接上
self.usbDevice = usbDevice
}
func readDataFromDevice() -> String? {
if let device = usbDevice { //首先要有设备接入才能读取数据
if device.numberOfWires == 4 { //usb设备必须是4根线的标准才能读取数据
return device.readData()
}
}
return nil
}
func writeDataToDevice(_ data: String) {
if let device = usbDevice { //首先要有设备接入才能写入数据
if device.numberOfWires == 4 { //usb设备必须是4根线的标准才能写入数据
device.writeData(data)
}
}
}
}
func lessonRun() {
let uDisk = UDisk()
let socket = USBSocket()
socket.insert(usbDevice: uDisk)
socket.writeDataToDevice("new data")
socket.readDataFromDevice()
}
在 USBSocket 类中,insert(usbDevice:) 方法的参数是一个 USB 类型,这就是协议强大的地方,如果用协议作为类型,不是指协议本身,协议本身是不能创建实例的,指的是所有遵循该协议的类型,在 lessonRun 函数中,我们创建了一个 UDisk 类型的实例并作为参数传递给 USBSocket 类型的实例,顺便提一句,类的继承也有同样的特性,如果某个函数的参数是 Person 类型,那么我们也可以传递 Teacher 类型的实例,因为 Teacher 也是一个 Person ,但是反过来就不行了
有了这样的基础,我们就可以定义移动硬盘了,USB接口并不需要关心数据的读写是如何实现,USB接口只需要知道接入的设备是遵循 USB 协议的,而协议中规定的数据的读写就有设备本身去实现了
协议中的计算属性
协议中的计算属性可以有不同的实现方式,因为协议只是用来做约定的,对于计算属性,{ get } 表示只要能获取到这个属性就行,不管它是存储属性还是计算属性都满足要求,而 { get set } 表示这个属性必须可读可写,那么就必须使用存储属性或者读写计算属性
protocol USB {
var width: Double { get } //接口有多宽
var height: Double { get } //接口由多高
var numberOfWires: Int { get set } //有几根线
func readData() -> String //读取数据
func writeData(_ data: String) //写入数据
}
class UDisk: USB {
private var data = "" //假设这是U盘存储的数据
var width: Double: {
return 40
}
var height: Double = 120
var numberOfWires: Int = 4
func readData() -> String {
print("读取U盘的数据")
return data
}
func writeData(_ data: String) {
print("写入新的数据")
self.data.append(data)
}
}
通过扩展实现协议
我们已经学过了 类型扩展 ,它能为类型新增功能,并且可以与协议结合起来使用,我们可以这样定义 UDisk 类
class UDisk {
private var data = "" //假设这是U盘存储的数据
var width: Double: {
return 40
}
var height: Double = 120
var numberOfWires: Int = 4
}
extension UDisk: USB {
func readData() -> String {
print("读取U盘的数据")
return data
}
func writeData(_ data: String) {
print("写入新的数据")
self.data.append(data)
}
}
通过扩展来让 UDisk 类遵循 USB 协议,而协议中的约定可以分在不同的扩展中实现,只要最终整个 UDisk 类满足协议就行了
通过扩展提供默认实现
像接口宽高和总线数这些属性基本上是固定不变的,我们可以为其提供默认的实现从而不必每次定义遵循该协议的类的时候都去重复写一遍
protocol USB {
var width: Double { get } //接口有多宽
var height: Double { get } //接口由多高
var numberOfWires: Int { get } //有几根线
func readData() -> String //读取数据
func writeData(_ data: String) //写入数据
}
extension USB {
var width: Double {
return 40
}
var height: Double {
return 120
}
var numberOfWires: Int {
return 4
}
}
class UDisk: USB {
private var data = "" //假设这是U盘存储的数据
//提供了默认实现的部分可以省略不写,也可以不使用默认实现自己实现
func readData() -> String {
print("读取U盘的数据")
return data
}
func writeData(_ data: String) {
print("写入新的数据")
self.data.append(data)
}
}
协议的继承
协议的继承跟类的继承不一样,因为协议只是提供标准,因此可以继承多个协议,现在我们定义一个USB2.0的协议,USB2.0提供批量写入数据
protocol USB {
var width: Double { get } //接口有多宽
var height: Double { get } //接口由多高
var numberOfWires: Int { get } //有几根线
func readData() -> String //读取数据
func writeData(_ data: String) //写入数据
}
protocol USB2_0: USB {
func writeDatas(_ datas: [String]) //批量写入数据
}
代理模式
协议还有一个重要的作用就是代理模式,举个例子,有钱人家会请保姆来帮忙打理家务,保姆就是代理我们做家务的对象,什么样的人才能做保姆呢,当然是遵循了 保姆 协议的人了,通过协议来定义保姆需要有哪些技能
protocol HousemaidDelegate: class {
var host: Person? { get set }
func washClothes()
func cleanHonse()
func cooking()
func takeCareOfBaby()
}
class Housemaid: Person, HousemaidDelegate {
weak var host: Person?
func washClothes() {
if host != nil {
print("\(name) 给主人 \(host!.name) 洗衣服")
}
}
func cleanHonse() {
if host != nil {
print("\(name) 给主人 \(host!.name) 打扫房间")
}
}
func cooking() {
if host != nil {
print("\(name) 给主人 \(host!.name) 做饭")
}
}
func takeCareOfBaby() {
if host != nil {
print("\(name) 给主人 \(host!.name) 带孩子")
}
}
}
class Person {
let id: String
var name: String = ""
let hometown: String
var age: Int = 0
var gender: Gender = .unknown
/** 保姆代理*/
weak var housemaid: HousemaidDelegate? {
didSet {
housemaid?.host = self
}
}
/** 做家务,如果有保姆,就让保姆做家务,没有保姆就不做*/
func doHousework() {
if housemaid != nil {
housemaid!.washClothes()
housemaid!.cleanHonse()
housemaid!.cooking()
housemaid!.takeCareOfBaby()
} else {
print("\(name) 又穷又懒,不想做家务")
}
}
}
func lessonRun() {
let person = Person.init(id: "1234567890", hometown: "YiChang")
person.name = "ZhangQi"
person.age = 25
person.gender = .female
let housemaid = Housemaid.init(id: "42321312413", hometown: "GuangDong")
housemaid.name = "LiLi"
housemaid.age = 20
housemaid.gender = .female
//现在 person 有了一个代理保姆
person.housemaid = housemaid
person.doHousework()
let me = Person.init(id: "34513312331", hometown: "ChongQing")
me.name = "JiangMing"
me.age = 25
me.gender = .male
me.doHousework()
}
为什么不像 Teacher 一样直接定义一个保姆类,非要设计一个保姆协议来实现呢,这样做的好处就是,将特定的任务交给代理去完成,是现代社会高效率的一部分,使用代理模式就模拟了这一过程,同时具有更高的灵活性,试想一下,随着社会进步,保姆这样的工作可能就不仅仅是人类来做了,有可能是机器人来完成,那么我们只要让机器人遵循保姆协议,保姆机器人就可以用来为人类服务,而人类的代码不需要做任何改动
如果我们把 Person 的 housemaid 属性修改成 Housemaid 类型,那么将来新出现的保姆机器人要怎么用?就只能修改 Person 的代码了,当一个程序很复杂时,这样的修改可能会非常麻烦,因此合理的设计对以后的维护升级起着至关重要的作用
新手程序员可能需要适当花些时间来消化这部分内容,例子虽小,以小见大,这已经属于中级程序员的要求了,如有疑问,可以跟我一起讨论
弱引用
代码中出现了一个新的关键字 weak ,弱引用是为了避免循环引用造成的内存泄露,先来看现象,在运行程序后,我们能看到 “生命周期结束,被自动销毁” 的输出,表示对象使用完毕,正常释放,如果我们把代码中两处 weak 去掉再运行,就不再输出释放的信息,也就是说 deinit 析构函数没有执行,对象在使用完后没有释放,我们把这样的现象称为 内存泄露 ,如果内存泄露越来越多,计算机就会有越来越多的内存被占用无法释放,应用就有可能会被系统杀死,因此要避免内存泄露
普通的引用也叫强引用,在对象释放的时候,会先释放其强引用的属性,当两个对象互相强引用时,就形成了一个死环,谁都无法释放谁,造成内存泄露,这样的情况下就需要使用弱引用
枚举,结构体,这样的值类型不存在弱引用,因为它们不属于引用类型,它们的拷贝形式不会造成循环引用和内存泄露,枚举和结构体也都可以遵循协议,协议可以分普通协议和 类专属协议 ,为什么要有类专属协议,因为只有类专属协议才能使用弱引用,请注意我们的 HousemaidDelegate 协议定义后面的 class 表示其为类专属协议,因此这个协议可以使用弱引用,枚举和结构体是不能遵循该协议的
网友评论