面向協(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)
2. OOP的不足
但有些問(wèn)題瞳脓,使用OOP并不能很好解決塑娇,比如:如何將 BVC、DVC 的公共方法 run 抽取出來(lái)劫侧?
class BVC: UIViewController {
func run() {
print("run")
}
}
class DVC: UITableViewController {
func run() {
print("run")
}
}
基于OOP想到的一些解決方案:
將run方法放到另一個(gè)對(duì)象A中埋酬,然后BVC、DVC擁有對(duì)象A屬性
缺點(diǎn):多了一些額外的依賴(lài)關(guān)系將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é)議就好了,如下:
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)前綴效果
- 創(chuàng)建帶有泛型的MJ結(jié)構(gòu)體
struct MJ<Base> {
let base: Base //Base是傳?的類(lèi)型
init(_ base: Base) { //base是傳?的類(lèi)型的值
self.base = base
}
}
- 創(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 {}
}
}
- 讓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())
- 優(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())
- 給類(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