我從55個Swift標準庫協議中學到了什么?

jopen 8年前發布 | 18K 次閱讀 Swift Apple Swift開發

Swift團隊使用協議的方法,給了我們哪些使用協議的提示?

好的。55個Swift標準庫公有協議,18分鐘,讓我們開始吧。

首先我只想問:為什么是協議?為什么面向協議編程?如果我們回到過去那段年少無知少不更事的面相對象編程時期,我們很多人最初學習的是Objective-C,這意味著我們免受多繼承的專橫。又或者你是這個房間里另一半喜歡C++的人,那么我們并沒有受過多繼承的啟示,我們稍后將對其進行討論。

單繼承中,層次結構是線性的:你有父輩、子輩以及孫子輩一系列的繼承樹。當你回到樹的頂端,所有的一切有一個單獨的父輩。這使得層次干凈,但同時你的確失去了合理使用多繼承所帶來的優勢。在Swift中不能繼承枚舉和結構類型,只有類可以。這意味著你有時需要弄得跟麻花一樣來讓你的類型有意義。這樣最終能得到真正通用的超類。然后一級一級下來,如果你可以想象更多的級在你從圖中獲得一個葉子結點之前,你才能得到一個真正可以實例化和使用的類。

"你有時需要弄得跟麻花一樣來讓你的類型有意義"

所以通過協議,你可以使得類型系統更加有組合性,你可以理清繼承的長鏈。當然你將要放棄一個長而高的繼承鏈,為了一個有協議一致性的寬的鏈。但我認為取舍是值得的,希望在這個演講結束后你也這么認為。

什么樣的東西放進協議里有意義?我不打算談我已經完成的很酷的協議,而是瀏覽一下蘋果在Swift標準庫中提供的協議。我們將瀏覽,也許你會學到一些你以前從未聽過的協議。我們究竟能找到什么偉大的想法,接著也許可以獲得一些靈感——在我們的代碼里什么樣的東西可以和協議一起用。基本上這個演講的思路是Swift團隊使用協議的方法,給我們一些可以怎么用協議的提示?

最終目標是讓你開始思考協議,想出一些很酷的點子然后開源,然后我會在你GitHub的倉庫上點星。

三種協議類別:"Can do","Is a"以及"Can be"

Swift標準庫包括54個真正的共有協議。一開始這個演講我開玩笑地想一個個說明它們,每個16秒來湊時間,但相反,我把它們分成三類。"Can do"協議,"Is a"協議,"Can be"協議。每次我們會看其中一個,看一些示例,再看看我們能夠學到的東西。

"Can do"協議

首先我們先來看看"Can do"協議。這些描述的事情是類型可以做或已經做過的,它們也以 -able 結尾為語法,這使得當你瀏覽頭文件時它們很容易被找到。第一個例子:一個類型遵守Hashable協議,意味著你可以得到這個類的整型散列值。這意味著你可以把它存儲進一個集合,可以當作一個字典的鍵等等。還有Equatable和Comparable協議,這意味著你可以用Swift中各種相等和比較的運算符比較兩個實例的值。這些都是很常見的協議,你可能已經在你自己的類型中實現了。你會注意到這些協議描述了你可以對類進行的操作。有比較、相等、散列。

"AbsoluteValuable(絕對有價值)。這聽起來如此重要。正因為它以'valuable(價值)'結尾"

這里做一個補充說明,讓我們來談談我認為的最好的命名協議,它值得一個特殊的過度動畫:AbsoluteValuable(絕對有價值)。這聽起來如此重要。正因為它以"valuable(價值)"結尾。但不幸的是它沒有聽起來那么重要。它只意味著支持絕對值函數。

還有一個小協議子集在這個"Can do"組中,和替代視圖或替代表示形式有關。

讓我們看看一個簡單的例子RawRepresentable。這意味著該類可以表示成某種原始值,然后你可以把原始值轉變回實際的實例。這聽起來很像Swift里的枚舉,有內置的原始值。所以所有有枚舉的功能的類有一個初始化函數,它接受一個原始值,一旦你有一個枚舉值就得到它原始值的版本。這些都建立在這個協議之上。你可以用自己的結構體和類做類似的事情,如果你喜歡。這里的想法是,東西內在的值是一樣的,你只是改變它外在的表示。只因為,原始值和實例值之間存在一對一的映射。

同屬此類。

接下來是CustomPlaygroundQuickLookable。這只是意味著你類型可以在playground中快速查看。同時這也意味著,你的類型是一樣的,你不是將它轉換為其他東西,而是為你的值提供另一種替代視圖。在這種情況下,它可以在快速瀏覽中顯示。

"所以我們有操作,我們有替代試圖。從中我們可以學到什么呢?"

如果你自己的類型有操作,比如說你曾正在寫下一代Instagram殺手級照片過濾APP,你可以添加一個過濾性的協議,然后讓你的照片實例、圖像去遵守它。假設之后,你的過濾APP火了,它真的流行起來,你想要擴展到視頻。然而視頻只是另一種形式的媒體。在理論上,你還可以應用過濾性的協議在視頻、音頻、3D照片上,無論將來出現了什么。

替代視圖的例子呢?總存在從大照片創建縮略圖的情況,你可以認為這是全尺寸照片的替代視圖。再次,這實際上不是一個轉換,它只是替代的表示形式。所以,你可以想象這樣一個Thumbnailable協議,希望你能想出一個更好的名字,甚至音頻版本的一個縮略圖。縮略圖就像一個低比特率版本的音頻之類的。

這里的基本思想,是把你APP和代碼中常見的操作抽象出來,協議化他們,如果有這個詞。為什么你要這么做?一個好處是使得想法可重用。你有幾種類型需要實現一些常見的操作,現在他們可以共享同一個有保證的公共的接口。你可以得到多態的好處,即使是在你的結構和枚舉里。而且,這種復合的方法可以幫助你從操作中分割東西出來。我知道這里意見會不同,但我喜歡從這樣的小塊來建立一個類型,基于它們能做什么。這是第一類的協議、操作、替代視圖。你可以建立的操作和視圖的集合用于您的類型。

"Is a"協議

下一類是"Is a"協議,這些描述類型是什么樣的。與"Can do"的協議相比,這些更基于身份。這意味著遵守多個協議,感覺很像Swift的多繼承。您可以在標準庫中找到這些協議,因為它們以"-type"結尾。它們占了整整一半標準庫,54個中有35個左右的是這類的。讓我們來看一個例子。CollectionType是個好協議。顯然Array、Dictionary、Set遵守CollectionType。或許更令人驚訝的是,Ranges和String views。如果你有一個字符串,你可以獲得它的UTF-8或UTF-16的表示形式。所以,它只是一系列的Unicode代碼點。所以,它也遵守CollectionType。

還有一些協議包括的一些原語像IntegerTyp、FloatingPointType、BooleanType等等。這些協議更像分組。所以有幾個整數類型。我們有無符號整數、有符號整數、16位整數等等。但它們都組合在一起,因為他們共同遵守整數類型的協議。如果你想出自己的整數類型,也許你想要一個4位整數或6位整數,有點非主流,那么為什么不遵守這個協議。但是,這不大可能發生。大多數這類協議你可能從來沒有編寫類型去遵守這些協議。例如你看BooleanType頭文件的注釋,實際上勸阻你創造更多的布爾類型,因為一個已足夠。另一個例子是MirrorPathType,它的頭文件有以下令人愉快的注釋:"不要聲明新的類遵守這個協議,它們不會像預期的那樣工作。”

所以,正如你所看到的,這意味著許多這一類的協議,你可能是使用遵守這些的類型。我相信每個人都使用一個數組或一個整數,但你可能不會創建遵守它們的類型。雖然有一些你可能使用——我們有ErrorType,我們早些時候聽Thomas說過,在Swift 2中新的錯誤處理模式用。還有SequenceType、GeneratorType,如果你正在構建集合化的東西且你想要支持迭代,可以看一看這些協議。

這就是"Is a"協議。協議被當作身份。我們可以從這個模式的協議中學到什么嗎?再次,因為這些都是基于身份而不是操作,你可以在更大的類型分組中使用它們。回到規范化動物王國的例子:這是一個夸張的很長的類層次結構。底部甚至沒有一種我們可以實例化的類型。在這個類層次結構上,每一步都比之前添加一些功能。因此,有了協議你可以讓你的類型系統有更多的組合性。你有這個協議的清單,你可以構建和在不同類型中使用。比如貓叫和狗吠,更多是一種"Can do"風格的動物能做的事,而"兩條腿"和"四條腿"則更多是一種身份類型。你會注意到兩條腿和四條腿也有繼承,因為協議可以像這樣繼承。

這意味著,一旦你設置好了這些協議,你可以建立起過去需要巨大的超類列表而現在只是一組協議的東西,包括繼承如果你需要它。當你構建你的類型,你可以在這里選擇身份和需要的功能。因為你的類型可以遵守多個協議,你可以一點點建立類型的功能,基于協議的一致性。

這就是第二類的標準庫協議--"Is a"協議,與分組和身份有關。

"Can be"協議(11.28)

最后我們有"Can be"類型。這不是同一個東西的替代視圖,正如我們已經看到的,這些都是直接轉換。從類型X轉換到Y。這些協議以"-Convertible"結尾。這意味著這個類型可以被轉換到或者轉換成別的東西。

讓我們看看幾個例子。我們有簡單的初始化風格的,如FloatLiteralConvertible、IntegerLiteralConvertible、ArrayLiteralConvertible等等。如果你的類型遵守FloatLiteralConvertible那么這意味著你需要一個初始化函數,接受某種floatLiteral,默認情況下這是一個double值,然后構建你的類型。所以轉換的方向是從一個浮點數到你的類型。

相比之下,有每個人的最好的朋友CustomStringConvertible之類的協議,又或如先前知道的Printable協議。它指定你的類型可以轉換成一個字符串,所以轉換變到另一個方向,是從你的類型中到一個字符串。

"一個Objective-C從業者看到,說'啊,那沒什么。我碼方法聲明的時間比每次我碼代碼的時間都長。'"

這里再次補充說明,54個協議中名稱最長的也在此類,ExtendedGraphemeClusterLiteralConvertible——41個字符。我相信那些來自Objective-C會說、或笑、就像:"啊,那沒什么。我碼方法聲明的時間比每次我碼代碼的時間都長。"這就是用于轉換的協議在這最后一組。我們可以從這樣的協議中學到什么,除了要盡量保持你類型的名字短?

這是很明顯的。如果你有類型,它可以成為其他類型,那就不要只添加一個函數,或添加一個被計算的屬性,或添加一個初始化函數。考慮設置一個協議。記住你可以使用協議指定你的類型被轉換到或轉換成別的。所需的其他任何技術討論的例子,除了動物,是員工數據庫。如果你有對象表示人、普通員工、經理、承包商,那么這些人可能是一個單獨的類型。如果承包商可以被雇傭而作為一個員工,或員工可以被晉升為經理,那這就是一種轉換。你不想再加入人的名稱、地址、電話號碼和社會安全號等等。你想相對無縫地把一個承包商轉換成員工。你可以用一個EmployeeConvertible協議。然后說承包商類型和應試者類型能遵守它。

這么做的好處是什么?為什么需要一個協議加上轉換函數,而似乎僅僅一個函數不是更簡單嗎?再次,它一部分是組合的方法。一個應試者可以成為員工的事實,是類型是什么的一部分,但這并不特別。其他的人也可以成為員工。通過使用一個協議可以保證有一個共同的定義良好的接口來將一些人轉換成一個員工。

還有一個好處是,代碼漂亮的像文檔一樣。如果你瀏覽代碼,或者項目里的其他人,你會看到"EmployeeConvertible",并且你已經熟悉它,它告訴了你這類型能做什么和接口是什么樣子。你還可以在你的項目查找"EmployeeConvertible"這個詞,然后在搜索結果中,就可以看到能成為員工的類型的列表。這就是"Can be"協議組合,處理你類型之間的轉換。

四個廣泛模式

所以我們看到三類來自標準庫的協議,它們與能力、身份和轉換有關。什么是廣泛的模式,想想我們自己的代碼?我們有四個:

  • 操作--如果有一組通用的操作你必須在類型中執行,考慮抽象出來當作協議。

  • 與替代視圖有關--如果你的類型有替代視圖,或另一種表示形式,不是一個完整的轉換,想想它是否遵守公有協議。

  • 代表身份--這是你做類似多繼承的地方,或混合多種類型(Mix-ins)的類型。考慮身份和類型,把相似的類型用協議來分組。

  • 最后我們有轉換,無論是從一個類型轉換成別的,還是一個類型被轉換到,如果特定的轉換在代碼中多次發生,考慮抽象很常見的轉換作為協議,這幫助你跟蹤事物,并保持一致的接口。

我想,看到蘋果把如此多的常用功能,如映射、過濾器、枚舉函數、抽象到協議,使用的也只是普通的舊協議和協議擴展,這是一個很好的例子。一個未來將如何強大的靈感。蘋果正遵循這個榜樣。例如,如果你看看數組的定義,它遵守8個協議,字符串遵守12協議,等等。所以這里的想法是,你創建這些特征打包在協議里,然后你的你的代碼庫就都可以使用。

我認為以這種方式思考你的類型,可以幫助你在腦海里保持清晰和并把這些類型分類。所以我肯定鼓勵在你自己的代碼里嘗試這些。仔細看看你的類型,聚焦于它們的共同點,看能不能用協議。

以上就是我全部要說的。協議萬歲,謝謝!

Q&A (17:47)

Q1:你好,又是我--Dave Ungar。演講讓我思考我自己的代碼,當不用協議時。例如我有一個結構體,它有10個函數。也許我應該用10個協議。具體來說來自標準庫,并沒有字符串的協議。因此我不得不創造一個,為了把一個Where子句轉換成別的一般化的東西,參數可以是一個字符串。所以你能說說看,是何時不該使用協議,還是為什么當我需要時沒有一個字符串協議?謝謝。

Greg:蘋果就是這樣,對吧?從不滿足每個人的需求。但是我認為何時用何時不用,我正在使用的方法是根據普遍性來分類。如果我只有一個類型遵守這個協議,很大程度上我就留給這個類型去處理。如果這個類型做10件事那就做10件吧。但如果它甚至發生兩次,只要不止一次,對我來說這就足夠去說"好吧,這是普遍得足以發生兩次",似乎是一個奇跡的重復,也許這將發生不只一次。所以這是一個主觀判斷的問題。但是我想說,不止發生一次是至少的要求。

觀眾:天哪。所以值得復制函數、聲明和所有的東西去得到協議。

Greg:沒錯,就是這樣。

Q2:你好。我想知道,你處理過泛型協議嗎?如果有,你的策略是什么?

Greg:我認為我有這樣一個例子,因為協議不能有泛型,但你可以取類型別名,正如你常看到的,你只是定義類型別名。如果你想,你可以稱之為類型別名T。如果你想讓它看起來像泛型。然后當你定義類型你只需要定義類型別名作為具體類型。這是我在協議里見過的方法。

觀眾:我認為,有時情況就變得有點困難,比如如果你聲明一個變量,然后你說"我想讓這個變量遵守這個協議。"如果這協議有一個類型別名,你不能這么做,因為編譯器不喜歡它。

Greg:是這樣,它更像是如果你定義你自己的類型,然后你可以給類型一個別名那么這也是一種選擇。但我還沒找到一個更好的在變量情況下的通用解決方案。

Q3:非常感謝你這次演講,我很喜歡它。這樣思考協議似乎讓我更加清晰了。我的問題是,你說的有4次你可以使用協議的地方,或思考協議的地方,操作,替代視圖,身份和轉換。我的問題是,你能說清一下替代視圖,替代試圖和轉換之前的區別。我通常思考的方式是,有時我可以將類型轉換為替代視圖,但是你似乎把它區分開來了,對吧?

Greg:好的,我聽懂了你的問題。問題的一部分是因為協議的命名方式。也許它們的命名糟糕。再次,Printable曾是一個 -able 協議,現在卻是CustomStringConvertible。這是臨陣倒戈,對吧?但我認為,我看它是以替代視圖的含義,事物本身并沒有改變,只是外在的樣子正在發生變化。從全尺寸圖像到縮略圖,或從枚舉值到原始值,它們其實是一樣的。只是你透過一個不同的鏡頭。然而轉換協議更像是"我有一個整數,我將變為一個字符串。"這是CustomStringConvertible,對吧?你把它改變成了一個完全不同的類型,這不僅僅是一個有自己東西的不同的視圖,它會有自己的生命周期。我就是這樣看的。

  • 本文僅用于學習和交流目的,轉載請注明文章譯者、出處以及本文鏈接。

  • 感謝博文視點對本期翻譯活動的支持

來自: http://www.cocoachina.com/swift/20160107/14868.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!