擴展:
擴展是將你自己的代碼插入到已經(jīng)存在的對象類型中的方法埃唯,即extending an existing object type。這種擴展可以在自己定義的類型中停局,也可以是Swift本身的對象類型岛抄,還可以是Cocoa的對象類型』】桑總之,擴展可以將你的代碼打入你的甚至別人的類型中文虏!
擴展的聲明必須是在文件的最頂端侣诺。聲明擴展:關(guān)鍵字extension放在已經(jīng)存在的對象類型名之前殖演,之后如果要加協(xié)議氧秘,那么就添加冒號和要添加的協(xié)議,之后再加大括號趴久,里面寫內(nèi)容丸相。此處的限制如下:
1、擴展不能重寫已經(jīng)存在的成員彼棍。(但是它可以重載已經(jīng)存在的方法)
2灭忠、擴展不能聲明存儲屬性。(但是它可以聲明計算屬性)
3座硕、類的擴展不能聲明指定構(gòu)造器和析構(gòu)器弛作。(但是它可以聲明便利構(gòu)造器)
擴展對象類型:
在真實的編程過程中,為了封裝一些不存在的函數(shù)(通過將它表達為屬性或者方法)华匾,我有時會擴展Swift內(nèi)部類型或者Cocoa類型映琳。這是一些真實app的例子:
在某個紙牌游戲中,我需要進行洗牌蜘拉,而牌儲存在數(shù)組里面萨西。那么我就擴展了Swift的Array,給它一個shuffle方法:
再如Cocoa的Core Graphics 框架有很多很有用的函數(shù)與CGRect結(jié)構(gòu)體有關(guān)旭旭,Swift對它有諸多擴展谎脯,不過對于取得CGRect的中心坐標(Center Point : CGPoint),還沒有捷徑可走持寄。所以我就擴展出了center屬性:
擴展可以定義static和class方法源梭。由于對象類型常常是全局可用的,所以這不失為是構(gòu)造一個全局函數(shù)的好辦法稍味。比如废麻,如果覺得金色會在開發(fā)中常常使用到,就沒有必要每一次都重復定義仲闽,可以將它封裝在全局函數(shù)中脑溢。然而也不需要直接構(gòu)造一個全局函數(shù),我們可以將他封裝在UIColor中:
現(xiàn)在你就可以像調(diào)用UIColor.redcolor一樣,使用UIColor.myGoldenColor來調(diào)用顏色了屑彻。
另一個擴展很好用的地方就是使Cocoa內(nèi)置類和你自己的數(shù)據(jù)類型一起工作验庙。比如在Zotz App中,我定義了一個枚舉類型社牲,用來讀檔存檔粪薛。
其中的問題就是,讓我存檔時我需要每次調(diào)用rawValue搏恤。
這看起來很丑违寿,所以必須要改。我們可以教會NSCoder(coder的類)怎樣去干活兒熟空。在這個擴展中藤巢,我重載了encoderObject:forKey" 方法:
事實上,我將rawValue的調(diào)用從我自己的代碼中放進了NSCoder里∠⒙蓿現(xiàn)在我就可以這樣調(diào)用了:
擴展還可以整理對象類型的代碼掂咒。一個常見的應用就是為對象類型增加其采用的協(xié)議。比如這樣:
如果你覺得多行定義你的對象類型太復雜迈喉,那么用擴展可以很好地分解代碼绍刮。
當你對Swift的結(jié)構(gòu)體(Struct)進行擴展的時候,一個有趣的事情發(fā)生了:你聲明一個構(gòu)造器挨摸,原先的隱式構(gòu)造器(Implicit initializer)可以保留下來:
這段代碼意味著你可以通過顯式構(gòu)造器Digit()孩革,也可以用原先的隱式構(gòu)造器Digit(number:)。這樣即使我們顯式定義了構(gòu)造器得运,也不會使原先的隱式構(gòu)造器消失膝蜈。
擴展協(xié)議(Extending Protocols):
在Swift2.0中,你可以擴展協(xié)議了澈圈!
像擴展其他的對象類型一樣彬檀,你也可以擴展協(xié)議的方法和屬性。不像協(xié)議的聲明中方法和屬性僅僅是要求采用者實現(xiàn)瞬女,擴展協(xié)議會是采用者繼承該方法和協(xié)議窍帝,也就是說它們是真正的方法和屬性。
看上面诽偷,Bird可以不用實現(xiàn)fly()就繼承Flier坤学。這是因為Flier協(xié)議的擴展提供了該方法,Bird實際上繼承了fly():
采用者可以實現(xiàn)從擴展協(xié)議繼承來的方法报慕,再重寫它:
但是請注意:這種繼承不是多態(tài)的深浮。采用者的實現(xiàn)不是重寫;而僅僅是另一個實現(xiàn)眠冈。內(nèi)部識別規(guī)則不適用飞苇,關(guān)鍵是引用菌瘫。
即使f在內(nèi)部其實是Insect(用is運算符可以知道),fly消息還是會發(fā)送給Flier布卡。
為了將它變得看起來像多態(tài)繼承雨让,我們必須將fly在原協(xié)議中定義:
現(xiàn)在Insect就保持了它的內(nèi)部完整:
這種差異是有意義的。因為協(xié)議采用不會引入動態(tài)分配忿等。因此編譯器必須進行靜態(tài)決定栖忠。如果在原協(xié)議中,要求方法被聲明贸街,那么采用者就必須實現(xiàn)它庵寞,所以我們就可以調(diào)用該方法。但是如果該方法僅僅存在于協(xié)議擴展薛匪,這就取決于運行的動態(tài)分配(Dynamic Dispatch)捐川。這就有可能使得協(xié)議落空,所以消息就被發(fā)送到協(xié)議擴展去了蛋辈。
協(xié)議擴展的主要好處就是允許代碼被轉(zhuǎn)移至一個合適的Scope中属拾。這里有一個例子:我有四個枚舉類型将谊,每一個都代表了Card的一個屬性察蹲,F(xiàn)ill搀崭、Color、Shape和Number。他們都有一個人int類型的初始值努释。我對與每次都賦給rawValue有點煩躁了,所以就給每個枚舉類型一個沒有外部參數(shù)名的代理構(gòu)造器纤虽,它們會調(diào)用內(nèi)置的init(rawValue:)構(gòu)造器:
盡管我不喜歡這樣的方式狡刘,但是在Swift1.2之前我沒有任何辦法。而現(xiàn)在瓦堵,我可以用協(xié)議擴展來改變這種情況基协。帶有初始值的枚舉類型將會自動采用內(nèi)置的協(xié)議 Rawpresentable,初始值的類型別名叫做RawValue菇用。所以我可以將我的構(gòu)造器加入到此協(xié)議中:
在Swift標準庫中澜驮,協(xié)議擴展意味著很多全局函數(shù)可以被改寫為方法。比如惋鸥,在Swift1.2之前杂穷,enumerate是一個全局函數(shù):
原先這個函數(shù)只能是全局函數(shù)。
這個函數(shù)只作用于序列(sequence)卦绣,所以也是SequenceType協(xié)議的采用者耐量。在Swift2.0之前,怎樣實現(xiàn)呢滤港?enumerate可能在SequenceType協(xié)議中被定義(requirement)廊蜒,但是這意味著所有的采用者都要實現(xiàn)它,這顯然不可能。所以我們只能把它聲明為一個全局函數(shù)山叮,然后用泛型約束保證入口八堡,用序列作為參數(shù)。
在Swift2.0聘芜,enumerate就可以當做方法定義在SequenceType協(xié)議中了:
現(xiàn)在就不需要什么泛型約束了兄渺,也不需要泛型,甚至不需要參數(shù)汰现。被發(fā)送了enumerate消息的序列就是要飆車enumerate的序列挂谍。
這個例子的方法可以被廣泛使用。很多Swift標準庫的全局函數(shù)變成了方法瞎饲。
擴展泛型:
當你擴展泛型類型的時候口叙,占位符類型名對于你的擴展聲明是可見的。你很可能用到它們嗅战。不過這會使你的代碼變得不清楚妄田,因為看起來你在使用沒有被定義過的變量。所以最好加點注釋驮捍。
在Swift2.0中疟呐,泛型類型擴展可以包含where語句了。這和泛型約束的效果類似:它限制了決定泛型類型的代碼东且,向編譯器保證你的代碼是合法的启具。
通過協(xié)議擴展,這意味著全局函數(shù)可以用作方法鲁冯。看下面的這個例子:
為什么我要用全局函數(shù)呢色查?因為在Swift2.0之前我不得不這么做薯演。如果我想把它(求最小值)變成Array的一個方法,在Swift1.2之前秧了,我可以擴展Array而且也可以引用Array的泛型占位符跨扮,然而它不能進一步限制占位符。所以這里沒有辦法將此方法插入Array并保證該占位符代表的類型是 Comparable示惊,所以編譯器也不允許使用<比較Array的元素好港。而在Swift2.0就大大不同了。不僅可以更進一步地約束泛型類型米罚,而且還可以將它變成Array的方法钧汹。
這個方法只能被元素是Comparable的序列調(diào)用,否則編譯器將拒絕它們录择。
第二行不編譯的原因就是因為我沒有使Digit結(jié)構(gòu)體采用Comparable拔莱。
同樣這個改變也使得Swift的標準庫有大規(guī)模的改變碗降,允許把全局函數(shù)寫入結(jié)構(gòu)體擴展或者協(xié)議擴展作為方法。比如塘秦,全局函數(shù)find(在Swift1.2之前)變成了集合類型的indexOf方法讼渊,此方法的元素必須是Equatable。
這就是協(xié)議擴展尊剔,同樣也是運用where語句的泛型擴展爪幻,二者在Swift2.0之前都是不被允許的。