0%

swift泛型笔记

泛型可以让代码处理类型更加灵活,在某些场景下可以很大程度的重用代码,泛型是什么,使用泛型的好处,这里不多说,网上有很多文章介绍的很详细,这里只讨论用法,Swift的泛型与其他语言有些类型,又有些不一样,搜了一下发现,网上的文章只描述了一点,并不全面,看完后依然没能很全面的说明泛型的用法,在这里记录完整的用法

定义泛型

swift的泛型定义方式有两种,一种通过<T>指定泛型参数,而在属性或方法,使用指定的泛型类型,下面是一个泛型方法的声明

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 交换两个同类型的变量
func swap<T>(a: inout T, b: inout T) {
let c = a
a = b
b = c
}
var (a, b) = (2, 1)

swap(&a, &b)
print(a, b) // 1, 2

swap(&a, &b)
print(a, b) // 2, 1

泛型类

上面是泛型方法的定义,如果在类里面,可以对类声明泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Test<T> {
/// 泛型属性
var testObj: T?

/// 泛型方法,可以直接使用类的泛型参数T
func swap<T>(a: inout T, b: inout T) {
let c = a
a = b
b = c
}

/// 方法的泛型定义可以不依赖于类的泛型参数
func swap2<TK>(a: inout TK, b: inout TK) {
let c = a
a = b
b = c
}
}

// 属性
let t = Test<Int>()
t.testObj = 12
print(t.testObj) // 12

var a: Int? = 2
var b: Int? = 1
t.swap(a: &a, b: &b)
print(a, b) // 1, 2

var s1: String? = "a"
var s2: String? = "b"
t.swap2(a: &s1, b: &s2)
print(s1, s2) // b, a

泛型参数

泛型可以支持多个类型参数,在声明处用逗号分开

1
2
3
4
5
6
7
8
9
10
class Test {
/// 多个泛型参数
func test<T1, T2>(t1: T1, t2: T2) -> Int {
print("t1: \(t1), t2: \(t2)")
return 1
}
}

let t = Test()
let result = t.test(t1: NSObject(), t2: "abc")

泛型约束

类型约束定义在泛型声明处,支持类约束和protocol约束,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protocol Run {
func run()
}

protocol Fly {
func fly()
}

/// 单协议约束
func test<T: Run>(animal: T) {
animal.run()
}

/// 类型和多协议约束
func test<T: NSObject & Fly & Run>(bird: T) {
bird.fly()
bird.run()
}

泛型可以用在方法参数,方法返回值,属性上

协议泛型

除了类,结构体和枚举也支持泛型,用法与类一样,但是协议protocol不能像上面一样使用
协议只能通过关联类型associatedtype来实现泛型的功能,相当于泛型声明为协议的成员,而泛型的成员在实现的时候指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Write {
/// 关联类型Element,相当于上面的T
associatedtype Element
func write(_ element: Element)
}

class File: Write {
// 在实现关联类型的协议的时候,需要指定关联类型
typealias Element = String

// 实现协议方法
func write(_ element: File.Element) {
print(element)
}
}

关联类型的约束

关联类型的约束与普通泛型约束一样,在声明的地方后面添加,通过逗号隔开

1
2
3
4
 protocol Write {
associatedtype Element: NSObject, Encodable
func write(_ element: Element)
}

关联自身类型

protocol利用associatedtype关联自身类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protocol Equalable {
func equal(_ a: Self) -> Bool
}

class Test: Equalable {
var id: Int = 0

init(id: Int) {
self.id = id
}

func equal(_ a: Test) -> Bool {
return self.id == a.id
}
}

let a = Test(id: 1)
let b = Test(id: 2)
a.equal(b) // false

associatedtype冲突

associatedtype有另一个问题没有解决,就是类型冲突,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protocol Read {
associatedtype Element
func read() -> Element
}
protocol Write {
associatedtype Element
func write(a: Element)
}

// 两个Element名字相同,无法指定
class ReadWrite: Read, Write {
func read() -> Int {
return 5
}
func write(a: String) {
print("writing \(a)")
}
}

在stackoverflow上有个不完美的解决方案:
https://stackoverflow.com/questions/37736457/protocol-with-same-associated-type-name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol ReadInt: Read {
associatedtype Element = Int
}
protocol WriteString: Write {
associatedtype Element = String
}

class ReadWrite: ReadInt, WriteString {
func read() -> Int {
return 5
}
func write(a: String) {
print("writing \(a)")
}
}

关于associatedtype

对于其他语言大多都使用直接指定类型<T>的方式声明泛型,而swift为什么还要搞出一个associatedtype呢,这篇文章有分析:http://www.cocoachina.com/swift/20160726/17188.html

使用关联类型,类似于类型当成成员属性使用,可以解耦依赖,如果需要使用对象的关联的类型,而类型与当前对象并没有直接关系,则通过成员的关联类型引用可以避免直接依赖对象类型,如下例子:机动车(Automobile)、燃料(Fuel)、尾气(Exhaust)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 机动车
public protocol Automobile {
associatedtype FuelType
associatedtype ExhaustType
func drive(fuel: FuelType) -> ExhaustType
}

/// 燃料
public protocol Fuel {
associatedtype ExhaustType
func consume() -> ExhaustType
}

/// 尾气
public protocol Exhaust {
func emit()
}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public struct UnleadedGasoline: Fuel {
public func consume() -> E {
print("consuming unleaded gas...")
return E()
}
}

public struct CleanExhaust: Exhaust {
public func emit() {
print("this is some clean exhaust...")
}
}

public class Car: Automobile {
public func drive(fuel: F) -> E {
return fuel.consume()
}
}

实际依赖关系:
机动车 -> 燃料
燃料 -> 尾气

但是代码有个问题是在定义Car的时候,必须声明尾气的类型E,而实际上机动车是不依赖于尾气的类型的,尾气的类型取决于燃料,如果Car支持两种燃料FuelA和FuelB,而两种燃料产生的尾气E1和E2,drive方法无法同时满足,只能定义两个方法

1
2
3
4
5
6
7
8
9
public class Car: Automobile {

public func drive(fuel: F1) -> E1 {
return fuel.consume()
}
public func drive(fuel: F2) -> E2 {
return fuel.consume()
}
}

如果使用关联类型的话可以解决这个问题,把尾气关联到燃料上

1
2
3
4
5
public class Car: Automobile {
public func drive(fuel: F) -> F.ExhaustType {
return fuel.consume()
}
}

以上就是Swift泛型的全部要点