Swift 中的泛型使用
恭喜你,你可真棒!在不到半個時間里,你已經通過宣傳IOS 8盛宴成功解鎖了第一個Swift教程!它是Swift系列教程的一個縮水版,就先給你偷看一眼了好了,希望你能夠喜歡!
如果你已經動手寫過Swift的程序,相信你已經了解了Swift語言的知識,比如如何寫類(class)和結構體(struct)。但Swift可沒這么簡單,呵呵呵。這篇教程主要講述Swift的一個強力的特性:泛型。這個特性在很多程序設計語言里都非常受歡迎。
對于類型安全(type-safe)語言,一個常見的問題就是如何編寫適用于多種類型輸入的程序。想象一下,兩個整型數相加和兩個浮點數相加的程序看起來應該非常類似,甚至一模一樣才對。唯一的區別就是變量的類型不同。
在強類型語言中,你需要去定義諸如addInts, addFloats, addDoubles 等方法來正確地處理參數及返回值。
許多編程語言已經解決了這個問題。例如,在C++中,使用Template來解決。而Swift,Java和C#則采用了泛型來解決這個問題。泛型,也是這篇文章要重點介紹的。
在這篇文章中,你將會學到Swift中如何使用泛型,也許你已經接觸過,也許沒有,不過沒關系,我們會來一一探索。然后,我們會創建一個Flicker圖片搜索應用,這個應用使用了自定義的泛型數據結構來保存用戶搜索的內容。
備注:本文假設你已經對Swift有基本的了解或者有過Swift開發經驗。如果你第一次接觸Swift或者對Swift不是太了解,建議你首先閱讀下other Swift tutorials。
泛型介紹
也許你不知道這個術語,但相信你已經在Swift中見到它了。Swift中的數組和字典類型就是使用泛型的經典例子。
Object-C開發者已經習慣使用數組和字典去保存多種數據類型。這種方式提供了很大的靈活性,但是誰又能知道一個API返回的數組里面到底是啥(數據類型)呢?你唯一能做的就是查看文檔或者查看(方法的)變量命令(這也是另外一種文檔喲!)。即使你查看了文檔,你也不能保證程序在運行期不產生bug或者其他異常。
相比Object-C,Swift中的數組和字典都是類型安全的。一個Int型數組只可以保存Int而不可以保存String。這意味著你不用再查看文檔啦,編譯器就可以幫你做類型檢查,然后你就就快可以愉快地coding了!
例如,在Object-C的UIKit中, 在自定義的View里面處理觸摸事件可以這么寫:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
上述方法里面的set只可以保存UITouch實例, 因為文檔里面就是這么說的。由于這個集合里面可以放任何對象,所以你需要在代碼里面進行類型轉換,也就是說把touches里面的對象轉為UITouch對象。
當前Swift的標準庫里面沒有定義集合對象,但是你可以使用數組來代替集合對象,你可以用swift重寫上面的代碼:
func touchesBegan(touches: [UITouch]!, withEvent event: UIEvent!)
上面的代碼明確告訴你 touches數組只可以包含 UITouch實例, 否則編譯器就會報異常。這樣一來,你就再不用去做那煩人的類型轉換了,因為編譯器給你做了類型安全檢查,保證數組里面只允許有 UITouch對象。
簡要說來,泛型為類提供了一個類型參數。所有的數組都有相同的作用,即按順序儲存變量的數值,泛型數組除了多了一個類型參數之外,沒有其他的不同之處。或許這樣想更容易理解:你將要應用在數組上的各種算法和儲存的數值類型無關,因此這些算法對于泛型數組和非泛型數組都適用。
既然你已經明白了泛型的基礎知識和用法,那我們就開始把它應用在一個具體的例子上吧。
泛型實例
為了測試泛型,你將會編寫一個在Flickr上搜索圖片的應用。
首先請下載這個程序雛形,并盡快熟悉里面主要的類。其中Flickr類用于和Flickr的API交互。請注意這個類里面包含了一個API key(通常用于用戶授權—譯者注),但如果你想要擴展這個應用的話可能需要用自己的key,注冊請點我。
構造并運行這個應用,你會看到這個:
好像什么都沒有?別急,用不了多久你就可以讓它幫你抓取可愛的喵圖了!
有序字典(原文Ordered Dictionaries)
你的應用會根據每個用戶的查詢情況下載圖片,按照圖片被搜索到的頻率由高到低排序并顯示。
但如果用戶對同樣的關鍵字搜索了兩次會怎樣?如果這個應用能顯示上次搜索的結果就好了。
或許用數組來實現這個功能也行得通,但為了學習泛型,你將會使用一個全新的數據結構:有序字典。
和數組不同的是,包括Swift在內地很多編程語言和框架都不保證集合(sets)和字典(dictionaries)的數據存儲順序。有序字典和普通的字典類似,不同之處在于它的key是有序的。你將會用這個特性,根據搜索關鍵字按順序存儲搜索結果。這樣存儲的好處是可以快速查詢并更新圖片列表。
一個草率的想法是自定義一個數據結構處理有序字典。但是你需要更加有前瞻性才行!你必須考慮到如何讓你的應用在未來幾年內都能正常工作!因此在這里使用泛型再合適不過了。
初始數據結構
點擊“文件\新建\文件...”新建一個文件,并選擇“IOS\Source\Swift File”。點擊“下一步”并把這個文件命名為“OrderedDictionary”。最后,點擊“創建”。
你會得到一個空的Swift文件,加這樣一段代碼進去:
struct OrderedDictionary { }
到現在為止應該都沒有什么問題。通過語義可以看出這個對象是一個結構體。
注意:總之,值的語義可以想象為“復制、粘貼的行為”,而不是“分享、參考的行為”。值的語義帶來一系列的好處,例如不用擔心一段代碼無意地修改你的數據。了解更多,點擊"Swift by Tutorials"的第三章節:類和結構體。
現在你需要將其一般化,以便它能夠裝載你需要的任何類型的數據。通過下列改變你對Swift中“結構”的定義:
struct OrderedDictionary
在尖括弧中的元素是通用類型的參數。KeyType和ValueType不是他們自身的類型,而是你可以使用在結構里定義取代的類型。現在就簡潔清新許多了!
最簡單的實現一個有順序的字典是保持一個數組和一個字典。字典中將會裝載衍射,而數組將裝載keys的順序。
在結構體內部的定義中,加入以下的代碼:
typealias ArrayType = [KeyType]typealias DictionaryType = [KeyType: ValueType] var array = ArrayType()var dictionary = DictionaryType()
這樣聲明有兩個目的,就像上例描述的,有兩種類型的用于給已經存在的類型的取新的名稱的別名。在這,你將分別地為后面的數組和字典賦值了別名。聲明別名是將復雜類型定義為更短名稱的類型的一種非常有效的方式。
你將注意怎么樣從結構體中定義用“KeyType”和“ValueType”的參數類型中替換類型。上例的"KeyTypes"是數組類型的。當然這是沒有這樣的類型的“KeyType”;當在一般的實例化時,將替代Swift像對OrderedDictionary的類型的一切類型通過。
就因為這樣,你將會注意到編譯錯誤:
Type 'Keytype' does not conform to protocol 'Hashable'
或許你會詫異怎么會這樣?請再觀察下Dictionary的繼承者:
struct Dictionary
除了在KeyType之后的HashTable, 其他的都和OrderedDictionary的定義特別的相似。在分號后面為KeyType聲明的Hashable,一定符合Hashable的協議。這是因為字典需要為hash key實現。
用這種方式約束泛型參數是非常常見的。例如,你想要依據你的應用使用參數做什么,來約束值的類型以,確保相等性、可打印性協議。
打開OrderedDictionary.Swift,用下例來取代你對結構體的定義:
struct OrderedDictionary
這樣為OrderedDictionary聲明KeyType,必須符合Hashable。這就意味著,無論KeyType變成什么類型,都可以接受為沒有聲明的字典的KEY。
這樣,文件再次編譯,將不會報錯!
Keys, Values 和所有的這些趣事
如果不能為字典添加值,那么字典有什么作用了?打開OrderedDictionary.swift,在你的結構體定義中添加以下函數:
// 1mutating func insert(value: ValueType, forKey key: KeyType, atIndex index: Int) -> ValueType?{ var adjustedIndex = index // 2 let existingValue = self.dictionary[key] if existingValue != nil { // 3 let existingIndex = find(self.array, key)! // 4 if existingIndex < index { adjustedIndex-- } self.array.removeAtIndex(existingIndex) } // 5 self.array.insert(key, atIndex:adjustedIndex) self.dictionary[key] = value // 6 return existingValue} |
</tr>
</tbody>
</table>
提示 1 | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
注意, newValue 是個包含 key 跟 value 的元組. |
</tr>
|||||||||||||||||||||||||||||||||
提示 2 | |||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
下列代碼可以將值從元組中提取出來: let(key, value) = newValue |
</tr>
||||||||||||||||||||||||||