iOS-Swift-面向協(xié)議編程

面向協(xié)議編程(Protocol Oriented Programming毕骡,簡(jiǎn)稱(chēng)POP)释涛,是Swift的一種編程范式亭畜,Apple于2015年WWDC提出侮攀,在Swift的標(biāo)準(zhǔn)庫(kù)中锣枝,能見(jiàn)到大量POP的影子。

同時(shí)兰英,Swift也是一門(mén)面向?qū)ο蟮木幊陶Z(yǔ)言(Object Oriented Programming撇叁,簡(jiǎn)稱(chēng)OOP),在Swift開(kāi)發(fā)中畦贸,OOP和POP是相輔相成的陨闹,任何一方并不能取代另一方,POP能彌補(bǔ)OOP一些設(shè)計(jì)上的不足。

一. 面向?qū)ο?/h1>

1. OOP的三大特性

OOP的三大特性:封裝趋厉、繼承寨闹、多態(tài)。
繼承的經(jīng)典使用場(chǎng)合:
當(dāng)多個(gè)類(lèi)(比如A觅廓、B鼻忠、C類(lèi))具有很多共性時(shí),可以將這些共性抽取到一個(gè)父類(lèi)中(比如D類(lèi))杈绸,最后A帖蔓、B、C類(lèi)繼承D類(lèi)

繼承.png

2. OOP的不足

繼承關(guān)系.png

但有些問(wèn)題瞳脓,使用OOP并不能很好解決塑娇,比如:如何將 BVC、DVC 的公共方法 run 抽取出來(lái)劫侧?

class BVC: UIViewController {
    func run() {
        print("run")
    }
}

class DVC: UITableViewController {
    func run() {
        print("run")
    }
}

基于OOP想到的一些解決方案:

  1. 將run方法放到另一個(gè)對(duì)象A中埋酬,然后BVC、DVC擁有對(duì)象A屬性
    缺點(diǎn):多了一些額外的依賴(lài)關(guān)系

  2. 將run方法增加到UIViewController分類(lèi)中
    缺點(diǎn):UIViewController會(huì)越來(lái)越臃腫烧栋,而且會(huì)影響它的其他所有子類(lèi)

二. 面向協(xié)議

1. POP的解決方案

創(chuàng)建Runnable協(xié)議写妥,在協(xié)議里聲明run方法,在extension里面默認(rèn)實(shí)現(xiàn)run方法审姓,然后讓BVC和DVC遵守Runnable協(xié)議珍特,如下:

protocol Runnable {
    func run()
}

extension Runnable { //在extension中提供默認(rèn)實(shí)現(xiàn),實(shí)現(xiàn)可選協(xié)議效果
    func run() {
        print("run")
    }
}

class BVC: UIViewController, Runnable {}
class DVC: UITableViewController, Runnable {}

比如魔吐,如果想把B2扎筒、C1公共的東西抽取出來(lái),可以把公共的東西寫(xiě)在協(xié)議里面酬姆,直接讓B2嗜桌、C1都繼承protocol協(xié)議就好了,如下:

抽取.png

2. POP的注意點(diǎn):

  • 優(yōu)先考慮創(chuàng)建協(xié)議辞色,而不是父類(lèi)(基類(lèi))
  • 優(yōu)先考慮值類(lèi)型(struct骨宠、enum),而不是引用類(lèi)型(class)
  • 巧用協(xié)議的擴(kuò)展功能
  • 不要為了面向協(xié)議而使用協(xié)議

三. 利用協(xié)議實(shí)現(xiàn)前綴效果

  1. 創(chuàng)建帶有泛型的MJ結(jié)構(gòu)體
struct MJ<Base> {
    let base: Base //Base是傳?的類(lèi)型
    init(_ base: Base) { //base是傳?的類(lèi)型的值
        self.base = base
    }
}
  1. 創(chuàng)建MJCompatible空協(xié)議淫僻,然后通過(guò)extension給協(xié)議定義MJ結(jié)構(gòu)體的類(lèi)型計(jì)算屬性和實(shí)例計(jì)算屬性
protocol MJCompatible {}
extension MJCompatible {
    static var mj: MJ<Self>.Type { //獲取MJ<Base>類(lèi)型屬性
        get { MJ<Self>.self }
        set {}
    }
    var mj: MJ<Self> {
        get { MJ(self) } //獲取MJ<Base>實(shí)例屬性
        set {}
    }
}
  1. 讓String遵守這個(gè)協(xié)議诱篷,那么String里面就有MJ結(jié)構(gòu)體的類(lèi)型計(jì)算屬性和實(shí)例計(jì)算屬性了,然后當(dāng)泛型是String類(lèi)型的時(shí)候雳灵,就給給MJ結(jié)構(gòu)體擴(kuò)展一個(gè)numberCount方法
extension String: MJCompatible {}
extension MJ where Base == String { //當(dāng)泛型是String類(lèi)型的時(shí)候,就給它擴(kuò)展一個(gè)numberCount方法
    func numberCount() -> Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) { //extension中可以訪(fǎng)問(wèn)本類(lèi)中的base屬性
            count += 1
        }
        return count
    }
}

var string = "123fdsf434"
print(string.mj.numberCount())
  1. 優(yōu)化:上面代碼只有String類(lèi)型有MJ前綴闸盔,我們讓NSString也遵守MJCompatible協(xié)議悯辙,并且把泛型指定為ExpressibleByStringLiteral,就可以實(shí)現(xiàn)String和NSString都有MJ前綴
extension String: MJCompatible {}
extension NSString: MJCompatible {}
extension MJ where Base: ExpressibleByStringLiteral { //遵守這個(gè)協(xié)議的不是String就是NSString
    func numberCount() -> Int {
        let string = base as! String
        var count = 0
        for c in string where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

var s1: String = "123fdsf434"
var s2: NSString = "123fdsf434"
var s3: NSMutableString = "123fdsf434"
print(s1.mj.numberCount())
print(s2.mj.numberCount())
print(s3.mj.numberCount())
  1. 給類(lèi)、對(duì)象擴(kuò)充前綴躲撰。讓Person遵守這個(gè)協(xié)議针贬,在擴(kuò)展中將泛型指定為Person,并且實(shí)現(xiàn)需要擴(kuò)充的實(shí)例方法和類(lèi)方法拢蛋,如下:
class Person {}
class Student: Person {}

//讓Person遵守這個(gè)協(xié)議桦他,并且給MJ前綴擴(kuò)充方法
extension Person: MJCompatible {}
extension MJ where Base: Person {
    func run() {} //實(shí)例方法
    static func test() {} //類(lèi)方法
}

Person.mj.test() //類(lèi)方法調(diào)用
Student.mj.test()

let p = Person()
p.mj.run() //實(shí)例方法調(diào)用

let s = Student()
s.mj.run()

四. 空協(xié)議的使用

如果想判斷某個(gè)實(shí)例是否是數(shù)組,不管是傳入[1, 2]實(shí)例還是傳入NSArray()實(shí)例谆棱,使用value is [Any]都可以判斷這個(gè)實(shí)例是否是數(shù)組類(lèi)型快压,如下:

//[Any]就是Array<Any>的意思,下面方法就是判斷傳進(jìn)來(lái)的value是不是Array<Any>類(lèi)型的
func isArray(_ value: Any) -> Bool { value is [Any] }
isArray( [1, 2] ) //這個(gè)實(shí)例是[Int],也就是Array<Int>類(lèi)型的,所以返回true
isArray( ["1", 2] ) //這個(gè)實(shí)例是[Any],也就是Array<Any>類(lèi)型的,所以返回true
isArray( NSArray() ) //這個(gè)實(shí)例是__NSArray0類(lèi)型的,也是數(shù)組,所以返回true
isArray( NSMutableArray() ) //true

如果想要判斷某個(gè)類(lèi)型是否是數(shù)組類(lèi)型,一般我們都是傳入XX.self垃瞧,然后通過(guò)type is XX.Type來(lái)判斷蔫劣,如下:

func isArrayType(_ type: Any.Type) -> Bool { type is Array<Any>.Type }
isArrayType([Any].self) //true
isArrayType(NSArray.self) //false
print(NSArray.self) //NSArray

但是對(duì)于NSArray,NSArray.self就是NSArray个从,所以上面打印false脉幢,如何解決這個(gè)問(wèn)題?
我們可以讓Array和NSArray都遵守一個(gè)空協(xié)議嗦锐,然后判斷type是不是某種協(xié)議類(lèi)型嫌松,這樣就可以達(dá)到兼容的目的了,如下:

protocol ArrayType {} //空協(xié)議
extension Array: ArrayType {} //Array遵守
extension NSArray: ArrayType {} //NSArray遵守
//以前我們說(shuō)過(guò)xxx.Type就是存放xxx.self的奕污,所以下面這么寫(xiě)不報(bào)錯(cuò)
//判斷外面?zhèn)魅氲氖遣皇悄撤N協(xié)議類(lèi)型萎羔,由于Array和NSArray都遵守了這個(gè)協(xié)議,所以達(dá)到了兼容的目的
func isArrayType(_ type: Any.Type) -> Bool { type is ArrayType.Type }
isArrayType([Int].self) //true
isArrayType([Any].self) //true
isArrayType(NSArray.self) //true
isArrayType(NSMutableArray.self) //true
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菊值,一起剝皮案震驚了整個(gè)濱河市外驱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腻窒,老刑警劉巖昵宇,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異儿子,居然都是意外死亡瓦哎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)柔逼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蒋譬,“玉大人,你說(shuō)我怎么就攤上這事愉适》钢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵维咸,是天一觀的道長(zhǎng)剂买。 經(jīng)常有香客問(wèn)我惠爽,道長(zhǎng),這世上最難降的妖魔是什么瞬哼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任婚肆,我火速辦了婚禮,結(jié)果婚禮上坐慰,老公的妹妹穿的比我還像新娘较性。我一直安慰自己,他們只是感情好结胀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布赞咙。 她就那樣靜靜地躺著,像睡著了一般把跨。 火紅的嫁衣襯著肌膚如雪人弓。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天着逐,我揣著相機(jī)與錄音崔赌,去河邊找鬼。 笑死耸别,一個(gè)胖子當(dāng)著我的面吹牛健芭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秀姐,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼慈迈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了省有?” 一聲冷哼從身側(cè)響起痒留,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蠢沿,沒(méi)想到半個(gè)月后伸头,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舷蟀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年恤磷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片野宜。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扫步,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匈子,到底是詐尸還是另有隱情河胎,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布虎敦,位于F島的核電站仿粹,受9級(jí)特大地震影響搁吓,放射性物質(zhì)發(fā)生泄漏原茅。R本人自食惡果不足惜吭历,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擂橘。 院中可真熱鬧晌区,春花似錦、人聲如沸通贞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昌罩。三九已至哭懈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茎用,已是汗流浹背遣总。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轨功,地道東北人旭斥。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像古涧,于是被迫代替她去往敵國(guó)和親垂券。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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