版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.07.12 星期五 |
前言
這個專題我們就一起看一下Swfit相關的基礎知識荧嵌。
開始
首先看下主要內容
主要內容:學習編寫函數和數據類型挣跋,同時做出最小的假設衷咽。 Swift泛型允許更少的bug和更加干凈清晰的代碼。
接著看下寫作環(huán)境
Swift 5, iOS 13, Xcode 11
Generic programming是一種編寫函數和數據類型的方法谁撼,同時對所使用的數據類型做出最小的假設歧胁。 Swift泛型創(chuàng)建的代碼沒有特定于底層數據類型,允許優(yōu)雅的抽象彤敛,產生更少的錯誤更清晰的代碼与帆。 它允許您編寫一次函數并在不同類型上使用它。
你會發(fā)現Swift中使用的泛型墨榄,這使得理解它們對完全掌握語言至關重要玄糟。 您將在Swift中遇到的通用示例是Optional
類型。 您可以選擇任何您想要的數據類型袄秩,當然也包括您自己創(chuàng)建的類型阵翎。 換句話說,Optional
數據類型在其可能包含的值類型上是通用的之剧。
在本教程中郭卫,您將在Swift playground
中進行實驗以了解:
What exactly generics are
Why they are useful
How to write generic functions and data structures
How to use type constraints
How to extend generic types
首先要創(chuàng)建一個新的playground
。 在Xcode中背稼,轉到File ? New ? Playground…
選擇macOS ? Blank
模板贰军。 單擊Next
并命名Playground為Generics
。 最后蟹肘,單擊Create
词疼!
作為居住在遙遠的王國的為數不多的程序員之一,你被召喚到皇家城堡幫助女王帘腹,這是一件非常重要的事情贰盗。 她已經忘記了自己擁有多少皇家科目,并且在計算方面需要一些幫助阳欲。
她要求編寫一個相加兩個整數的函數舵盈。 將以下內容添加到新創(chuàng)建的playground
:
func addInts(x: Int, y: Int) -> Int {
return x + y
}
addInts(x:y :)
獲取兩個Int
值并返回它們的總和。 您可以通過將以下代碼添加到playground
上來嘗試:
let intSum = addInts(x: 1, y: 2)
這是一個簡單的例子球化,展示了Swift的類型安全性秽晚。 您可以使用兩個整數調用此函數,但不能使用任何其他類型赊窥。
女王很高興爆惧,并立即要求另外一個add
函數來計算她的財富 - 這個時候,添加Double
值锨能。 創(chuàng)建第二個函數addDoubles(x:y :)
:
func addDoubles(x: Double, y: Double) -> Double {
return x + y
}
let doubleSum = addDoubles(x: 1.0, y: 2.0)
addInts
和addDoubles
的函數簽名是不同的扯再,但函數體是相同的芍耘。 你不僅有兩個函數,而且它們里面的代碼是重復的熄阻。 泛型Generics
可用于將這兩個函數減少為一個并刪除冗余代碼斋竞。
但是,首先秃殉,您將了解日常Swift中常見編程的一些常見情況坝初。
Other Examples of Swift Generics
您可能沒有意識到,但您使用的一些最常見的結構钾军,例如數組鳄袍,字典,選項和結果都是泛型類型吏恭!
1. Arrays
將以下內容添加到您的playground
:
let numbers = [1, 2, 3]
let firstNumber = numbers[0]
在這里拗小,您創(chuàng)建一個包含三個數字的簡單數組,然后從該數組中取出第一個數字樱哼。
現在按住Option
鍵單擊哀九,首先是numbers
,然后是firstNumber
搅幅。 你看到了什么阅束?
因為Swift
具有類型推斷 type inference,所以您不必顯式定義常量的類型茄唐,但它們都具有確切的類型息裸。 numbers
是一個[Int]
- 也就是一個整數數組 - 而firstNumber
是一個Int
。
Swift Array
類型是泛型類型沪编。 通用類型都至少有一個類型參數界牡,一個尚未指定的其他類型的占位符。 您需要指定其他類型才能專門化泛型類型并實際創(chuàng)建它的實例漾抬。
例如,Array
的type參數確定數組中的內容常遂。 您的數組是專用的纳令,因此它只能包含Int
值。 這支持Swift的類型安全性克胳。 當你從那個數組中刪除任何東西時平绩,Swift - 更重要的是你 - 知道它必須是一個Int
。
通過向playground
添加稍長版本的相同代碼漠另,您可以更好地了解Array
的一般特性:
var numbersAgain: Array<Int> = []
numbersAgain.append(1)
numbersAgain.append(2)
numbersAgain.append(3)
let firstNumberAgain = numbersAgain[0]
通過Option-click
來檢查numbersAgain
和firstNumberAgain
的類型捏雌;類型將與以前的值完全相同。 在這里笆搓,您可以使用顯式通用語法指定numbersAgain
的類型性湿,方法是將Int
放在Array
之后的尖括號中纬傲。 您已將Int
作為type
參數的顯式類型參數提供。
嘗試在數組中添加其他內容肤频,如String
:
numbersAgain.append("All hail Lord Farquaad")
您將收到錯誤 - 類似于:Cannot convert value of type ‘String’ to expected argument type ‘Int’
叹括。 編譯器告訴您不能將字符串添加到整數數組。 作為泛型類型Array
的一種方法宵荒,append
是一種所謂的泛型方法汁雷。 因為此數組實例是專用類型Array <Int>
,所以它的append
方法現在也專門用于append(_ newElement:Int)
报咳。 它不會讓你添加一些不正確的類型侠讯。
刪除導致錯誤的行。 接下來暑刃,您將看到標準庫中另一個泛型示例厢漩。
2. Dictionaries
字典也是泛型類型,并導致類型安全的數據結構稍走。
在playground
末尾創(chuàng)建以下魔法王國詞典袁翁,然后查找Freedonia的國家代碼:
let countryCodes = ["Arendelle": "AR", "Genovia": "GN", "Freedonia": "FD"]
let countryCode = countryCodes["Freedonia"]
檢查兩個聲明的類型。 您將看到countryCodes
是String
鍵和String
值的字典 - 此字典中沒有任何其他內容婿脸。 形式泛型類型是Dictionary
粱胜。
3. Optionals
在上面的示例中,請注意countryCode
的類型是String狐树?
焙压。 這實際上只是Optional
的簡寫。
如果<and>
看起來很熟悉抑钟,那是因為甚至Optional
是泛型類型涯曲。 泛型到處都是!
這里編譯器強制您只能使用字符串鍵訪問字典在塔,并且始終返回返回的字符串值幻件。 可選類型用于表示countryCode
,因為可能沒有與該鍵對應的值蛔溃。 例如绰沥,如果您嘗試查找The Emerald City
,則countryCode
的值將為nil
贺待,因為它不存在于您的魔法王國詞典中徽曲。
將以下內容添加到您的playground
,以查看創(chuàng)建可選字符串的完整顯式語法:
let optionalName = Optional<String>.some("Princess Moana")
if let name = optionalName {}
檢查name
的類型麸塞,您將看到它是String
秃臣。
Optional binding 可選綁定,即if-let
構造哪工,是對各種類型的泛型轉換奥此。 它需要T?
類型的通用值弧哎,并為您提供類型為T
的通用值。這意味著您可以使用if let
到任何具體類型得院。
4. Results
Result
是Swift 5
中的一個新類型傻铣。與Optional
一樣,它是一個包含兩個case
的通用枚舉祥绞。 結果要么取而代之非洲,要么success
,要么failure
蜕径。 每個case
都有自己的關聯(lián)泛型類型两踏,success
有一個值,failure
有一個Error
兜喻。
考慮這種情況梦染,皇家魔法師招募你施放一些法術。 已知法術會生成一個符號朴皆,但未知法術會失敗帕识。 該函數看起來像這樣:
enum MagicError: Error {
case spellFailure
}
func cast(_ spell: String) -> Result<String, MagicError> {
switch spell {
case "flowers":
return .success("??")
case "stars":
return .success("?")
default:
return .failure(.spellFailure)
}
}
Result
允許您編寫返回值或錯誤的函數,而無需使用try
語法遂铡。 作為額外的好處肮疗,failure case
的通用規(guī)范意味著您不需要像使用catch
塊那樣檢查類型。 如果出現錯誤扒接,您可以確定與.failure case
案例相關聯(lián)的值中會出現MagicError
伪货。
試試一些法術來看看Result
:
let result1 = cast("flowers") // .success("??")
let result2 = cast("avada kedavra") // .failure(.spellFailure)
掌握了泛型的基礎知識,您可以學習如何編寫自己的通用數據結構和函數钾怔。
Writing a Generic Data Structure
隊列queue
是一種類似于列表或堆棧的數據結構碱呼,但是您只能向其添加新值(將它們排入隊列)并且只從前面獲取值(將它們出列)。 如果您曾經使用過OperationQueue
宗侦,那么這個概念可能會很熟悉愚臀。
女王,對你在本教程前面的努力感到高興矾利,現在希望你能夠編寫函數懊悯,以幫助跟蹤等待與她交談的皇室成員。
將以下struct
聲明添加到playground
的末尾:
struct Queue<Element> {
}
Queue
是泛型類型梦皮,在其泛型參數子句中具有類型參數Element
。 另一種說法是桃焕,Queue
在類型Element
上是通用的剑肯。 例如,Queue <Int>
和Queue <String>
將在運行時成為它們自己的具體類型观堂,它們只能分別對字符串和整數進行入隊和出列让网。
將以下屬性添加到隊列中:
private var elements: [Element] = []
您將使用此數組來保存元素呀忧,您將其初始化為空數組。 請注意溃睹,您可以使用Element
而账,就好像它是一個真實的類型,即使它稍后會被填充因篇。 您將其標記為private
泞辐,因為您不希望Queue
的使用者訪問elements
。 您希望強制它們使用方法來訪問后備存儲竞滓。
最后咐吼,實現兩個主要的隊列方法:
mutating func enqueue(newElement: Element) {
elements.append(newElement)
}
mutating func dequeue() -> Element? {
guard !elements.isEmpty else { return nil }
return elements.remove(at: 0)
}
同樣,類型參數Element
在struct
中的任何位置都可用商佑,包括內部方法锯茄。 使類型通用就像使其每個方法在同一類型上隱式通用。 您已經實現了類型安全的通用數據結構茶没,就像標準庫中的那樣肌幽。
在playground
底部玩一下你的新數據結構,通過將他們的royal id
添加到隊列中來排隊等待主題:
var q = Queue<Int>()
q.enqueue(newElement: 4)
q.enqueue(newElement: 2)
q.dequeue()
q.dequeue()
q.dequeue()
q.dequeue()
通過故意制造盡可能多的錯誤來觸發(fā)與泛型相關的不同錯誤消息抓半,從而獲得一些樂趣 - 例如喂急,在隊列中添加一個字符串。 您現在對這些錯誤了解得越多琅关,就越容易在更復雜的項目中識別和處理它們煮岁。
Writing a Generic Function
女王有很多要處理的數據,她要求你編寫的下一段代碼將采用鍵和值的字典并將其轉換為列表涣易。
將以下函數添加到playground
的底部:
func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)] {
return Array(dictionary)
}
仔細看看函數聲明画机,參數列表和返回類型。
該函數對于您已命名為Key
和Value
的兩種類型是通用的新症。 唯一的參數是具有Key
和Value
類型的鍵值對的字典步氏。 返回值是表單元組的數組 - 你猜對了 - (Key,Value)
徒爹。
您可以在任何有效字典上使用pairs(from:)
荚醒,并且它將起作用,這要歸功于泛型:
let somePairs = pairs(from: ["minimum": 199, "maximum": 299])
// result is [("maximum", 299), ("minimum", 199)]
let morePairs = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"])
// result is [(1, "Swift"), (2, "Generics"), (3, "Rule")]
當然隆嗅,由于你無法控制字典項進入數組的順序界阁,你可能會在playground
上看到一個元組值的順序更像“Generics”, “Rule”, “Swift”
,實際上胖喳,它們那樣做泡躯!
在運行時,每個可能的Key
和Value
將作為一個單獨的函數,填充函數聲明和正文中的具體類型较剃。對pair(from :)
的第一次調用返回一個(String咕别,Int)
元組數組。第二個調用在元組中使用翻轉的類型順序写穴,并返回(Int惰拱,String)
元組的數組。
您創(chuàng)建了一個可以使用不同的調用返回不同類型的函數啊送。您可以看到如何將邏輯保存在一個位置可以簡化代碼偿短。您需要使用一個函數處理兩個調用,而不需要兩個不同的函數删掀。
現在您已經了解了創(chuàng)建和使用泛型類型和函數的基礎知識翔冀,現在是時候繼續(xù)使用一些更高級的功能了。您已經看到了泛型如何按類型限制事物披泪,但您可以添加其他約束以及擴展泛型類型以使它們更有用纤子。
Constraining a Generic Type
為了分析一小部分她最忠誠的主題的年齡,女王要求一個函數來排序數組并找到中間值款票。
將以下函數添加到playground
時:
func mid<T>(array: [T]) -> T? {
guard !array.isEmpty else { return nil }
return array.sorted()[(array.count - 1) / 2]
}
你會收到一個錯誤控硼。 問題是要使sorted()
工作,數組的元素必須是Comparable
艾少。 只要元素類型實現Comparable
卡乾,你需要以某種方式告訴Swift mid
可以接受任何數組。
將函數聲明更改為以下內容:
func mid<T: Comparable>(array: [T]) -> T? {
guard !array.isEmpty else { return nil }
return array.sorted()[(array.count - 1) / 2]
}
在這里缚够,您使用:
語法將類型約束添加到泛型類型參數T
幔妨。您現在只能使用Comparable
元素數組調用該函數,以便sorted()
始終有效谍椅! 通過添加以下內容來嘗試約束函數:
mid(array: [3, 5, 1, 2, 4]) // 3
您在使用Result
時已經看到過這種情況:Failure
類型被限制為Error
误堡。
1. Cleaning Up the Add Functions
現在您已了解類型約束,您可以從playground
的開頭創(chuàng)建add
函數的通用版本 - 這將更加優(yōu)雅雏吭,并且請女王們非常高興锁施。 將以下協(xié)議和擴展添加到您的playground
:
protocol Summable { static func +(lhs: Self, rhs: Self) -> Self }
extension Int: Summable {}
extension Double: Summable {}
首先,您創(chuàng)建一個Summable
協(xié)議杖们,該協(xié)議表明任何符合的類型都必須具有加法運算符+
悉抵。 然后,指定Int
和Double
類型符合它摘完。
現在使用泛型參數T
和類型約束姥饰,您可以創(chuàng)建一個通用函數add
:
func add<T: Summable>(x: T, y: T) -> T {
return x + y
}
您已將兩個函數(實際上更多,因為您需要更多其他Summable
類型)減少到一個并刪除冗余代碼孝治。 您可以在整數和雙精度上使用新函數:
let addIntSum = add(x: 1, y: 2) // 3
let addDoubleSum = add(x: 1.0, y: 2.0) // 3.0
您還可以在其他類型上使用它列粪,例如字符串
extension String: Summable {}
let addString = add(x: "Generics", y: " are Awesome!!! :]")
通過向Summable
添加其他符合要求的類型栅螟,您的add(x:y :)
函數由于其泛型驅動的定義而變得更加廣泛有用!
Extending a Generic Type
一個Court Jester
一直在協(xié)助女王守護等待的皇家臣民篱竭,讓女王知道下一個主題,然后才正式問候步绸。 他透過她客廳的窗戶偷看掺逼。 您可以使用擴展來對其行為進行建模,該擴展應用于本教程前面的通用隊列類型瓤介。
擴展Queue
類型并在Queue
定義的正下方添加以下方法:
extension Queue {
func peek() -> Element? {
return elements.first
}
}
peek
返回第一個元素而不將其出列吕喘。 擴展泛型類型很容易! 泛型類型參數與原始定義的主體一樣可見刑桑。 您可以使用擴展程序查看隊列:
q.enqueue(newElement: 5)
q.enqueue(newElement: 3)
q.peek() // 5
您將看到值5
作為隊列中的第一個元素氯质,但沒有任何內容出列,并且隊列具有與以前相同數量的元素祠斧。
Royal Challenge:擴展Queue
類型以實現一個函數isHomogeneous
闻察,它檢查隊列的所有元素是否相等。 您需要在Queue
聲明中添加類型約束琢锋,以確痹可以檢查其元素是否相互相等。
Answer:
首先編輯
Queue
的定義吴超,以便Element
符合Equatable
協(xié)議:struct Queue<Element: Equatable> {
然后
compose
是你playground
底部的Homogeneous()
:extension Queue { func isHomogeneous() -> Bool { guard let first = elements.first else { return true } return !elements.contains { $0 != first } } }
最后钉嘹,測試結果:
var h = Queue<Int>() h.enqueue(newElement: 4) h.enqueue(newElement: 4) h.isHomogeneous() // true h.enqueue(newElement: 2) h.isHomogeneous() // false
Subclassing a Generic Type
Swift
具有子類泛型類的能力。 在某些情況下鲸阻,這可能很有用跋涣,例如創(chuàng)建泛型類的具體子類。
將以下泛型類添加到playground
中:
class Box<T> {
// Just a plain old box.
}
在這里定義一個Box
類鸟悴。 該box
可以包含任何內容陈辱,這就是為什么它是泛型類。 您可以通過兩種方式將Box
子類化:
- 1) 你可能想要擴展
box
的功能以及它是如何工作的遣臼,但要保持通用性性置,所以你仍然可以在box
里放任何東西; - 2) 您可能希望擁有一個專門的子類,它始終知道其中的內容揍堰。
Swift
允許兩者鹏浅。 將其添加到您的playground
:
class Gift<T>: Box<T> {
// By default, a gift box is wrapped with plain white paper
func wrap() {
print("Wrap with plain white paper.")
}
}
class Rose {
// Flower of choice for fairytale dramas
}
class ValentinesBox: Gift<Rose> {
// A rose for your valentine
}
class Shoe {
// Just regular footwear
}
class GlassSlipper: Shoe {
// A single shoe, destined for a princess
}
class ShoeBox: Box<Shoe> {
// A box that can contain shoes
}
你在這里定義了兩個Box
子類:Gift
和ShoeBox
。 Gift
是一種特殊的Box
屏歹,它是分開的隐砸,因此你可以在其上定義不同的方法和屬性,例如wrap()
蝙眶。 但是季希,它在類型上仍然具有通用性褪那,這意味著它可以包含任何內容。 Shoe
和GlassSlipper
是一種非常特殊的鞋子式塌,已經被聲明博敬,可以放在ShoeBox
的一個實例中進行交付。
在子類聲明下聲明每個類的實例:
let box = Box<Rose>() // A regular box that can contain a rose
let gift = Gift<Rose>() // A gift box that can contain a rose
let shoeBox = ShoeBox()
請注意峰尝,ShoeBox
初始化程序不再需要采用泛型類型參數偏窝,因為它已在ShoeBox
的聲明中修復。
接下來武学,聲明子類ValentinesBox
的一個新實例 - 一個包含玫瑰的盒子祭往,一個專門用于情人節(jié)的神奇禮物。
let valentines = ValentinesBox()
雖然標準盒子用白紙包裹火窒,但你希望你的節(jié)日禮物有點發(fā)燒友硼补。 將以下方法添加到ValentinesBox
:
override func wrap() {
print("Wrap with ??? paper.")
}
最后,通過將以下代碼添加到您的playground
來比較包裝這兩種類型的結果:
gift.wrap() // plain white paper
valentines.wrap() // ??? paper
ValentinesBox
雖然使用泛型構造熏矿,但它作為標準子類運行已骇,其方法可以從超類繼承和覆蓋。 多么優(yōu)雅曲掰!
Enumerations With Associated Values
女王對您的工作很滿意疾捍,并希望為您提供獎勵:您選擇的通用寶藏或獎章。
將以下聲明添加到playground
的末尾:
enum Reward<T> {
case treasureChest(T)
case medal
var message: String {
switch self {
case .treasureChest(let treasure):
return "You got a chest filled with \(treasure)."
case .medal:
return "Stand proud, you earned a medal!"
}
}
}
此語法允許您編寫枚舉栏妖,其中至少有一個case
是通用box
乱豆。 使用message
var
,您可以將值恢復吊趾。 在上面說明的Result示例中宛裕,成功和失敗案例都是通用的,具有不同的類型论泛。
要恢復關聯(lián)值揩尸,請使用如下:
let message = Reward.treasureChest("??").message
print(message)
Swift泛型是許多常見語言功能的核心,例如數組和可選項屁奏。 您已經了解了如何使用它們來構建優(yōu)雅岩榆,可重用的代碼,從而減少錯誤坟瓢。
有關更多信息勇边,請閱讀Apple指南Swift編程語言的泛型Generics章節(jié)和通用參數和參數語言 Generic Parameters and Arguments參考章節(jié)。 您將在Swift中找到有關泛型的更多詳細信息折联,以及一些方便的示例粒褒。
Swift
中的泛型是一個不可或缺的功能,您每天都會使用它來編寫功能強大且類型安全的抽象诚镰。
后記
本篇主要講述了Swift泛型相關奕坟,感興趣的給個贊或者關注~~~