swift中的協(xié)議和擴(kuò)展

1.Swift中的Protocol

什么是Protocol?

Protocol是Swift中的一種自定義類型驰唬,可以使用protocol定義某種約定顶岸,而不是某一種類型,一般用于表示某種類型的共性叫编。

Protocol 用法

定義一個(gè)protocol

protocol PersonProtocol {
    func getName()
    func getSex()
}

某個(gè)class辖佣、struct或者enum要遵守這種約定的話,需要實(shí)現(xiàn)約定的方法

struct Person: PersonProtocol {
    func getName() {
         print("MelodyZhy")
    }
    func getSex() {
         print("boy")
    }
}
protocol中的約定方法搓逾,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
protocol中也可以定義屬性凌简,但必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(get set)
protocol PersonProtocol {
    // 我們也可以在protocol中定義屬性
    // ?必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(get set)
    var height: Int { get set }
    func getName()
    func getSex()
    // protocol中的約定方法,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
    // ? Default argument not permitted in a protocol method
    // func getAge(age: Int = 18)
    func getAge(age: Int)
}
雖然height在protocol中是一個(gè)computed property,但在遵守該約定的類型中可以簡(jiǎn)單的定義成一個(gè)stored property
當(dāng)protocol中定義了一個(gè)只讀屬性恃逻,其實(shí)我們也可以在遵守該約定的類型中完成該屬性的可讀可寫
protocol PersonProtocol {
    var height: Int { get set }
    var weight: Int { get }
    func getName()
    func getSex()
    func getAge(age: Int)
}

struct Person: PersonProtocol {
    var height = 178
    var weight = 120
    func getName() {
         print("MelodyZhy")
    }
    func getSex() {
         print("boy")
    }
    func getAge(age: Int) {
        print("age = \(age)")
    }
}

var person = Person()
person.height   // 178
person.height = 180
person.height  // 180

person.weight // 120
// 可以更改(但在以前版本的Swift中是不能直接這樣更改的
// 需要借助一個(gè)內(nèi)部的stored property
// 然后把這個(gè)屬性設(shè)計(jì)成一個(gè)computed property實(shí)現(xiàn) get 和 set 方法)
person.weight = 130 // 130
// 當(dāng)我們把person從Person轉(zhuǎn)換成PersonProtocol時(shí) 他就是只讀的了
// ?Cannot assign to property: 'weight' is a get-only property
(person as PersonProtocol).weight = 120
如何定義可選的protocol屬性或者方法?
@objc protocol PersonProtocol {
    optional var height: Int { get set }
    optional var weight: Int { get }
    optional func getName()
    optional func getSex()
    optional func getAge(age: Int)
}

class Person: PersonProtocol {
    // 如果想提供可選的約定方法或者屬性那么只能定義@objc的protocol
    // 并且這種約定只能class能遵守
}
protocol可以繼承藕施,當(dāng)然struct寇损、class、enum都可以同時(shí)遵守多個(gè)約定
// 例如:
protocol PersonProtocol {
    var height: Int { get set }
    var weight: Int { get }
    func getName()
    func getSex()
    func getAge(age: Int)
}
protocol Engineer: PersonProtocol {
    var good: Bool { get }
}
protocol Animal {
}
struct Person: Engineer, Animal {
    // 省略了該實(shí)現(xiàn)約定的方法和屬性
}

2.protocol extension

protocol extension 最關(guān)鍵的一點(diǎn)就是能在 protocol extension 方法中獲取 protocol 的屬性裳食,因?yàn)镾wift編譯器知道任何一個(gè)遵守 protocol 的自定義類型矛市,一定會(huì)定義這個(gè) protocol 約定的各種屬性,既然這樣我們就可以在 protocol extension 中添加默認(rèn)的實(shí)現(xiàn)了诲祸。這也是為什么會(huì)有 protocol oriented programming 這個(gè)概念浊吏,但這時(shí)候肯定會(huì)有人說我通過面對(duì)對(duì)象的編程方式也可以實(shí)現(xiàn)而昨,但為什么要用遵守 protocol 的方法呢,這個(gè)要等到了解 extension 中的 type constraints 后解釋...

先看一個(gè)通過 protocol extension 添加默認(rèn)實(shí)現(xiàn)的代碼例子

// 定義一個(gè)人屬性的 protocol
protocol PersonProperty {
    var height: Int { get } // cm
    var weight: Double { get } // kg
    // 判斷體重是否合格的函數(shù)
    func isStandard() -> Bool
}
extension PersonProperty {
    // 給 protocol 添加默認(rèn)的實(shí)現(xiàn)
    func isStandard() -> Bool {
        return self.weight == Double((height - 100)) * 0.9
    }
    // 給 protocol 添加默認(rèn)屬性
    var isPerfectHeight: Bool {
        return self.height == 178
    }
}
struct Person: PersonProperty {
    var height: Int
    var weight: Double
    // 如果自定義類型里面創(chuàng)建了遵守的 protocol 中的方法
    // 那么他將覆蓋 protocol 中的方法
//    func isStandard() -> Bool {
//        return true
//    }
}
// 創(chuàng)建遵守 PersonProperty 的自定義類型
let p = Person(height: 178, weight: 61.5)
// 那么 p 這個(gè)自定義類型 天生就有判斷這個(gè)人身高體重是否合格的方法
p.isStandard() // false
// 同樣天生具有判斷是否是 Perfect Height 的屬性
p.isPerfectHeight // true

protocol extension 中的 type constraints

這相當(dāng)于給 protocol extension 中的默認(rèn)實(shí)現(xiàn)添加限定條件,寫法如下

// 運(yùn)動(dòng)因素的 protocol
protocol SportsFactors {
    // 運(yùn)動(dòng)量
    var sportQuantity: Double { get }
}

// 下面這種寫法就用到了 extension 中的 type constraints
// 意思是 只有同時(shí)遵守了 SportsFactors 和 PersonProperty 時(shí)
// 才使 PersonProperty 獲得擴(kuò)展 并提供帶有 sportQuantity 屬性的 isStandard 方法
extension PersonProperty where Self: SportsFactors {
    func isStandard() -> Bool {
        // 隨意寫的算法 不要在意
        return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
    }
}

protocol oriented programming 的優(yōu)點(diǎn)

1找田、首先繼承是 class 專有的歌憨,所以它不能用來擴(kuò)展其他類型,但 protocol 是沒有這種局限性的
2墩衙、試想一下务嫡,上面的代碼你用面對(duì)對(duì)象的編程方式的話可能你就需要多一個(gè)運(yùn)動(dòng)量的屬性,同時(shí)也要修改 isStandard 函數(shù)漆改,一切看起來特別自然心铃,隨著后續(xù)需求的更改可能會(huì)有更多因素影響是否是合格的體重,那么這時(shí)候你就會(huì)在不知不覺中將你代碼的耦合度成倍提高挫剑,其實(shí)對(duì)于這個(gè)類來說去扣,他完全不需要知道是否是合格體重的計(jì)算細(xì)節(jié),所以我們完全可以把這些類型無關(guān)的細(xì)節(jié)從類型定義上移出去樊破,用一個(gè) protocol 封裝好這些細(xì)節(jié)愉棱,然后讓其成為這個(gè)類型的一種修飾,這就是POP的核心思想捶码。
3羽氮、當(dāng)有多種因素制約是否是合格體重時(shí),我們可以用多個(gè) protocol 來對(duì)該類型進(jìn)行修飾惫恼,每一種修飾的相關(guān)細(xì)節(jié)档押,我們都在對(duì)應(yīng)的 protocol extension 中單獨(dú)的封裝起來,這樣就大大降低了代碼的耦合度祈纯,同時(shí)代碼的可維護(hù)性也得到了相應(yīng)的提高令宿。swift標(biāo)準(zhǔn)庫(kù)中大部分都是用這種思想構(gòu)建的。**

5.Protocol Oriented Programming 面向協(xié)議編程

面向協(xié)議編程中腕窥,Protocol 實(shí)際上就是 DIP 中的抽象接口粒没。通過之前的講解,采用面向協(xié)議的方式進(jìn)行編程簇爆,即是對(duì)依賴反轉(zhuǎn)原則 DIP 的踐行癞松,在一定程度上降低代碼的耦合性,避免耦合性過高帶來的問題入蛆。下面通過一個(gè)具體實(shí)例簡(jiǎn)單講解一下:
首先是高層次結(jié)構(gòu)的實(shí)現(xiàn)响蓉,創(chuàng)建EmmettBrown的類,然后聲明了一個(gè)需求(travelInTime方法)哨毁。

// 高層次實(shí)現(xiàn) - EmmettBrown
final class EmmettBrown {
    private let timeMachine: TimeTraveling
    init(timeMachine: TimeTraveling) {
        self.timeMachine = timeMachine
    }
    func travelInTime(time: TimeInterval) -> String {
        return timeMachine.travelInTime(time: time)
    }
}

采用 Protocol 定義抽象接口 travelInTime枫甲,低層次的實(shí)現(xiàn)將需要依賴這個(gè)接口。

// 抽象接口 - 時(shí)光旅行
protocol TimeTraveling {
    func travelInTime(time: TimeInterval) -> String
}

最后是低層次實(shí)現(xiàn),創(chuàng)建DeLorean類想幻,通過遵循TimeTraveling協(xié)議粱栖,完成TravelInTime抽象接口的具體實(shí)現(xiàn)。

// 低層次實(shí)現(xiàn) - DeLorean
final class DeLorean: TimeTraveling {
    func travelInTime(time: TimeInterval) -> String {
        return "Used Flux Capacitor and travelled in time by: \(time)s"
    }
}

使用的時(shí)候只需要?jiǎng)?chuàng)建相關(guān)類即可調(diào)用其方法脏毯。

// 使用方式
let timeMachine = DeLorean()
let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)

Delegate - 利用 Protocol 解耦

委托(Delegate)是一種設(shè)計(jì)模式闹究,表示將一個(gè)對(duì)象的部分功能轉(zhuǎn)交給另一個(gè)對(duì)象。委托模式可以用來響應(yīng)特定的動(dòng)作抄沮,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù)跋核,而無需關(guān)心外部數(shù)據(jù)源的類型。部分情況下叛买,Delegate 比起自上而下的繼承具有更松的耦合程度砂代,有效的減少代碼的復(fù)雜程度。

那么 Deleagte 和 Protocol 之間是什么關(guān)系呢率挣?在 Swift 中刻伊,Delegate 就是基于 Protocol 實(shí)現(xiàn)的,定義 Protocol 來封裝那些需要被委托的功能椒功,這樣就能確保遵循協(xié)議的類型能提供這些功能捶箱。

Protocol 是 Swift 的語言特性之一,而 Delegate 是利用了 Protocol 來達(dá)到解耦的目的动漾。

Delegate 使用實(shí)例:
//定義一個(gè)委托
protocol CustomButtonDelegate: AnyObject{
    func CustomButtonDidClick()
}
 
class ACustomButton: UIView {
    ...
    weak var delegate: ButtonDelegate?
    func didClick() {
        delegate?.CustomButtonDidClick()
    }
}

// 遵循委托的類
class ViewController: UIViewController, CustomButtonDelegate {
    let view = ACustomButton()
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        view.delegate = self
    }
    func CustomButtonDidClick() {
        print("Delegation works!")
    }
}

代碼說明

如前所述丁屎,Delegate 的原理其實(shí)很簡(jiǎn)單。ViewController會(huì)將 ACustomButtondelegate 設(shè)置為自己旱眯,同時(shí)自己遵循晨川、實(shí)現(xiàn)了 CustomButtonDelegate 協(xié)議中的方法。這樣在后者調(diào)用 didClick 方法的時(shí)候會(huì)調(diào)用 CustomButtonDidClick 方法删豺,從而觸發(fā)前者中對(duì)應(yīng)的方法共虑,從而打印出 Delegation works!

循環(huán)引用

我們注意到,在聲明委托時(shí)呀页,我們使用了 weak 關(guān)鍵字妈拌。目的是在于避免循環(huán)引用。ViewController擁有 view蓬蝶,而 view.delegate 又強(qiáng)引用了ViewController尘分,如果不將其中一個(gè)強(qiáng)引用設(shè)置為弱引用,就會(huì)造成循環(huán)引用的問題丸氛。

AnyObject

定義委托時(shí)音诫,我們讓 protocol 繼承自 AnyObject。這是由于雪位,在 Swift 中,這表示這一個(gè)協(xié)議只能被應(yīng)用于 class(而不是 struct 和 enum)。

實(shí)際上雹洗,如果讓 protocol 不繼承自任何東西香罐,那也是可以的,這樣定義的 Delegate 就可以被應(yīng)用于 class 以及 struct时肿、enum庇茫。由于 Delegate 代表的是遵循了該協(xié)議的實(shí)例,所以當(dāng) Delegate 被應(yīng)用于 class 時(shí)螃成,它就是 Reference type旦签,需要考慮循環(huán)引用的問題,因此就必須要用 weak 關(guān)鍵字寸宏。

但是這樣的問題在于宁炫,當(dāng) Delegate 被應(yīng)用于structenum 時(shí),它是 Value type氮凝,不需要考慮循環(huán)引用的問題羔巢,也不能被使用 weak關(guān)鍵字。所以當(dāng)Delegate未限定只能用于 class罩阵,Xcode 就會(huì)對(duì) weak 關(guān)鍵字報(bào)錯(cuò):'weak' may only be applied to class and class-bound protocol types

delegate只能用于類中

定義協(xié)議的格式
編寫協(xié)議的格式:
protocol 協(xié)議名字 : 基協(xié)議 {  //當(dāng)然也可以不遵守基協(xié)議
    //方法的聲明
}
例:定義一個(gè)買票的協(xié)議
protocol buyTicketProtocol {
    func buyTicket() -> Void
}
Tips:
如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類也必須要繼承NSObject這個(gè)類
遵守協(xié)議的格式

一個(gè)類若要遵守一個(gè)協(xié)議,只需要在自己所繼承的父類后面寫上要遵守的協(xié)議名并以逗號(hào)","隔開,如果這個(gè)類無需繼承,那么直接在冒號(hào)后面寫上協(xié)議的名字就好

遵守協(xié)議的格式:
class Person : NSObject,SportProtocol{}
例:定義一個(gè)會(huì)買票的黃牛類
class Tout : buyTicketProtocol {  //無繼承類遵守協(xié)議
    func buyTicket() {
        print("here's your ticket")
    }
}
Tips:
Swift中的基協(xié)為NSObjectProtocol,這與OC中的基協(xié)議(NSObject)有些不同

協(xié)議的繼承

  • 上面有提到,當(dāng)我們自定義一個(gè)協(xié)議的時(shí)候可以選擇讓這個(gè)協(xié)議繼承自NSObjectProtocol,不單單如此,自定義的協(xié)議也可以遵守另外一個(gè)協(xié)議哦,基本格式如下:
protocol showTicketNumberProtocol {  //展示票號(hào)
    func showTicketNumber() -> Void
}
protocol buyTicketProtocol : showTicketNumberProtocol  {  //買票
    func buyTicket() -> Void
}
  • 如果一個(gè)類遵循了一個(gè)含有繼承的協(xié)議,那么這個(gè)類就必須實(shí)現(xiàn)這個(gè)協(xié)議鏈中所有的必須實(shí)現(xiàn)的函數(shù),否則編譯報(bào)錯(cuò)
class Tout : buyTicketProtocol  { 
    func buyTicket() {
        print("here's your ticket")
    }
    func showTicketNumber() {  //必須實(shí)現(xiàn)buyTicketProtocol所繼承的"父"協(xié)議中的函數(shù)
        print("123456")
    }
}
Tips:
上面提到的"如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類也必須要繼承NSObject這個(gè)類"
這是因?yàn)槲覀冃枰狽SObject這個(gè)父類來幫我們實(shí)現(xiàn)NSObjectProtocol中定義的函數(shù),否則編譯器會(huì)以"沒有實(shí)現(xiàn)NSObjectProtocol中的函數(shù)為由而報(bào)錯(cuò)
協(xié)議中可選實(shí)現(xiàn)的函數(shù)

為了保證Swift語言的嚴(yán)謹(jǐn)性,不建議在協(xié)議中定義可選實(shí)現(xiàn)的函數(shù),不過不建議不代表不能嘛,我們可以利用OC特性來實(shí)現(xiàn)在Swift協(xié)議中定義可選實(shí)現(xiàn)函數(shù)

  • 創(chuàng)建帶有OC特性的協(xié)議
@objc  //表示一下代碼含有OC特性
protocol showTicketNumberProtocol {
    optional func showTicketNumber() -> Void  //optional修飾的函數(shù)為可選擇實(shí)現(xiàn)(或不實(shí)現(xiàn))的函數(shù)
}
  • 遵守帶有OC特性的協(xié)議
class Tout : showTicketNumberProtocol  {
    //終于,下面這個(gè)函數(shù)可以不實(shí)現(xiàn),并且不會(huì)報(bào)錯(cuò)了
    @objc func showTicketNumber() {  //由于showTicketNumberProtocol含有OC特性,于是這個(gè)協(xié)議中所有的函數(shù)在實(shí)現(xiàn)之前都要有@objc來修飾
        print("123456")
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竿秆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稿壁,更是在濱河造成了極大的恐慌幽钢,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傅是,死亡現(xiàn)場(chǎng)離奇詭異匪燕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)落午,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門谎懦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溃斋,你說我怎么就攤上這事界拦。” “怎么了梗劫?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵享甸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我梳侨,道長(zhǎng)蛉威,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任走哺,我火速辦了婚禮蚯嫌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己择示,他們只是感情好束凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栅盲,像睡著了一般汪诉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谈秫,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天扒寄,我揣著相機(jī)與錄音,去河邊找鬼拟烫。 笑死该编,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的构灸。 我是一名探鬼主播上渴,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喜颁!你這毒婦竟也來了稠氮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤半开,失蹤者是張志新(化名)和其女友劉穎隔披,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寂拆,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奢米,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纠永。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鬓长。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尝江,靈堂內(nèi)的尸體忽然破棺而出涉波,到底是詐尸還是另有隱情,我是刑警寧澤炭序,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布啤覆,位于F島的核電站,受9級(jí)特大地震影響惭聂,放射性物質(zhì)發(fā)生泄漏窗声。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一辜纲、第九天 我趴在偏房一處隱蔽的房頂上張望笨觅。 院中可真熱鬧拦耐,春花似錦、人聲如沸屋摇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炮温。三九已至,卻和暖如春牵舵,著一層夾襖步出監(jiān)牢的瞬間柒啤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工畸颅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留担巩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓没炒,卻偏偏與公主長(zhǎng)得像涛癌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子送火,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容