iOS9 with Swift 協(xié)議

協(xié)議

協(xié)議是一種表示類型的相通性的方法,往往這些類型某些方面迥異。比如,一個(gè)Bee對象和一個(gè)Bird對象就在飛行方面有共同之處。因此定義一個(gè)Flier類型就很有用。問題來了:在哪種意義上,Bee和Bird可以算Flier呢?

除了繼承,當(dāng)然一個(gè)可能的選擇是類繼承的方式。如果Bee和Bird都是類,對于父類和子類是有繼承關(guān)系的。所以Flier可以使Bee和Bird的共同父類。然而這不一定是對的。因?yàn)锽ee是一種昆蟲,而Bird不是。雖然他們都可以飛行。所以我們需要一個(gè)類,能把他們聯(lián)結(jié)在一起。

此外,如果Bee和Bird不是類怎么辦?要知道在Swift中,這是很可能發(fā)生的,很多重要的對象都可以由結(jié)構(gòu)體來替代類。但是結(jié)構(gòu)體卻沒有繼承等級。別忘了這是結(jié)構(gòu)體和類的最主要的區(qū)別。所以說結(jié)構(gòu)體也需要和類一樣能夠表達(dá)共同性。

Swift通過協(xié)議(Protocol)來解決這個(gè)問題。協(xié)議相當(dāng)重要,在Swift Header中超多70個(gè)協(xié)議。此外Oc也有協(xié)議。Swift和Oc的協(xié)議大致相符,而且可以互換。Cocoa很依賴協(xié)議。

雖然協(xié)議是一種對象類型,但是卻沒有一個(gè)協(xié)議對象——你無法實(shí)例化一個(gè)協(xié)議。協(xié)議相當(dāng)輕量化。一個(gè)協(xié)議的聲明只是一堆屬性和方法的列表。屬性沒有值,方法中沒有代碼。一個(gè)“真正的”對象就可以聲明它屬于這個(gè)協(xié)議;或者是說他采用(adopting)或者遵循(conforming)該協(xié)議。一個(gè)對象類型一旦采用了該協(xié)議就是表明它會使用協(xié)議列出的屬性和方法。

比如說,作為一個(gè)Flier需要包括一個(gè)fly方法。所以Flier協(xié)議就可以標(biāo)明fly方法:在協(xié)議的列表中添加fly,并且使函數(shù)體為空,就像這樣:

1

任何類型,包括枚舉、結(jié)構(gòu)體、類甚至其他的協(xié)議都可以采用這個(gè)協(xié)議。可以在協(xié)議前面用分號在聲明中連接到上面類型名后面。(如果采用者是子類,協(xié)議跟在它的父類之后,中間用逗號號隔開。)

若Bird是一個(gè)結(jié)構(gòu)體,它可以這樣采用Filer協(xié)議:


2

出現(xiàn)了點(diǎn)問題,原來是沒有遵循協(xié)議內(nèi)容使用fly方法,現(xiàn)在改一下:

3

以上就是協(xié)議的大致內(nèi)容,不過在現(xiàn)實(shí)中,我們還需要協(xié)議中的方法充實(shí)一點(diǎn)。

Tips:在Swift2.0中,多虧了協(xié)議擴(kuò)展,協(xié)議可以聲明方法并且提供具體實(shí)現(xiàn)內(nèi)容,之后我將講解。

為什么要用協(xié)議?

你現(xiàn)在可以對于為什么要用協(xié)議摸不著頭腦。我們讓Bird采用了Flier,之后呢?如果我們想要Bird知道怎么去fly,為什么我們不直接給他一個(gè)fly方法,反而給他協(xié)議呢?原因和類型分不開。別忘了,協(xié)議也是一個(gè)類型。Flier也是。所以我可以在任何我能用類型的地方使用Flier—— 將它聲明為一個(gè)變量,例如用在函數(shù)參數(shù)處。


4

想想上面的代碼,它包含了協(xié)議的全部點(diǎn)。一個(gè)協(xié)議是一種類型,所以多態(tài)是成立的。協(xié)議提供了另一種表達(dá)類型和子類型概念的方式。由于可替換原則,這意味著Flier可以是任意類型的一個(gè)實(shí)例(注意定義的是實(shí)例方法,而非類方法)。無論對象類型是哪種,只要它采用了Flier協(xié)議,因?yàn)橹灰捎昧嗽搮f(xié)議,就會有一個(gè)fly方法。因此編譯器就會允許我們向這個(gè)對象發(fā)送fly消息。這樣說了,一個(gè)Flier就是可以向其發(fā)送fly的對象。

然而反過來就不正確,也就是說有fly方法的對象不一定是Flier。畢竟只有采用了該協(xié)議才是Flier。


5

這個(gè)Bee雖然有fly方法,但他不是Flier,所以tellToFly是不會接受它作為其參數(shù)的。為了使代碼編譯,可以在其聲明后添加Flier協(xié)議:


6.1
6.2

現(xiàn)在可以說下實(shí)際應(yīng)用了。就像我之前說的,Swift中有大量的協(xié)議,讓我們使用一二吧。其中一個(gè)很有用的協(xié)議就是CustomStringConvertible。這個(gè)協(xié)議需要我們使用description字符串屬性。這有一個(gè)神奇的效果:當(dāng)該屬性的實(shí)例被用在字符\插入(Interpolation)或者輸出(Print)的時(shí)候,description屬性值將自動用于代表這個(gè)實(shí)例。

注意一個(gè)類型是可以采用多個(gè)協(xié)議的。比如Double類型就采用了CustomStringConvertible, Hashable, Comparable 等等協(xié)議。


7


當(dāng)然上面的代碼是不會編譯的,因?yàn)槲译m然讓他們采用了上述的協(xié)議,但是在結(jié)構(gòu)體內(nèi)部沒有定義具體的代碼。

協(xié)議類型的檢驗(yàn)和轉(zhuǎn)型:

協(xié)議是一種類型,而且協(xié)議的采用者是其子類型。所以多態(tài)是適用的。因此當(dāng)對象被聲明為協(xié)議類型時(shí),在對象的聲明類型和實(shí)際類型之間調(diào)節(jié)的運(yùn)算符就會工作。比如說,可以用is運(yùn)算符去檢測該對象是Bee還是Bird,因?yàn)槁暶黝愋虵lier既可以是Bee又可以是Bird。


8

類似地,as!和as?可以被用來將聲明為協(xié)議類型的對象轉(zhuǎn)型為實(shí)際類型。這個(gè)能力相當(dāng)重要,因?yàn)榻邮軈f(xié)議的對象常常需要傳遞消息,而這協(xié)議是不能完成的。比如getworm這個(gè)方法:


9

Bird作為Flier,它可以fly,而作為Bird,它可以getWorm。所以你不能告訴所有的Flier都讓他們?nèi)etWorm:


10

所以此時(shí)就需要將其進(jìn)行轉(zhuǎn)型:


11

聲明一個(gè)協(xié)議:

協(xié)議只能在文件的最頂端聲明。通過使用關(guān)鍵字protocol來進(jìn)行聲明,之后寫協(xié)議名(作為類型名,首字母大寫),之后再跟大括號,里面寫這些東西:

屬性(Properties):

在協(xié)議中,屬性聲明包括var關(guān)鍵字(不能是let)、屬性名、冒號、其類型和大括號(包含get或者get set)。在以前,采用者對于屬性的具體實(shí)施可以是可寫入的,而在現(xiàn)在,它必須是:作為只讀的計(jì)算屬性或者常量(let)存儲屬性,調(diào)用者不能使用get set 屬性。

至于聲明static或者class屬性,可以在屬性前加static關(guān)鍵字。如果是采用者是類,那么和在類屬性里面一樣自由。

方法(Method):

協(xié)議中的方法聲明就是將普通函數(shù)聲明去掉函數(shù)體,也就是,它沒有大括號和函數(shù)內(nèi)容。任何對象函數(shù)類型都是合法的,包括init和subsccript。(下標(biāo)也是沒有大括號和函數(shù)內(nèi)容,但是和屬性一樣,會有g(shù)et 或者get set)。

至于聲明static或者class屬性,可以在屬性前加static關(guān)鍵字。如果是采用者是類,那么和在類屬性里面一樣自由。

當(dāng)在枚舉類型或者結(jié)構(gòu)體類型中使用時(shí),如果一個(gè)方法需要被聲明為mutating,那么這個(gè)協(xié)議必須標(biāo)記為mutating;而且如果協(xié)議中沒有mutating,協(xié)議采用者不能添加mutating。但是,協(xié)議中有mutating,采用者可以省略。(其實(shí)這里說的都是要有mutating)

類型別名(Type Alias):

協(xié)議聲明中可以引入內(nèi)部類型別名(Type alias)作為原本類型名的同義詞。比如,typealias Time = Double 允許Time類型在協(xié)議大括號內(nèi)部進(jìn)行引用。其他地方,比如采用者類型中,Time并不存在,但是Double是它的一個(gè)匹配。

協(xié)議采用(Protocol adoption):

協(xié)議自身就可以采用別的協(xié)議(一個(gè)或者多個(gè)),方法就像你所想的,分號后面用逗號隔開。事實(shí)上,這為你提供了建立完整的類型的二級繼承體系!這在Swift頭文件中被大量運(yùn)用。

為了表達(dá)清楚,采用了別的協(xié)議的協(xié)議可能需要重復(fù)被采用協(xié)議的內(nèi)容(在大括號里的),但是由于這種重復(fù)可能是隱式的,所以也不是必要的。然而,對象類型采用了這種協(xié)議既要滿足此協(xié)議中的內(nèi)容,還要滿足該協(xié)議采用的其他協(xié)議的內(nèi)容。

Tips:如果一個(gè)協(xié)議的目的僅僅是為了將其他協(xié)議組合起來(通過采用別的協(xié)議),而沒有添加任何的新要求,而且你只在一個(gè)地方用這個(gè)協(xié)議的話,你大可不必這樣。你可以直接用protocol<...,...>,括號里面的協(xié)議用逗號分隔。

可選的協(xié)議成員:

在Oc中,協(xié)議的成員可以被聲明為可選值,表示該成員不一定要在采用者中實(shí)現(xiàn)。為了更好地兼容Oc,Swift允許可選的協(xié)議成員,但是只限于顯式橋接到OC的協(xié)議中(通過將@objc置于協(xié)議之前),在這種協(xié)議中,可選的成員即內(nèi)部的方法和屬性也要標(biāo)記optional:


12

只有類才可以采用這樣的協(xié)議,而且該特性在該類是NSObject的子類的情況下才會工作,或者可選成員被@objc標(biāo)記、

13

協(xié)議采用者并不保證會將可選成員實(shí)施,所以Swift不知道對其發(fā)送song或者sing消息是否安全。

在這種情況下,Swift通過將song包裝進(jìn)可選值中來解決問題。如果Flier 的采用者沒用實(shí)現(xiàn)該屬性,那么其結(jié)果就是一個(gè)nil,顯然這沒有什么危害。


14

Tips:這是個(gè)很少見的情況,會以雙重包裝的可選值結(jié)束。比如,如果可選屬性的值是String?,那么取得的值就是String??。

Warning:該可選屬性可以通過其協(xié)議被聲明為{get set},但是沒有合法的語法在這種協(xié)議類型的對象中去為這種屬性賦值。如果f是一個(gè)Flier,song被定義為{get set},你就不能設(shè)置f.song,我覺得這是一個(gè)Swift的bug。

像sing這樣的可選方法,可能還要更復(fù)雜一些。如果方法沒有被實(shí)現(xiàn),我們是被禁止首先調(diào)用它的。為了解決這個(gè)問題,該方法自己就自動被標(biāo)記成了該類型的可選值。因此要向它發(fā)送消息,就必須先進(jìn)行解包。比較安全的方法就是用?解包:


15

這段代碼就可以很安全地運(yùn)行了。只要采用者實(shí)現(xiàn)了sing,那么效果就是向f發(fā)送sing。如果Flier采用者沒有實(shí)現(xiàn)sing,什么事也不會發(fā)生。如果用!強(qiáng)制解包,那么后面那種情況程序就會崩潰。

如果可選的方法返回一直值,那么此值也會被包在可選值中:


16

如果你調(diào)用sing?( ),結(jié)果返回的就是可選的字符串:


17

如果用!,那么結(jié)果就是String。

很多Cocoa協(xié)議都有可選成員。比如你的iOS app有一個(gè)app delegate類就采用了UIApplicationDelegate 協(xié)議。該協(xié)議的方法都是可選的。然而,事實(shí)上,這不會對你調(diào)用它們有什么影響,(你不需要對他們進(jìn)行特別標(biāo)注即@objc),因?yàn)槟愕腶ppdelegate類已經(jīng)是NSObject的子類了,所以這個(gè)特性將會直接生效,而不管你實(shí)不實(shí)現(xiàn)方法。同樣地,你常常會使UIViewController的子類采用有可選成員的Cocoa delegate協(xié)議,這也是NSObject的子類,所以你直接實(shí)現(xiàn)你想實(shí)現(xiàn)的方法就可以了,而不用特別的標(biāo)記。

類的協(xié)議:

類的協(xié)議是指,在聲明中協(xié)議名后面冒號跟著class關(guān)鍵字的協(xié)議,表示它只能夠被 類 對象類型 采用:


18

(如果協(xié)議已經(jīng)被@objc標(biāo)記,那么就沒有必要標(biāo)記class了,因?yàn)锧objc屬性已經(jīng)暗示它是一個(gè)類的協(xié)議了。)

一個(gè)主要的使用類的協(xié)議的原因就是:利用類的特性——特別的內(nèi)存管理機(jī)制。

19

關(guān)鍵字weak標(biāo)記了delegate屬性作為特殊內(nèi)存管理,只有類的實(shí)例才有這種特殊內(nèi)存管理。delegate屬性是協(xié)議類型,而結(jié)構(gòu)體和枚舉都可能采用協(xié)議,所以為了滿足編譯器的要求:該對象事實(shí)上是類的實(shí)例,而不是結(jié)構(gòu)體或者枚舉的實(shí)例,所以這個(gè)協(xié)議被聲明為類的協(xié)議。

隱式Required構(gòu)造器:

設(shè)想一個(gè)協(xié)議聲明了一個(gè)構(gòu)造器,而且一個(gè)類采用了這個(gè)協(xié)議。由于該協(xié)議的規(guī)定,該類和其子類必須實(shí)現(xiàn)該構(gòu)造器。因此該類不僅要實(shí)現(xiàn)構(gòu)造器,而且它必須標(biāo)記其為required。所以說,定義在協(xié)議中的構(gòu)造器是隱式required,該類被顯式強(qiáng)制實(shí)現(xiàn)這個(gè)要求。


20

編譯錯(cuò)誤:Initializer requirement init( ) can only be satisfied by a required initializer in non-final class Bird.?

為了解決這個(gè)問題,我們必須指定我們的構(gòu)造器為required:


21

或者,就像剛剛錯(cuò)誤提示,還有一種方法,標(biāo)記Bird類為final。這意味著它就不能有任何子類了——這也保證了這個(gè)問題不會再出現(xiàn)了。所以如果Bird標(biāo)記為final,就不需要標(biāo)記它的init為required了。

上面的代碼匯總,Bird并沒有被final標(biāo)記,而是它的init被required標(biāo)記。這意味著任何Bird 的子類將會實(shí)現(xiàn)制度構(gòu)造器,所以就意味著它們放棄了構(gòu)造器繼承,必須實(shí)現(xiàn)required構(gòu)造器并且標(biāo)記required。

上面提到的和現(xiàn)實(shí)中iOS編程一個(gè)令人煩惱的特性密不可分。比如,你subclass了內(nèi)置的Cocoa類 UIViewController(你很可能這么做),然后你給了這個(gè)子類一個(gè)構(gòu)造器(你很可能這么做):


22

這個(gè)代碼就不會編譯。編譯錯(cuò)誤顯示:required initializer init(coder: ) must be provided by subclass of UIViewController.

我們現(xiàn)在就知道其中的緣故了:UIViewController采用了一個(gè)NSCoding 的協(xié)議,而這個(gè)協(xié)議需要一個(gè)構(gòu)造器 init(coder: )。這都不是你造成的,UIViewController 和NSCoding都是Cocoa定義的。但是這不礙事,這就是我剛剛提到的情形。你的UIViewController子類要不就是繼承了init(coder:) 要不必須顯式實(shí)現(xiàn)它并且標(biāo)記required。既然你的子類已經(jīng)有了一個(gè)指定構(gòu)造器,那么繼承就泡湯了,只能用后者了。

但是如果你壓根兒沒想過要實(shí)現(xiàn)這么個(gè)構(gòu)造器,再這樣操作這就顯得沒有意義了。Xcode的Fix-It特性提供了下面的方法:


23

它既滿足了構(gòu)造器(在c5,會有為什么即使他沒有完成條件,但依然是合法構(gòu)造器的原因),又使得他在被調(diào)用的時(shí)候會自動crash。

如果你真的有要實(shí)現(xiàn)的東西,那么只需要?jiǎng)h除fatalError 一行,并且用你自己的代碼代替就可以了。最小的實(shí)現(xiàn)內(nèi)容就是super.init(coder: aDecoder),當(dāng)然如果你有需要初始化的屬性,你也可以先初始化它們。

不僅UIViewController,還有很多Cocoa的內(nèi)置類都采用了NSCoding。如果你有它們的子類并且要自己初始化構(gòu)造器,那么會經(jīng)常遇到這個(gè)問題。

字面轉(zhuǎn)換:

Swift中的一個(gè)非常好的點(diǎn)就是:它的很多特性是在內(nèi)部實(shí)現(xiàn)的而且在頭文件中可以看到。Literal就是一例。比如你可以直接寫出5來制造一個(gè)值為5的Int(而不需要很正式地寫Int(5))不是因?yàn)樾W(xué),而是因?yàn)镮nt采用了IntegerLiteralConvertible協(xié)議。不只是Int Literal 是這樣工作,其他Literal也是這樣的原理。下面是在Swift頭文件中定義的literal convertible protocol:


24

你自己的對象類型也可采用上面的轉(zhuǎn)換協(xié)議。這意味著literal可以出現(xiàn)在你的對象類型實(shí)例可以出現(xiàn)的地方。

比如我們定義一個(gè)Nest類型,其中包括雞蛋個(gè)數(shù)變量:

25

因?yàn)镹est采用了IntegerLiteralConvertible,我們可以將Int傳至Nest可以用的地方,然后init(integerLiteral:)就會被自動調(diào)用,從而產(chǎn)生一個(gè)新的Nest對象包含特定的雞蛋數(shù)。


26
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評論 2 376

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