假設(shè)現(xiàn)在項(xiàng)目中有一個已經(jīng)由其他人實(shí)現(xiàn)的Person類:
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
convenience init() {
self.init(name: "unknow", age: 1)
}
func say() {
print("Hi, my name is \(name)")
}
}
該類有一個已經(jīng)寫好的say函數(shù):
var p = Person(name: "Jack", age: 18)
p.say() // Hi, my name is Jack
現(xiàn)在假如有一個需求,我們需要用到Person類的所有功能,唯一不同的就是要改變say函數(shù)的功能御蒲,需要將age也打印出來,但是又不能去修改Person類的代碼。
按照常規(guī)的處理方式稍味,這時我們可以用兩種方式:第一種就是創(chuàng)建一個Person的子類,重寫say函數(shù)荠卷。第二種就是寫一個Person的擴(kuò)展模庐,這里不采用子類的方式,因?yàn)檫@種情況下油宜,擴(kuò)展才是優(yōu)先的掂碱。
創(chuàng)建擴(kuò)展:
extension Person {
func jkr_say() {
print("Hi, my name is \(name), my age is \(age)")
}
}
不同于Objective-C,擴(kuò)展不能夠重寫慎冤,所以需要定義一個不同名的函數(shù)疼燥。同時,按照一般Objective-C的編碼習(xí)慣蚁堤,添加一個前綴醉者。
現(xiàn)在就可以使用我們擴(kuò)展的函數(shù):
p.jkr_say() // Hi, my name is Jack, my age is 18
擴(kuò)展
這里其實(shí)就已經(jīng)完成了需求,但是還有另外一種方式,先看一下效果:
p.jkr.say() // Hi, my name is Jack, my age is 18
函數(shù)名仍然是say撬即,但是通過p.jkr.xxx來調(diào)用立磁,這個jkr就是我們的自定義前綴。
通過代碼的語法可以判斷剥槐,要實(shí)現(xiàn)這種調(diào)用鏈唱歧,jkr應(yīng)該是實(shí)例p的一個實(shí)例屬性。所以需要為Person添加一個實(shí)例屬性粒竖,這個實(shí)例屬性中有我們定義的say函數(shù)颅崩。首先我們先定義這個實(shí)例屬性對于的類型,采用結(jié)構(gòu)體來實(shí)現(xiàn)一個前綴的封裝蕊苗,并在內(nèi)部添加一個say函數(shù):
struct JKRPrefix {
func say() {
print("Hi my name is...")
}
}
然后為Person添加一個實(shí)例沿后,由于不能夠修改源碼,所以要通過擴(kuò)展朽砰,而擴(kuò)展只能夠擴(kuò)展計(jì)算屬性:
extension Person {
var jkr: JKRPrefix {
get {
JKRPrefix()
}
}
}
現(xiàn)在就可以實(shí)現(xiàn)之前的函數(shù)調(diào)用得运,現(xiàn)在還無法獲取到Person實(shí)例的name和age屬性,這里先只打印出了如下字符串:
p.jkr.say() // Hi my name is...
了能夠通過jkr使用Person實(shí)例的屬性和方法锅移,這里明顯需要將Person實(shí)例傳給jkr:
struct JKRPrefix {
var proxy: Person
func say() {
print("Hi, my name is \(proxy.name), my age is \(proxy.age)")
}
}
extension Person {
var jkr: JKRPrefix {
get {
JKRPrefix(proxy: self)
}
}
}
現(xiàn)在就可以實(shí)現(xiàn)前綴的調(diào)用:
p.jkr.say() // Hi, my name is Jack, my age is 18
注:上面并沒有出現(xiàn)循環(huán)引用熔掺,Person類的jkr屬性是計(jì)算屬性,并沒有存儲和引用關(guān)系非剃。
范型
現(xiàn)在的JKRPrefix是只能支持Person類型的置逻,因?yàn)閜roxy屬性限定了Person類型,為了讓JKRPrefix能夠又?jǐn)U展性备绽,這里使用范型券坞,swift對于范型的支持比Objective-C強(qiáng)大很多:
struct JKRPrefix<ProxyType> {
var proxy: ProxyType
func say() {
(proxy as? Person).map { print("Hi, my name is \($0.name), my age is \($0.age)") }
}
}
extension Person {
var jkr: JKRPrefix<Person> {
get {
JKRPrefix(proxy: self)
}
}
}
現(xiàn)在JKRPrefix已經(jīng)可以支持所有的類型了,下面嘗試為String也添加一個前綴:
extension String {
var jkr: JKRPrefix<String> {
get {
JKRPrefix(proxy: self)
}
}
}
let str = "ABC"
str.jkr.say() // 無任何打印
附加條件判斷
上面可以看到肺素,str.jkr明明是不需要say函數(shù)的恨锚,但是由于say方法是寫在JKRPrefix中的,str.jkr也有了say函數(shù)倍靡,雖然可以通過proxy屬性的類型判斷來避免類型錯誤猴伶,但是這也不是一個完美的辦法。
首先塌西,swift支持?jǐn)U展的附加條件判斷他挎。這里可以使用附加條件判斷,對JKRPrefix的擴(kuò)展的范型類型進(jìn)行條件限定捡需,首先先將say函數(shù)抽離出來寫在擴(kuò)展中办桨,然后進(jìn)行附加條件判斷:
struct JKRPrefix<ProxyType> {
var proxy: ProxyType
// func say() {
// (proxy as? Person).map { print("Hi, my name is \($0.name), my age is \($0.age)") }
// }
}
extension JKRPrefix where ProxyType: Person {
func say() {
print("Hi, my name is \(proxy.name), my age is \(proxy.age)")
}
}
現(xiàn)在,只有Person實(shí)例才有say函數(shù)站辉,String實(shí)例是沒有的:
p.jkr.say() // Hi, my name is Jack, my age is 18
// str.jkr.say() // 報錯呢撞,找不到
協(xié)議
上面已經(jīng)實(shí)現(xiàn)了前綴對于不同類型的通用適配损姜,現(xiàn)在的問題就是在于不太方便用,因?yàn)椴煌愋褪庀迹家憣τ诘臄U(kuò)展來添加一個jkr的計(jì)算屬性摧阅,這個添加jkr計(jì)算的功能又都是幾乎相同的。在Objective-C中脓鹃,對于所有類型都有一個相同的屬性或者方法,抽取父類是第一想到的辦法古沥。而在這個需求中瘸右,這些毫不相干的類型抽取父類明顯不是一個好辦法。
對于這類需求岩齿,可以使用面向協(xié)議編程的思想太颤。Swfit提供了面向協(xié)議編程的支持,相比于Objective-C的協(xié)議盹沈,Swift更加的強(qiáng)大龄章。Swift可以通過對協(xié)議的擴(kuò)展,支持協(xié)議的默認(rèn)實(shí)現(xiàn)乞封,這就可以讓協(xié)議做到定義一些帶有實(shí)現(xiàn)邏輯的計(jì)算屬性或者函數(shù)做裙,來讓遵守這個協(xié)議的類都直接使用它們。同時肃晚,Swfit又支持?jǐn)U展來遵守協(xié)議锚贱,實(shí)現(xiàn)不修改類型的源碼,讓其遵守協(xié)議关串。
下面將Person和String的jkr計(jì)算屬性都抽取出到一個協(xié)議中:
protocol JKRPrefixProtocol : Any {}
extension JKRPrefixProtocol {
var jkr: JKRPrefix<Self> {
get { JKRPrefix(proxy: self) }
set {}
}
}
下面將之前刪除的無用代碼都去掉:
struct JKRPrefix<ProxyType> {
var proxy: ProxyType
}
protocol JKRPrefixProtocol : Any {}
extension JKRPrefixProtocol {
var jkr: JKRPrefix<Self> {
get { JKRPrefix(proxy: self) }
set {}
}
}
現(xiàn)在所有代碼都封裝好了拧廊,為一個類增加帶前綴的方法需要兩步:
第一步類擴(kuò)展遵守協(xié)議:
extension Person: JKRPrefixProtocol {}
第二步對JKRPrefix做附近條件判斷的擴(kuò)展:
extension JKRPrefix where ProxyType: Person {
func say() {
print("Hi, my name is \(proxy.name), my age is \(proxy.age)")
}
}
就可以了:
var p = Person(name: "Jack", age: 18)
p.jkr.say() // Hi, my name is Jack, my age is 18
結(jié)構(gòu)體的附加條件判斷
下面為String添加一個方法,使其能夠返回字符串中數(shù)字的個數(shù):
extension String: JKRPrefixProtocol {}
extension JKRPrefix where ProxyType == String {
func numberCount() -> Int {
var count = 0
for c in proxy where ("0"..."9").contains(c) {
count += 1
}
return count
}
}
var str = "123abc789"
print(str.jkr.numberCount())
需要注意的就是結(jié)構(gòu)體類型判斷用" == "晋修。
Self和self
上面是實(shí)例方法的添加吧碾,下面為前綴擴(kuò)展添加類方法的功能,需要添加類方法墓卦。
如果前綴要實(shí)現(xiàn)類方法倦春,由于實(shí)例方法是在JKRPrefix的擴(kuò)展中添加中,那么也應(yīng)該在JKRPrefix中的添加類方法:
extension JKRPrefix where ProxyType == String {
static func hello() {
print(ProxyType.self, "向你問好")
}
}
// 預(yù)計(jì)實(shí)現(xiàn)的效果
// String.jkr.hello()
能夠通過 String.jkr調(diào)用出jkr落剪,那么可以確定jkr屬性一定是JKRPrefixProtocol的類屬性溅漾,jkr類型還不確定,先用Int替代:
extension JKRPrefixProtocol {
static var jkr: Int {
3
}
}
能夠通過String.jkr.xxx調(diào)用出 JKRPrefix的類方法著榴,那么jkr可以確定是JKRPrefix.Type:
extension JKRPrefixProtocol {
static var jkr: JKRPrefix<Self>.Type {
get { JKRPrefix<Self>.self }
set {}
}
}
Self代表當(dāng)前類型,self代表方法調(diào)用者添履,在實(shí)例方法中代表實(shí)例,在類方法中代碼當(dāng)前類型:
func test() {
print(Self.self == type(of: self)) // true
}
static func test() {
print(Self.self == self.self) // true
}
在JKRPrefixProtocol的擴(kuò)展中脑又,分別添加了連個jkr屬性暮胧,一個實(shí)例屬性锐借,一個類屬性,其中JKRPrefix<Self>范型中對于的Self都是代表遵守協(xié)議的當(dāng)前類的類型往衷。
計(jì)算實(shí)例屬性 var jkr 中 返回的 JKRPrefix(proxy: self) 實(shí)例傳入的 self 代表遵守協(xié)議的當(dāng)前類型的實(shí)例本身钞翔。
計(jì)算類型屬性 static var jkr 中 返回的 JKRPrefix<Self>.self,JKRPrefix.self返回的是 JKRPrefix 的類型席舍,這樣就可以調(diào)用JKRPrefix中的類方法布轿。
完整代碼:
protocol JKRPrefixProtocol : Any {}
extension JKRPrefixProtocol {
var jkr: JKRPrefix<Self> {
get { JKRPrefix(proxy: self) }
set {}
}
static var jkr: JKRPrefix<Self>.Type {
get { JKRPrefix<Self>.self }
set {}
}
}
extension String: JKRPrefixProtocol {}
extension JKRPrefix where ProxyType == String {
func numberCount() -> Int {
var count = 0
for c in proxy where ("0"..."9").contains(c) {
count += 1
}
return count
}
static func hello() {
print(ProxyType.self, "向你問好")
}
}
var str = "123abc123";
print(str.jkr.numberCount()) // 6
String.jkr.hello() // String 向你問好
關(guān)聯(lián)對象
前面已經(jīng)實(shí)現(xiàn)了為Person增加了一個帶前綴的say函數(shù)。假如現(xiàn)在有一個需求来颤,要為Person增加了一個業(yè)務(wù)邏輯汰扭,存儲一個lines屬性,當(dāng)調(diào)用say的時候福铅,判斷是否有l(wèi)ines萝毛,有l(wèi)ines的時候打印lines。
Swift也支持關(guān)聯(lián)對象滑黔,通過擴(kuò)展為類添加一個關(guān)聯(lián)對象“拾現(xiàn)在為Person擴(kuò)展了一個帶前綴lines屬性的關(guān)聯(lián)對象,和一個帶前綴的自定義初始化方法:
extension Person: JKRPrefixProtocol {
// 將擴(kuò)展單獨(dú)存儲在一個文件中略荡,就可以實(shí)現(xiàn)LINES_KEY對外界的隱藏
fileprivate static var LINES_KEY: Void?
}
extension JKRPrefix where ProxyType: Person {
func say() {
// 可選值綁定
if let str = lines {
print("\(proxy.name) say: \(str)")
} else {
print("\(proxy.name) say: 大家好庵佣,我是某龍?zhí)?)
}
}
// 關(guān)聯(lián)對象
var lines: String? {
get {
objc_getAssociatedObject(proxy, &ProxyType.LINES_KEY) as? String
}
set {
objc_setAssociatedObject(proxy, &ProxyType.LINES_KEY, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// 默認(rèn)參數(shù)、可選類型參數(shù)
static func person(name: String = "某龍?zhí)?, age: Int, lines: String? = nil) -> Person{
var p = Person.init(name: name, age: age)
p.jkr.lines = lines
return p
}
}
var persons = [
Person.jkr.person(name: "Jack", age: 31, lines: "我是有臺詞還露臉的大咖"),
Person.jkr.person(name: "Ling", age: 21, lines: "我是男七號的裸替汛兜,能露下臉很開心"),
Person.jkr.person(age: 36)
]
// 我是男七號的裸替秧了,能露下臉很開心
print(persons[1].jkr.lines ?? "")
// 某龍?zhí)?36
print(persons[2].name, persons[2].age, persons[2].jkr.lines ?? "")
for p in persons {
// Jack say: 我是有臺詞還露臉的大咖
// Ling say: 我是男七號的裸替,能露下臉很開心
// 某龍?zhí)?say: 大家好序无,我是某龍?zhí)? p.jkr.say()
}