Swift 4 中的泛型

這是我基于英文原文翻譯的譯文褥芒,如果你對本文感興趣而且想轉(zhuǎn)發(fā),你應(yīng)該在轉(zhuǎn)發(fā)文章里加上本文的鏈接

譯者:britzlieg

英文原文鏈接

作為Swift中最重要的特性之一工扎,泛型使用起來很巧妙捉兴。很多人都不太能理解并使用泛型臭胜,特別是應(yīng)用開發(fā)者。泛型最適合libraries, frameworks, and SDKs的開發(fā)魂迄。在這篇文章中粗截,我將用不同于其他教程的角度來講解泛型。我們將使用餐館的例子捣炬,這個餐館能從SwiftCity的城市理事會中獲得授權(quán)熊昌。為了保持簡潔,我將內(nèi)容控制在以下四個主題:

  • 1湿酸、泛型函數(shù)和泛型類型
  • 2婿屹、關(guān)聯(lián)類型協(xié)議
  • 3、泛型的Where語句
  • 4推溃、泛型下標(biāo)

我們接下來看看具體怎么做昂利!

泛型函數(shù)和泛型類型

開一家Swift餐館

讓我們新開張一家餐館。當(dāng)開張的時候铁坎,我們不僅關(guān)注餐館的結(jié)構(gòu)蜂奸,也關(guān)注來自城市理事會的授權(quán)。更重要的硬萍,我們將關(guān)注我們的業(yè)務(wù)窝撵,以便于它功能化和有利可圖。首先襟铭,怎么讓一家公司怎么看上去像一個理事會碌奉?一個公司應(yīng)該要有一些基礎(chǔ)的功能。


protocol Company {
  func buy(product: Product, money: Money)
  func sell(product: Product.Type, money: Money) -> Product?
}

buy函數(shù)把商品添加到庫存中寒砖,并花費(fèi)公司相應(yīng)的現(xiàn)金赐劣。sell函數(shù)創(chuàng)建/查找所需花費(fèi)的該類型商品,并返回出售的商品哩都。

泛型函數(shù)

在這個協(xié)議中魁兼,Product如果是一個確定的類型的話不太好。把每一個product統(tǒng)一成一個確定的商品類型是不可能的漠嵌。每個商品都有自己的功能咐汞,屬性等盖呼。在這些各種類型的函數(shù)中,使用一個確定的類型是一個壞主意化撕。讓我們回到理事會那里看看几晤。總而言之植阴,不管是哪個公司蟹瘾,它都需要購買和賣出商品。所以掠手,理事會必須找到適合這兩個功能的一種通用的解決方案憾朴,以適合于每家公司。他們可以使用泛型來解決這個問題喷鸽。

protocol Company {
  func buy<T>(product: T, with money: Money)
  func sell<T>(product: T.Type, for money: Money) -> T?
}

我們把我們原來的確定類型Product用默認(rèn)類型T來代替众雷。這個類型參數(shù)<T>把這些函數(shù)定義成泛型。在編譯時做祝,默認(rèn)類型會被確定類型替代砾省。當(dāng)buy和sell函數(shù)被調(diào)用時,具體類型就會被確定下來剖淀。這使得不同產(chǎn)品能靈活使用同一個函數(shù)纯蛾。例如,我們在Swift餐館中賣Penne Arrabiata纵隔。我們可以像下面一樣直接調(diào)用sell函數(shù):

let penneArrabiata = swiftRestaurant.sell(product: PenneArrabiata.Self, for: Money(value:7.0, currency: .dollar))

在編譯時翻诉,編譯器用類型PenneArrabiata替換類型T。當(dāng)這個方法在運(yùn)行時被調(diào)用的時候捌刮,它已經(jīng)時有一個確定的類型PenneArrabiata而不是一個默認(rèn)的類型碰煌。但這帶來另外一個問題,我們不能只是簡單的買賣各種類型的商品绅作,還要定義哪些商品時能夠被合法買賣芦圾。這里就引入where類型約束。理事會有另一個協(xié)議LegallyTradable俄认。它將檢查和標(biāo)記我們可以合法買賣的商品个少。理事會強(qiáng)制我們對所有買賣實(shí)行這個協(xié)議,并列舉每一個符合協(xié)議的從商品眯杏。所以我們需要為我們的泛型函數(shù)添加約束夜焦,以限制只能買賣符合協(xié)議的商品。

protocol Company {
  func buy<T: LegallyTradable>(product: T, with money: Money)
  func sell<T: LegallyTradable>(product: T.Type, for money: Money) -> T?
}

現(xiàn)在岂贩,我們可以放心用這些函數(shù)了茫经。通常,我們把符合LegallyTradable協(xié)議的默認(rèn)類型T作為我們Company協(xié)議函數(shù)的參數(shù)。這個約束被叫做Swift中的協(xié)議約束卸伞。如果一個商品不遵循這個協(xié)議抹镊,它將不能作為這個函數(shù)的參數(shù)。

泛型類型

我們把注意力轉(zhuǎn)移到我們的餐館上荤傲。我們得到授權(quán)并準(zhǔn)備關(guān)注餐館的管理垮耳。我們聘請了一位出色的經(jīng)理和她想建立一套能跟蹤商品庫存的系統(tǒng)。在我們的餐館中弃酌,我們有一個面食菜單氨菇,顧客喜歡各種各樣的面食儡炼。這就是我們?yōu)槭裁葱枰粋€很大的地方去存儲面食妓湘。我們創(chuàng)建一個面食套餐列表,當(dāng)顧客點(diǎn)套餐的時候乌询,將套餐從列表中移除榜贴。無論何時,餐館會買面食套餐妹田,并把它加到我們的列表中唬党。最后,如果列表中的套餐少于三個鬼佣,我們的經(jīng)理將訂新的套餐驶拱。這是我們的PastaPackageList結(jié)構(gòu):

struct PastaPackageList {
  var packages: [PastaPackage]
 
  mutating func add(package: PastaPackage) {
    packages.append(item)
  }
 
  mutating func remove() -> PastaPackage {
    return packages.removeLast()
  }
 
  func isCapacityLow() -> Bool {
    return packages.count < 3
  }
}

過了一會,我們的經(jīng)理開始考慮為餐館中的每一樣商品創(chuàng)建一個列表晶衷,以便更好的跟蹤蓝纲。與其每次創(chuàng)建獨(dú)立列表結(jié)構(gòu),不如用泛型來避免這個問題晌纫。如果我們定義我們的庫存列表作為一個泛型類税迷,我們可以很容易使用同樣的結(jié)構(gòu)實(shí)現(xiàn)創(chuàng)建新的庫存列表。與泛型函數(shù)一樣锹漱,使用參數(shù)類型<T>定義我們的結(jié)構(gòu)箭养。所以我們需要用T默認(rèn)類型來替代PastaPackage具體類型

struct InventoryList<T> {
  var items: [T]
  
  mutating func add(item: T) {
    items.append(item)
  }
 
  mutating func remove() -> T {
    return items.removeLast()
  }
  
  func isCapacityLow() -> Bool {
    return items.count < 3
  }
}

這些泛型類型讓我們可以為每個商品創(chuàng)建不同的庫存列表,而且使用一樣的實(shí)現(xiàn)哥牍。

var pastaInventory = InventoryList<PastaPackage>()
pastaInventory.add(item: PastaPackage())
var tomatoSauceInventory = InventoryList<TomatoSauce>()
var flourSackInventory = InventoryList<FlourSack>()

泛型的另外一個優(yōu)勢是只要我們的經(jīng)理需要額外的信息毕泌,例如庫存中的第一種商品,我們都可以通過使用擴(kuò)展來添加功能嗅辣。Swift允許我們?nèi)懡Y(jié)構(gòu)體撼泛,類和協(xié)議的擴(kuò)展。因?yàn)榉盒偷臄U(kuò)展性辩诞,當(dāng)我們定義結(jié)構(gòu)體時坎弯,不需要提供類型參數(shù)。在擴(kuò)展中,我們?nèi)匀挥媚J(rèn)類型抠忘。讓我們看看我們?nèi)绾螌?shí)現(xiàn)我們經(jīng)理的需求撩炊。

extension InventoryList { // We define it without any type parameters
  var topItem: T? {
    return items.last
  }
}

InventoryList中存在類型參數(shù)T作為類型topItem的遵循類型,而不需要再定義類型參數(shù)∑槁觯現(xiàn)在我們有所有商品的庫存列表拧咳。因?yàn)槊總€餐館都要從理事會中獲取授權(quán)去長時間存儲商品,我們依然沒有一個存儲的地方囚灼。所以骆膝,我們把我們的關(guān)注點(diǎn)放到理事會上。

關(guān)聯(lián)類型協(xié)議

我們再次回去到城市理事會去獲取存儲食物的允許灶体。理事會規(guī)定了一些我們必須遵守的規(guī)則阅签。例如,每家有倉庫的餐館都要自己清理自己的倉庫和把一些特定的食物彼此分開蝎抽。同樣政钟,理事會可以隨時檢查每間餐館的庫存。他們提供了每個倉庫都要遵循的協(xié)議樟结。這個協(xié)議不能針對特定的餐館养交,因?yàn)閭}庫物品可以改變成各種商品,并提供給餐館瓢宦。在Swift中碎连,泛型協(xié)議一般用關(guān)聯(lián)類型。讓我們看看理事會的倉庫協(xié)議是怎么樣的驮履。

protocol Storage {
  associatedtype Item
  var items: [Item] { set get }
  mutating func add(item: Item)
  var size: Int { get }
  mutating func remove() -> Item
  func showCurrentInventory() -> [Item]
}

Storage協(xié)議并沒有規(guī)定物品怎么存儲和什么類型被允許存儲鱼辙。在所有商店,實(shí)現(xiàn)了Storage協(xié)議的餐館必須制定一種他們他們存儲的特定類型的商品疲吸。這要保證物品從倉庫中添加和移除的正確性座每。同樣的,它必須能夠完整展示當(dāng)前倉庫摘悴。所以峭梳,對于我們的倉庫,我們的Storage協(xié)議如下所示:

struct SwiftRestaurantStorage: Storage {
  typealias Item = Food // Optional
  var items = [Food]()
  var size: Int { return 100 }
  mutating func add(item: Food) { ... }
  mutating func remove() -> Food { ... }
  func showCurrentInventory() -> [Food] { ... }
}

我們實(shí)現(xiàn)理事會的Storage協(xié)議□逵鳎現(xiàn)在看來葱椭,關(guān)聯(lián)類型Item可以用我們的Food類型來替換。我們的餐館倉庫都可以存儲Food口四。關(guān)聯(lián)類型Item只是一個協(xié)議的默認(rèn)類型孵运。我們用typealias關(guān)鍵字來定義類型。但是蔓彩,需要指出的是治笨,這個關(guān)鍵字在Swift中是可選的驳概。即使我們不用typealias關(guān)鍵字,我們依然可以用Food替換協(xié)議中所有用到Item的地方旷赖。Swift會自動處理這個顺又。

限制關(guān)聯(lián)類型為特定類型

事實(shí)上,理事會總是會想出一些新的規(guī)則并強(qiáng)制你去遵守等孵。一會后稚照,理事會改變了Storage協(xié)議。他們宣布他們將不允許任何物品在Storage俯萌。所有物品必須遵循StorableItem協(xié)議果录,以保證他們都適合存儲。換句話咐熙,它們都限制為關(guān)聯(lián)類型Item

protocol Storage {
  associatedtype Item: StorableItem // Constrained associated type
  var items: [Item] { set get }
  var size: Int { get }
  mutating func add(item: Item)
  mutating func remove() -> Item
  func showCurrentInventory() -> [Item]
}

用這個方法弱恒,理事會限制類型為當(dāng)前關(guān)聯(lián)類型。任何實(shí)現(xiàn)Storage協(xié)議的都必須使用實(shí)現(xiàn)StorableItem協(xié)議的類型糖声。

泛型的Where語句

使用泛型的Where語句的泛型

讓我們回到文章剛開始的時候斤彼,看看Company協(xié)議中的Money類型分瘦。當(dāng)我們討論到協(xié)議時蘸泻,買賣中的money參數(shù)事實(shí)上是一個協(xié)議。

protocol Money {
  associatedtype Currency
  var currency: Currency { get }
  var amount: Float { get }
  func sum<M: Money>(with money: M) -> M where M.Currency == Currency
}

然后嘲玫,再過了一會悦施,理事會打回了這個協(xié)議,因?yàn)樗麄冇辛硪粋€規(guī)則去团。從現(xiàn)在開始抡诞,交易只能用一些特定的貨幣。在這個之前土陪,我們能各種用Money類型的貨幣昼汗。不同于每種貨幣定義money類型的做法,他們決定用Money協(xié)議來改變他們的買賣函數(shù)鬼雀。

protocol Company {
  func buy<T: LegallyTradable, M: Money>(product: T.Type, with money: M) -> T? where M.Currency: TradeCurrency
  func sell<T: LegallyTradable, M: Money>(product: T, for money: M) where M.Currency: TradeCurrency
}

where語句和類型約束的where語句的區(qū)別在于顷窒,where語句會被用于定義關(guān)聯(lián)類型。換句話源哩,在協(xié)議中鞋吉,我們不能限制關(guān)聯(lián)的類型,而會在使用協(xié)議的時候限制它励烦。

泛型的where語句的擴(kuò)展

泛型的where語句在擴(kuò)展中有其他用法谓着。例如,當(dāng)理事會要求用漂亮的格式(例如“xxx EUR”)打印money時坛掠,他們只需要添加一個Money的擴(kuò)展赊锚,并把Currency限制設(shè)置成```Euro``治筒。

extension Money where Currency == Euro {
  func printAmount() {
    print("\(amount) EUR")
  }
}

泛型的where語句允許我們添加一個新的必要條件到Money擴(kuò)展中,因此只有當(dāng)CurrencyEuro時舷蒲,擴(kuò)展才會添加printAmount()方法矢炼。

泛型的where 語句的關(guān)聯(lián)類型

在上文中,理事會給Storage協(xié)議做了一些改進(jìn)阿纤。當(dāng)他們想檢查一切是否安好句灌,他們想列出每一樣物品,并控制他們欠拾∫刃浚控制進(jìn)程對于每個Item是不一樣的。因?yàn)檫@樣藐窄,理事會僅僅需要提供Iterator關(guān)聯(lián)類型到Storage協(xié)議中资昧。

protocol Storage {
  associatedtype Item: StorableItem
  var items: [Item] { set get }
  var size: Int { get }
  mutating func add(item: Item)
  mutating func remove() -> Item
  func showCurrentInventory() -> [Item]
 
  associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
  func makeIterator() -> Iterator
}

Iterator協(xié)議有一個叫Element``的關(guān)聯(lián)類型。在這里荆忍,我們給它加上一個必要條件格带,在Storage協(xié)議中,Element必須與Item```類型相等刹枉。

泛型下標(biāo)

來自經(jīng)理和理事會的需求看起來是無窮無盡的叽唱。同樣的,我們需要滿足他們的要求微宝。我們的經(jīng)理跑過來跟我們說她想要用一個Sequence來訪問存儲的物品棺亭,而不需要訪問所有的物品。經(jīng)理想要個語法糖蟋软。

extension Storage {
  subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int {
    var result = [Item]()
    for index in indices {
      result.append(self.items[index])
    }
    return result
  }
}

在Swift 4中镶摘,下標(biāo)也可以是泛型,我們可以用條件泛型來實(shí)現(xiàn)岳守。在我們的使用中凄敢,indices參數(shù)必須實(shí)現(xiàn)Sequence協(xié)議。從Apple doc中可以知道湿痢,“The generic where clause requires that the iterator for the sequence must traverse over elements of type Int.”這就保證了在sequence的indices跟存儲中的indices是一致的涝缝。

結(jié)語

我們讓我們的餐館功能完備。我們的經(jīng)理和理事會看起來也很高興蒙袍。正如我們在文章中看到的俊卤,泛型是很強(qiáng)大的。我們可以用泛型來滿足各種敏感的需求害幅,只要我們知道概念消恍。泛型在Swift的標(biāo)準(zhǔn)庫中也應(yīng)用廣泛。例如以现,ArrayDictionary類型都是泛型集合狠怨。如果你想知道更多约啊,你可以看看這些類是怎么實(shí)現(xiàn)的。 Swift Language Doc 也提供了泛型的解析佣赖。最近Swift語言提供了泛型的一些說明Generic Manifesto恰矩。我建議你去看完所有的文檔,以便更好的理解當(dāng)前用法和未來的規(guī)劃憎蛤。感謝大家的閱讀外傅!如果你對接下來的文章有疑惑,建議俩檬,評論或者是想法萎胰,清在 Twitter 聯(lián)系我,或者評論棚辽!你也可以在GitHub上關(guān)注我哦技竟!

本文Github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屈藐,隨后出現(xiàn)的幾起案子榔组,更是在濱河造成了極大的恐慌,老刑警劉巖联逻,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搓扯,死亡現(xiàn)場離奇詭異,居然都是意外死亡遣妥,警方通過查閱死者的電腦和手機(jī)擅编,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箫踩,“玉大人,你說我怎么就攤上這事谭贪【持樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵俭识,是天一觀的道長慨削。 經(jīng)常有香客問我,道長套媚,這世上最難降的妖魔是什么缚态? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮堤瘤,結(jié)果婚禮上玫芦,老公的妹妹穿的比我還像新娘。我一直安慰自己本辐,他們只是感情好桥帆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布医增。 她就那樣靜靜地躺著,像睡著了一般老虫。 火紅的嫁衣襯著肌膚如雪叶骨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天祈匙,我揣著相機(jī)與錄音忽刽,去河邊找鬼。 笑死夺欲,一個胖子當(dāng)著我的面吹牛缔恳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洁闰,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歉甚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扑眉?” 一聲冷哼從身側(cè)響起纸泄,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腰素,沒想到半個月后聘裁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弓千,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年衡便,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洋访。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡镣陕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姻政,到底是詐尸還是另有隱情呆抑,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布汁展,位于F島的核電站鹊碍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏食绿。R本人自食惡果不足惜侈咕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望器紧。 院中可真熱鬧耀销,春花似錦、人聲如沸品洛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帽揪,卻和暖如春硝清,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背转晰。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工芦拿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人查邢。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓蔗崎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扰藕。 傳聞我的和親對象是個殘疾皇子缓苛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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

  • 本章將會介紹 泛型所解決的問題泛型函數(shù)類型參數(shù)命名類型參數(shù)泛型類型擴(kuò)展一個泛型類型類型約束關(guān)聯(lián)類型泛型 Where...
    寒橋閱讀 639評論 0 2
  • 原文:Generics Manifesto -- Douglas Gregor 譯者注 在我慢慢地深入使用 Swi...
    kemchenj閱讀 1,988評論 0 6
  • 泛型代碼可以確保你寫出靈活的,可重用的函數(shù)和定義出任何你所確定好的需求的類型邓深。你的可以寫出避免重復(fù)的代碼未桥,并且用一...
    iOS_Developer閱讀 800評論 0 0
  • 泛型(Generics) 泛型代碼允許你定義適用于任何類型的,符合你設(shè)置的要求的,靈活且可重用的 函數(shù)和類型。泛型...
    果啤閱讀 678評論 0 0
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束芥备。你可以寫出...
    無灃閱讀 1,472評論 0 4