前言
OC缺乏一個重要特性,不支持泛型。Swift擁有了這一特性,是靈活性的語法,在函數、結構體、類、枚舉中都可以應用,相當于暫位符的作用,當類型暫時不確定,只有等到調用函數時才能確定具體類型的時候可以引入泛型。
簡單理解泛型就是先占位,具體占位做什么,用的時候再說。Swift的Array和Dictionary類型都是泛型集
應用場景
簡單模仿一個棧操作,主要實現出棧、入棧兩個功能
// MARK: - 模仿棧
class TestStack {
var valueArray: [Int] = []
/// 壓棧
func push(value: Int) -> () {
valueArray.append(value)
}
/// 出棧
func pop() -> (Int?) {
if let lastValue = valueArray.last {
valueArray.removeLast()
return lastValue
}else {
return nil
}
}
}
這樣實現的棧只能操作Int類型,如果需求變更,要處理String類型,怎么解決?替換所有Int為String可以解決,但這種代碼明顯上不了臺面。此外另外我們還會想到Any類型,如下
// MARK: - 模仿棧
class TestStack {
var valueArray: [Any] = []
/// 壓棧
func push(value: Any) -> () {
valueArray.append(value)
}
/// 出棧
func pop() -> (Any?) {
if let lastValue = valueArray.last {
valueArray.removeLast()
return lastValue
}else {
return nil
}
}
}
如此TestStack可操作類型就不局限于一種了,但是帶來了兩個問題:1、數據類型安全問題;2、每次對棧進行操作時,都需要進行一系列繁瑣的類型轉換(casting操作,使用as來進行類型轉換)
這里簡單介紹下Any和AnyObject的區別:
在 Swift 3 之前,我們可以寫完一個項目都只用 AnyObject 來代表大多數實例,好像不用與 Any 類型打交道。但事實上,Any 和 AnyObject 是有明顯區別的,因為 Any 可以代表 struct、class、func 等等幾乎所有類型,而 AnyObject 只能代表 class 生成的實例。
那為什么之前我們在 Swift 2 里可以用 [AnyObject] 聲明數組,并且在里面放 Int、String 等 struct 類型呢?這是因為 Swift 2 中,會針對這些 Int、String 等 struct 進行一個 Implicit Bridging Conversions,在 Array 里插入他們時,編譯器會自動將其 bridge 到 Objective-C 的 NSNumber、NSString 等類型,這就是為什么我們聲明的 [AnyObject] 里可以放 struct 的原因。
但在 Swift 3 當中,為了達成一個門真正的跨平臺語言,相關提案將 Implicit Bridging Conversions 給去掉了。所以如果你要把 String 這個 struct 放進一個 [AnyObject] 里,一定要 as NSString,這些轉換都需要顯示的進行了——畢竟 Linux 平臺默認沒有 Objective-C runtime。這樣各平臺的表現更加一致。當然這是其中一個目標。
參照泛型的特性,定義一個泛型類型。使用泛型后的示例代碼如下:
`
// MARK: - 模仿棧
class TestStack<T> {
var valueArray: [T] = []
/// 壓棧
func push(value: T) -> () {
valueArray.append(value)
}
/// 出棧
func pop() -> (T?) {
if let lastValue = valueArray.last {
valueArray.removeLast()
return lastValue
}else {
return nil
}
}
}
`
使用泛型:在初始化時通過明確的類型(這里用Int示例)來定義參數,之后編譯器會將所有的泛型(T)替換成Int類型(如此實現的棧,最大優勢在于能夠匹配任何類型)。
// 示例1:Int
let myStack1 = TestStack<Int>()
myStack1.push(value: 1)
myStack1.push(value: 2)
myStack1.push(value: 3)
print(myStack1.pop() ?? "0")
// 示例2:String
let myStack2 = TestStack<String>()
myStack2.push(value: "a")
myStack2.push(value: "b")
myStack2.push(value: "c")
print(myStack2.pop() ?? "0")
泛型約束
泛型約束大致分為以下幾種:
協議約束,泛型類型必須遵循某些協議
繼承約束,泛型類型必須是某個類的子類類型
條件約束,泛型類型必須滿足某種條件
協議約束
還拿TestStack棧說事兒,這里添加一個函數isContainValue,要判斷棧中是否包含傳入的元素,需要用到等式符(==)和不等符(!=)對任何兩個該類型進行比較。Swift標準庫中定義了一個Equatable協議,只有支持了這個協議才可以使用等式符(==)和不等符(!=),否則報紅。所有的Swift標準類型自動支持Equatable協議,但是泛型由于不確定類型,系統不知對象是否支持Equatable協議,所以直接使用==或!=報紅
解決方案,讓TestStack棧中,讓泛型T遵循Equatable協議
繼承約束
這里定義了三個類Person、Leader、Coder,其中Leader是繼承Person,
// MARK: - 人
class Person: NSObject {
func watchTV() -> () {
print("Person看電視:人民的名義")
}
}
// MARK: - 領導
class Leader: Person {
override func watchTV() -> () {
print("Leader看電視:人民的名義")
}
}
// MARK: - 程序員
class Coder {
func watchTV() -> () {
print("coder看電視:人民的名義")
}
}
func testWatchTV<T: Person>(obj:T) -> () {
obj.watchTV()
}
testWatchTV函數,接受一個泛型參數,要求該泛型類型必須繼承Person,否則爆紅。
條件約束
在類型名后面使用where來指定對類型的特殊需求,比如限定類型實現某一個協議,限定兩個類型是相同的,或者限定某個類必須有一個特定的父類等
// 定義一個協議
@objc protocol TestProtocol {
@objc optional func testOne() -> ()
}
// MARK: - 人
class Person: NSObject, TestProtocol {
var name: String?
func watchTV() -> () {
print("Person看電視:人民的名義")
}
}
// MARK: - 領導
class Leader: NSObject {
var name: String?
func watchTV() -> () {
print("Leader看電視:人民的名義")
}
}
testCondition函數要求傳入的參數都是P類型的繼承自Person,L類型繼承自Leader,同時還附加了條件(where)這兩個類都遵守了TestProtocol協議,由于Leader類型沒有遵守TestProtocol協議,條件不滿足所以爆紅。修改方法則是Leader類遵守TestProtocol。
參考鏈接:http://www.lxweimin.com/p/a907f0c09a60
參考鏈接:http://swift.gg/2015/09/16/swift-generics/