在Swift中使用泛型

Swift 5.0 都发布这么久了,而泛型作为该语言的重要特性,还是非常有必要了解一下的。
在 Swift 泛型的运用几乎随处可见,在系统层面,Swift 标准库是通过泛型代码构建出来的,Swift 的数组和字典类型都是泛型集。在主流的轮子里,也是有大量的泛型使用。使用泛型可以提审代码的复用性。
下面就通过实例看看我们的代码怎么写:

1、函数中使用泛型
举个简单的例子哦,比如现在有个简单的需求,就是写一个方法,这个方法需要把传过来的整数参数打印出来。

简单需求卡卡的代码就出来啦:

/// 打印一个整形数字
func myPrintInt(arg:Int){
print(arg)
}

看着没啥毛病哦,产品姥爷又说我现在这个方法要支持字符串。

简单再给添加一个方法好啦:

func myPrintString(arg:String){
print(arg)
}

产品:现在要支持Float、Array、Dictionary ......

这也好办啊:

// 打印任何东西
func myPrintAny(arg:Any){
print(any1)
}

很好,现在我要你传进去两个参数,而且参数类型要一致,你要怎么写。

下面的写法可以不?参数靠大家自觉。

//参数类型千万要一样啊。。。。。
func myPrintAny(any1:Any, any2:Any){
print(any1)
print(any2)
}

写成这样的话就可以那赔偿走人啦。

这时候就应该使用泛型了。

// 打印任何东西
func myPrint<T>(any1:T, any2:T){
print(any1)
print(any2)
}

方法的使用:

myPrint(any1: 1, any2: 1)
myPrint(any1: "1", any2: "1")
myPrint(any1: ["1","2"], any2: ["3","4"])

这里就可以看出泛型的优势所在了,大大提升了代码的可复用性。而且同时也提升了代码的安全性。

泛型和Any的区别
从表面上看,这好像和泛型极其相似。Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。然而,理解两者之间的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。

2、类中泛型
实现一个栈,栈里边的元素可以是任何类型,但是所有元素又必须是同一种类型,使用泛型实现的代码就是这样的。

//类作用域
class YJKStack<T>: NSObject {
//栈空间
private var list:[T] = []

//进栈
public func push(item:T){
list.append(item)
}

//出栈
public func pop() -> T{
return list.removeLast()
}
}

当你扩展一个泛型类的时候,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

简单说就是类中的泛型,在其扩展中也是可以进行使用的。

extension YJKStack{
/// 获取栈顶元素
public func getLast() -> T?{
return list.last
}
}

3、泛型类型约束
在实际运用中,我们的参数虽然可以不是特定的类,但是通常需要这个参数要实现某个协议或者是某个类的子类。这时候就要给泛型添加约束了,代码就是下面这一堆喽

//class YJKProtocolStack<T: A&B>  须实现多个协议的话,用 & 符号链接就好啦。
class YJKProtocolStack<T: A>: NSObject {
//栈空间
private var list:[T] = []

//进栈
public func push(item:T){
list.append(item)
}

//出栈
public func pop() -> T{
return list.removeLast()
}
}

protocol A {}

protocol B {}

看了上面的代码,可能有的小伙伴就迷茫啦,既然有YJKProtocolStack<T: A&B>, 为啥没有 YJKProtocolStack<T: A|B>呢,其实想想就可以明白,如果用 | 的话,T 表示的就不是一个指定的类型啦,这样和泛型的定义是不一致的。

4、关联类
在类及函数里都知道泛型怎么玩了,那么在协议里怎么用啦,是不是和类是一样的呢,写个代码看一下:

//Protocols do not allow generic parameters; use associated types instead
//一敲出来,编译器就提示你错误啦,并且告诉你怎么写了。
protocol C<T> {

}

//正确的写法就是下面这样的哦
protocol C {
// Swift 中使用 associatedtype 关键字来设置关联类型实例
// 具体类型由实现类来决定
associatedtype ItemType

func itemAtIndex(index:Int) -> ItemType

func myPrint(item:ItemType)

// 局部作用域的泛型和类的写法是一样的。
func test<T>(a:T)
}

//协议的泛型约束
protocol D {
associatedtype ItemType:A
}

再来看看实现类怎么玩:

//遵循了 C 协议的类
class CClassOne<T>:C{

//要指定 C 协议中, ItemType 的具体类型
typealias ItemType = T

public var list:[ItemType] = []

//协议方法的实现
func itemAtIndex(index:Int) -> ItemType{
return list[index]
}

func myPrint(item:ItemType){

}

func test<T>(a: T) {

}
}

//实现2
class CClassTwo:C{

typealias ItemType = Int

public var list:[ItemType] = []
func itemAtIndex(index:Int) -> ItemType{
return list[index]
}

func myPrint(item:ItemType){

}

func test<T>(a: T) {

}
}

通过上面两个例子可以看出,只要在实现类中 指定 ItemType 的类型就好啦。这个类型 还可以是个泛型,也可以是具体的数据类型。

还有一点要讲的就是结构体中使用泛型和类是完全一样的处理哦。

5、Where 语句
Where 其实也是做类型约束的,你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

看看下面几个代码就行啦,不多说了

func test4<T:A>(arg1:T){}
func test5<T>(arg1:T) where T:A{}
// 上面两个方法的作用是一模一样的

//这个方法 arg1 和 arg2 只需是实现 C 协议的对象就好啦
func test6<T1:C, T2:C>(arg1:T1, arg2:T2){}

//这个方法 arg1 和 arg2 需要实现 C 协议, 并且 T1 与 T2 的泛型类型要一致
func test7<T1:C, T2:C>(arg1:T1, arg2:T2) where T1.ItemType == T2.ItemType{}

//这个方法 arg1 和 arg2 需要实现 C 协议, && T1 与 T2 的泛型类型要一致 && T1 的泛型 遵循A协议
func test8<T1:C, T2:C>(arg1:T1, arg2:T2) where T1.ItemType == T2.ItemType, T1.ItemType:A{}

本文写到这里就没结束啦,简单介绍啦泛型在 Swift 中的使用,大家想要深入理解泛型,想要融会贯通、运用自如,还需要大家找时间多看看大神写的代码。

转自:https://www.jianshu.com/p/a01f212e628c

0 个评论

要回复文章请先登录注册