Swift自定義前綴

假設(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()
}

源碼

image

源碼鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末验毡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帝嗡,更是在濱河造成了極大的恐慌晶通,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哟玷,死亡現(xiàn)場離奇詭異狮辽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巢寡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門喉脖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抑月,你說我怎么就攤上這事树叽。” “怎么了谦絮?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵题诵,是天一觀的道長洁仗。 經(jīng)常有香客問我,道長性锭,這世上最難降的妖魔是什么赠潦? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮草冈,結(jié)果婚禮上她奥,老公的妹妹穿的比我還像新娘。我一直安慰自己怎棱,他們只是感情好哩俭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹄殃,像睡著了一般携茂。 火紅的嫁衣襯著肌膚如雪你踩。 梳的紋絲不亂的頭發(fā)上诅岩,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音带膜,去河邊找鬼吩谦。 笑死,一個胖子當(dāng)著我的面吹牛膝藕,可吹牛的內(nèi)容都是我干的式廷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼芭挽,長吁一口氣:“原來是場噩夢啊……” “哼滑废!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袜爪,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蠕趁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辛馆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺陋,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年昙篙,在試婚紗的時候發(fā)現(xiàn)自己被綠了腊状。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡苔可,死狀恐怖缴挖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情焚辅,我是刑警寧澤醇疼,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布硕并,位于F島的核電站,受9級特大地震影響秧荆,放射性物質(zhì)發(fā)生泄漏倔毙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一乙濒、第九天 我趴在偏房一處隱蔽的房頂上張望陕赃。 院中可真熱鬧,春花似錦颁股、人聲如沸么库。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诉儒。三九已至,卻和暖如春亏掀,著一層夾襖步出監(jiān)牢的瞬間忱反,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工滤愕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留温算,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓间影,卻偏偏與公主長得像注竿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魂贬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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