協(xié)議

上一篇:類與結(jié)構(gòu)體
當(dāng)前篇:協(xié)議
下一篇:多線程

協(xié)議 可以理解為約定,這個概念在現(xiàn)代生活中處處可見,比如插座和電源接頭,生產(chǎn)插座和電源接頭的廠家千千萬,但是無論買哪一家的,它們都能對上接口,因為它們都是按照統(tǒng)一的約定生產(chǎn)的,如果你在美國買一個電源接口拿回國用,就不能直接用,因為美國的標(biāo)準(zhǔn)跟國內(nèi)不一樣

再比如U盤,生產(chǎn)U盤的廠家千千萬,生產(chǎn)電腦的廠家也千千萬,但是它們的USB接口永遠(yuǎn)都是一樣的,只要生產(chǎn)電腦的廠家和生產(chǎn)U盤的廠家都按照統(tǒng)一標(biāo)準(zhǔn)來生產(chǎn),那么我隨便買一個U盤都能用

電腦本身也是由各種廠家提供的芯片組合起來成為一臺完整的計算機(jī),比如說內(nèi)存條,生產(chǎn)內(nèi)存條的廠家必須按照統(tǒng)一標(biāo)準(zhǔn)去生產(chǎn),生產(chǎn)主板的廠家也必須按照統(tǒng)一標(biāo)準(zhǔn)來提供內(nèi)存條插槽,這樣我們就可以想換內(nèi)存條就換內(nèi)存條,只要買一個插上就行了,這樣不僅對用戶有好處,對于整個社會的生產(chǎn)也起到了至關(guān)重要的作用:合作分工

回到編程中,協(xié)議就是一種約定,標(biāo)準(zhǔn),一個 類型 如果要遵循某個協(xié)議,就必須提供協(xié)議要求的東西,下面我們用USB來做一個示范

protocol USB {
    
    var width: Double { get }       //接口有多寬
    var height: Double { get }      //接口由多高
    var numberOfWires: Int { get }  //有幾根線
    
    func readData() -> String       //讀取數(shù)據(jù)
    func writeData(_ data: String)  //寫入數(shù)據(jù)
    
}

這就是一個協(xié)議的定義,其實就是規(guī)定要有哪些屬性,哪些方法,這些屬性和函數(shù)都只有一個形式,并沒有具體的實現(xiàn),也就是說協(xié)議只提供標(biāo)準(zhǔn),實現(xiàn)部分需要遵循該協(xié)議的類型自己實現(xiàn)。下面我們來定義一個U盤的類型,這個U盤類型遵循 USB 協(xié)議


class UDisk: USB {
    
    private var data = ""   //假設(shè)這是U盤存儲的數(shù)據(jù)
    
    var width: Double = 40
    var height: Double = 120
    var numberOfWires: Int = 4
    
    func readData() -> String {
        print("讀取U盤的數(shù)據(jù)")
        return data
    }
    
    func writeData(_ data: String) {
        print("寫入新的數(shù)據(jù)")
        self.data.append(data)
    }
    
}

我們定義了一個 UDisk 的類,他跟我們之前定義類的方式完全一樣,沒有任何不同,只不過這個類里面必須有 USB 協(xié)議所要求的東西。順便提一下,類名稱的冒號后面跟的不一定是父類,也可能是協(xié)議,所以看到冒號不一定表示繼承,要看冒號后面跟的是類還是協(xié)議,如果一個類既有繼承又要遵循協(xié)議,那么繼承寫在最前面,協(xié)議跟在后面,并用逗號分隔開,一個類只能繼承自一個類,但是可以遵循多個協(xié)議

再定義一個 USB 插孔類

class USBSocket {
    
    private var usbDevice: USB?     //插入這個USB接口的設(shè)備
    
    func insert(usbDevice: USB) {   //insert 函數(shù)用來插入USB設(shè)備
        if usbDevice.width != 120 || usbDevice.height != 40 {
            //如果usb設(shè)備的長寬不符合標(biāo)準(zhǔn),函數(shù)結(jié)束,表示這個設(shè)備接不上
            return
        }
        //長寬符合標(biāo)準(zhǔn),設(shè)備成功接上
        self.usbDevice = usbDevice
    }
    
    func readDataFromDevice() -> String? {
        if let device = usbDevice { //首先要有設(shè)備接入才能讀取數(shù)據(jù)
            if device.numberOfWires == 4 { //usb設(shè)備必須是4根線的標(biāo)準(zhǔn)才能讀取數(shù)據(jù)
                return device.readData()
            }
        }
        return nil
    }
    
    func writeDataToDevice(_ data: String) {
        if let device = usbDevice { //首先要有設(shè)備接入才能寫入數(shù)據(jù)
            if device.numberOfWires == 4 { //usb設(shè)備必須是4根線的標(biāo)準(zhǔn)才能寫入數(shù)據(jù)
                device.writeData(data)
            }
        }
    }
    
}

func lessonRun() {
    let uDisk = UDisk()
    let socket = USBSocket()
    socket.insert(usbDevice: uDisk)
    socket.writeDataToDevice("new data")
    socket.readDataFromDevice()
}

在 USBSocket 類中,insert(usbDevice:) 方法的參數(shù)是一個 USB 類型,這就是協(xié)議強(qiáng)大的地方,如果用協(xié)議作為類型,不是指協(xié)議本身,協(xié)議本身是不能創(chuàng)建實例的,指的是所有遵循該協(xié)議的類型,在 lessonRun 函數(shù)中,我們創(chuàng)建了一個 UDisk 類型的實例并作為參數(shù)傳遞給 USBSocket 類型的實例,順便提一句,類的繼承也有同樣的特性,如果某個函數(shù)的參數(shù)是 Person 類型,那么我們也可以傳遞 Teacher 類型的實例,因為 Teacher 也是一個 Person ,但是反過來就不行了

有了這樣的基礎(chǔ),我們就可以定義移動硬盤了,USB接口并不需要關(guān)心數(shù)據(jù)的讀寫是如何實現(xiàn),USB接口只需要知道接入的設(shè)備是遵循 USB 協(xié)議的,而協(xié)議中規(guī)定的數(shù)據(jù)的讀寫就有設(shè)備本身去實現(xiàn)了

協(xié)議中的計算屬性

協(xié)議中的計算屬性可以有不同的實現(xiàn)方式,因為協(xié)議只是用來做約定的,對于計算屬性,{ get } 表示只要能獲取到這個屬性就行,不管它是存儲屬性還是計算屬性都滿足要求,而 { get set } 表示這個屬性必須可讀可寫,那么就必須使用存儲屬性或者讀寫計算屬性

protocol USB {
    
    var width: Double { get }       //接口有多寬
    var height: Double { get }      //接口由多高
    var numberOfWires: Int { get set }  //有幾根線
    
    func readData() -> String       //讀取數(shù)據(jù)
    func writeData(_ data: String)  //寫入數(shù)據(jù)
    
}
class UDisk: USB {
    
    private var data = ""   //假設(shè)這是U盤存儲的數(shù)據(jù)
    
    var width: Double: {
       return 40
    }
    var height: Double = 120
    var numberOfWires: Int = 4
    
    func readData() -> String {
        print("讀取U盤的數(shù)據(jù)")
        return data
    }
    
    func writeData(_ data: String) {
        print("寫入新的數(shù)據(jù)")
        self.data.append(data)
    }
    
}

通過擴(kuò)展實現(xiàn)協(xié)議

我們已經(jīng)學(xué)過了 類型擴(kuò)展 ,它能為類型新增功能,并且可以與協(xié)議結(jié)合起來使用,我們可以這樣定義 UDisk 類

class UDisk {
    
    private var data = ""   //假設(shè)這是U盤存儲的數(shù)據(jù)
    
    var width: Double: {
       return 40
    }
    var height: Double = 120
    var numberOfWires: Int = 4
    
}

extension UDisk: USB {

    func readData() -> String {
        print("讀取U盤的數(shù)據(jù)")
        return data
    }
    
    func writeData(_ data: String) {
        print("寫入新的數(shù)據(jù)")
        self.data.append(data)
    }

}

通過擴(kuò)展來讓 UDisk 類遵循 USB 協(xié)議,而協(xié)議中的約定可以分在不同的擴(kuò)展中實現(xiàn),只要最終整個 UDisk 類滿足協(xié)議就行了

通過擴(kuò)展提供默認(rèn)實現(xiàn)

像接口寬高和總線數(shù)這些屬性基本上是固定不變的,我們可以為其提供默認(rèn)的實現(xiàn)從而不必每次定義遵循該協(xié)議的類的時候都去重復(fù)寫一遍

protocol USB {
    
    var width: Double { get }       //接口有多寬
    var height: Double { get }      //接口由多高
    var numberOfWires: Int { get }  //有幾根線
    
    func readData() -> String       //讀取數(shù)據(jù)
    func writeData(_ data: String)  //寫入數(shù)據(jù)
    
}

extension USB {
    
    var width: Double {
        return 40
    }
    
    var height: Double {
        return 120
    }
    
    var numberOfWires: Int {
        return 4
    }
    
}
class UDisk: USB {
    
    private var data = ""   //假設(shè)這是U盤存儲的數(shù)據(jù)
    
    //提供了默認(rèn)實現(xiàn)的部分可以省略不寫,也可以不使用默認(rèn)實現(xiàn)自己實現(xiàn)

    func readData() -> String {
        print("讀取U盤的數(shù)據(jù)")
        return data
    }
    
    func writeData(_ data: String) {
        print("寫入新的數(shù)據(jù)")
        self.data.append(data)
    }
    
}

協(xié)議的繼承

協(xié)議的繼承跟類的繼承不一樣,因為協(xié)議只是提供標(biāo)準(zhǔn),因此可以繼承多個協(xié)議,現(xiàn)在我們定義一個USB2.0的協(xié)議,USB2.0提供批量寫入數(shù)據(jù)

protocol USB {
    
    var width: Double { get }       //接口有多寬
    var height: Double { get }      //接口由多高
    var numberOfWires: Int { get }  //有幾根線
    
    func readData() -> String       //讀取數(shù)據(jù)
    func writeData(_ data: String)  //寫入數(shù)據(jù)
    
}

protocol USB2_0: USB {
    
    func writeDatas(_ datas: [String])  //批量寫入數(shù)據(jù)
    
}

代理模式

協(xié)議還有一個重要的作用就是代理模式,舉個例子,有錢人家會請保姆來幫忙打理家務(wù),保姆就是代理我們做家務(wù)的對象,什么樣的人才能做保姆呢,當(dāng)然是遵循了 保姆 協(xié)議的人了,通過協(xié)議來定義保姆需要有哪些技能

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
        }
    }
    
    /** 做家務(wù),如果有保姆,就讓保姆做家務(wù),沒有保姆就不做*/
    func doHousework() {
        if housemaid != nil {
            housemaid!.washClothes()
            housemaid!.cleanHonse()
            housemaid!.cooking()
            housemaid!.takeCareOfBaby()
        } else {
            print("\(name) 又窮又懶,不想做家務(wù)")
        }
    }
}
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
    
    //現(xiàn)在 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 一樣直接定義一個保姆類,非要設(shè)計一個保姆協(xié)議來實現(xiàn)呢,這樣做的好處就是,將特定的任務(wù)交給代理去完成,是現(xiàn)代社會高效率的一部分,使用代理模式就模擬了這一過程,同時具有更高的靈活性,試想一下,隨著社會進(jìn)步,保姆這樣的工作可能就不僅僅是人類來做了,有可能是機(jī)器人來完成,那么我們只要讓機(jī)器人遵循保姆協(xié)議,保姆機(jī)器人就可以用來為人類服務(wù),而人類的代碼不需要做任何改動

如果我們把 Person 的 housemaid 屬性修改成 Housemaid 類型,那么將來新出現(xiàn)的保姆機(jī)器人要怎么用?就只能修改 Person 的代碼了,當(dāng)一個程序很復(fù)雜時,這樣的修改可能會非常麻煩,因此合理的設(shè)計對以后的維護(hù)升級起著至關(guān)重要的作用

新手程序員可能需要適當(dāng)花些時間來消化這部分內(nèi)容,例子雖小,以小見大,這已經(jīng)屬于中級程序員的要求了,如有疑問,可以跟我一起討論

弱引用

代碼中出現(xiàn)了一個新的關(guān)鍵字 weak ,弱引用是為了避免循環(huán)引用造成的內(nèi)存泄露,先來看現(xiàn)象,在運(yùn)行程序后,我們能看到 “生命周期結(jié)束,被自動銷毀” 的輸出,表示對象使用完畢,正常釋放,如果我們把代碼中兩處 weak 去掉再運(yùn)行,就不再輸出釋放的信息,也就是說 deinit 析構(gòu)函數(shù)沒有執(zhí)行,對象在使用完后沒有釋放,我們把這樣的現(xiàn)象稱為 內(nèi)存泄露 ,如果內(nèi)存泄露越來越多,計算機(jī)就會有越來越多的內(nèi)存被占用無法釋放,應(yīng)用就有可能會被系統(tǒng)殺死,因此要避免內(nèi)存泄露

普通的引用也叫強(qiáng)引用,在對象釋放的時候,會先釋放其強(qiáng)引用的屬性,當(dāng)兩個對象互相強(qiáng)引用時,就形成了一個死環(huán),誰都無法釋放誰,造成內(nèi)存泄露,這樣的情況下就需要使用弱引用

枚舉,結(jié)構(gòu)體,這樣的值類型不存在弱引用,因為它們不屬于引用類型,它們的拷貝形式不會造成循環(huán)引用和內(nèi)存泄露,枚舉和結(jié)構(gòu)體也都可以遵循協(xié)議,協(xié)議可以分普通協(xié)議和 類專屬協(xié)議 ,為什么要有類專屬協(xié)議,因為只有類專屬協(xié)議才能使用弱引用,請注意我們的 HousemaidDelegate 協(xié)議定義后面的 class 表示其為類專屬協(xié)議,因此這個協(xié)議可以使用弱引用,枚舉和結(jié)構(gòu)體是不能遵循該協(xié)議的

上一篇:類與結(jié)構(gòu)體
當(dāng)前篇:協(xié)議
下一篇:多線程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容