Swift 4 中的泛型

這是我基於英文原文翻譯的譯文,若是你對本文感興趣並且想轉發,你應該在轉發文章里加上本文的連接html

譯者:britzlieggit

英文原文連接github

做爲Swift中最重要的特性之一,泛型使用起來很巧妙。不少人都不太能理解並使用泛型,特別是應用開發者。泛型最適合libraries, frameworks, and SDKs的開發。在這篇文章中,我將用不一樣於其餘教程的角度來說解泛型。咱們將使用餐館的例子,這個餐館能從SwiftCity的城市理事會中得到受權。爲了保持簡潔,我將內容控制在如下四個主題:swift

  • 一、泛型函數和泛型類型
  • 二、關聯類型協議
  • 三、泛型的Where語句
  • 四、泛型下標

咱們接下來看看具體怎麼作!app

泛型函數和泛型類型

開一家Swift餐館

讓咱們新開張一家餐館。當開張的時候,咱們不只關注餐館的結構,也關注來自城市理事會的受權。更重要的,咱們將關注咱們的業務,以便於它功能化和有利可圖。首先,怎麼讓一家公司怎麼看上去像一個理事會?一個公司應該要有一些基礎的功能。函數

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

buy函數把商品添加到庫存中,並花費公司相應的現金。sell函數建立/查找所需花費的該類型商品,並返回出售的商品。post

泛型函數

在這個協議中,Product若是是一個肯定的類型的話不太好。把每個product統一成一個肯定的商品類型是不可能的。每一個商品都有本身的功能,屬性等。在這些各類類型的函數中,使用一個肯定的類型是一個壞主意。讓咱們回到理事會那裏看看。總而言之,不論是哪一個公司,它都須要購買和賣出商品。因此,理事會必須找到適合這兩個功能的一種通用的解決方案,以適合於每家公司。他們可使用泛型來解決這個問題。ui

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

咱們把咱們原來的肯定類型Product用默認類型T來代替。這個類型參數<T>把這些函數定義成泛型。在編譯時,默認類型會被肯定類型替代。當buy和sell函數被調用時,具體類型就會被肯定下來。這使得不一樣產品能靈活使用同一個函數。例如,咱們在Swift餐館中賣Penne Arrabiata。咱們能夠像下面同樣直接調用sell函數:spa

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

在編譯時,編譯器用類型PenneArrabiata替換類型T。當這個方法在運行時被調用的時候,它已經時有一個肯定的類型PenneArrabiata而不是一個默認的類型。但這帶來另一個問題,咱們不能只是簡單的買賣各類類型的商品,還要定義哪些商品時可以被合法買賣。這裏就引入where類型約束。理事會有另外一個協議LegallyTradable。它將檢查和標記咱們能夠合法買賣的商品。理事會強制咱們對全部買賣實行這個協議,並列舉每個符合協議的從商品。因此咱們須要爲咱們的泛型函數添加約束,以限制只能買賣符合協議的商品。翻譯

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

如今,咱們能夠放心用這些函數了。一般,咱們把符合LegallyTradable協議的默認類型T做爲咱們Company協議函數的參數。這個約束被叫作Swift中的協議約束。若是一個商品不遵循這個協議,它將不能做爲這個函數的參數。

泛型類型

咱們把注意力轉移到咱們的餐館上。咱們獲得受權並準備關注餐館的管理。咱們聘請了一位出色的經理和她想創建一套能跟蹤商品庫存的系統。在咱們的餐館中,咱們有一個麪食菜單,顧客喜歡各類各樣的麪食。這就是咱們爲何須要一個很大的地方去存儲麪食。咱們建立一個麪食套餐列表,當顧客點套餐的時候,將套餐從列表中移除。不管什麼時候,餐館會買麪食套餐,並把它加到咱們的列表中。最後,若是列表中的套餐少於三個,咱們的經理將訂新的套餐。這是咱們的PastaPackageList結構:

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
  }
}複製代碼

過了一會,咱們的經理開始考慮爲餐館中的每同樣商品建立一個列表,以便更好的跟蹤。與其每次建立獨立列表結構,不如用泛型來避免這個問題。若是咱們定義咱們的庫存列表做爲一個泛型類,咱們能夠很容易使用一樣的結構實現建立新的庫存列表。與泛型函數同樣,使用參數類型<T>定義咱們的結構。因此咱們須要用T默認類型來替代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
  }
}複製代碼

這些泛型類型讓咱們能夠爲每一個商品建立不一樣的庫存列表,並且使用同樣的實現。

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

泛型的另一個優點是隻要咱們的經理須要額外的信息,例如庫存中的第一種商品,咱們均可以經過使用擴展來添加功能。Swift容許咱們去寫結構體,類和協議的擴展。由於泛型的擴展性,當咱們定義結構體時,不須要提供類型參數。在擴展中,咱們仍然用默認類型。讓咱們看看咱們如何實現咱們經理的需求。

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

InventoryList中存在類型參數T做爲類型topItem的遵循類型,而不須要再定義類型參數。如今咱們有全部商品的庫存列表。由於每一個餐館都要從理事會中獲取受權去長時間存儲商品,咱們依然沒有一個存儲的地方。因此,咱們把咱們的關注點放到理事會上。

關聯類型協議

咱們再次回去到城市理事會去獲取存儲食物的容許。理事會規定了一些咱們必須遵照的規則。例如,每家有倉庫的餐館都要本身清理本身的倉庫和把一些特定的食物彼此分開。一樣,理事會能夠隨時檢查每間餐館的庫存。他們提供了每一個倉庫都要遵循的協議。這個協議不能針對特定的餐館,由於倉庫物品能夠改變成各類商品,並提供給餐館。在Swift中,泛型協議通常用關聯類型。讓咱們看看理事會的倉庫協議是怎麼樣的。

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協議並無規定物品怎麼存儲和什麼類型被容許存儲。在全部商店,實現了Storage協議的餐館必須制定一種他們他們存儲的特定類型的商品。這要保證物品從倉庫中添加和移除的正確性。一樣的,它必須可以完整展現當前倉庫。因此,對於咱們的倉庫,咱們的Storage協議以下所示:

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] { ... }
}複製代碼

咱們實現理事會的Storage協議。如今看來,關聯類型Item能夠用咱們的Food類型來替換。咱們的餐館倉庫均可以存儲Food。關聯類型Item只是一個協議的默認類型。咱們用typealias關鍵字來定義類型。可是,須要指出的是,這個關鍵字在Swift中是可選的。即便咱們不用typealias關鍵字,咱們依然能夠用Food替換協議中全部用到Item的地方。Swift會自動處理這個。

限制關聯類型爲特定類型

事實上,理事會老是會想出一些新的規則並強制你去遵照。一會後,理事會改變了Storage協議。他們宣佈他們將不容許任何物品在Storage。全部物品必須遵循StorableItem協議,以保證他們都適合存儲。換句話,它們都限制爲關聯類型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]
}複製代碼

用這個方法,理事會限制類型爲當前關聯類型。任何實現Storage協議的都必須使用實現StorableItem協議的類型。

泛型的Where語句

使用泛型的Where語句的泛型

讓咱們回到文章剛開始的時候,看看Company協議中的Money類型。當咱們討論到協議時,買賣中的money參數事實上是一個協議。

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

而後,再過了一會,理事會打回了這個協議,由於他們有另外一個規則。從如今開始,交易只能用一些特定的貨幣。在這個以前,咱們能各類用Money類型的貨幣。不一樣於每種貨幣定義money類型的作法,他們決定用Money協議來改變他們的買賣函數。

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語句的區別在於,where語句會被用於定義關聯類型。換句話,在協議中,咱們不能限制關聯的類型,而會在使用協議的時候限制它。

泛型的where語句的擴展

泛型的where語句在擴展中有其餘用法。例如,當理事會要求用漂亮的格式(例如「xxx EUR」)打印money時,他們只須要添加一個Money的擴展,並把Currency限制設置成`Euro

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

泛型的where語句容許咱們添加一個新的必要條件到Money擴展中,所以只有當CurrencyEuro時,擴展才會添加printAmount()方法。

泛型的where 語句的關聯類型

在上文中,理事會給Storage協議作了一些改進。當他們想檢查一切是否安好,他們想列出每同樣物品,並控制他們。控制進程對於每一個Item是不同的。由於這樣,理事會僅僅須要提供Iterator關聯類型到Storage協議中。

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協議有一個叫Element``的關聯類型。在這裏,咱們給它加上一個必要條件,在Storage協議中,Element必須與Item```類型相等。

泛型下標

來自經理和理事會的需求看起來是無窮無盡的。一樣的,咱們須要知足他們的要求。咱們的經理跑過來跟咱們說她想要用一個Sequence來訪問存儲的物品,而不須要訪問全部的物品。經理想要個語法糖。

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中,下標也能夠是泛型,咱們能夠用條件泛型來實現。在咱們的使用中,indices參數必須實現Sequence協議。從Apple doc中能夠知道,「The generic where clause requires that the iterator for the sequence must traverse over elements of type Int.」這就保證了在sequence的indices跟存儲中的indices是一致的。

結語

咱們讓咱們的餐館功能完備。咱們的經理和理事會看起來也很高興。正如咱們在文章中看到的,泛型是很強大的。咱們能夠用泛型來知足各類敏感的需求,只要咱們知道概念。泛型在Swift的標準庫中也應用普遍。例如,ArrayDictionary類型都是泛型集合。若是你想知道更多,你能夠看看這些類是怎麼實現的。 Swift Language Doc 也提供了泛型的解析。最近Swift語言提供了泛型的一些說明Generic Manifesto。我建議你去看完全部的文檔,以便更好的理解當前用法和將來的規劃。感謝你們的閱讀!若是你對接下來的文章有疑惑,建議,評論或者是想法,清在 Twitter 聯繫我,或者評論!你也能夠在GitHub上關注我哦!

本文Github地址

相關文章
相關標籤/搜索