Swift - 泛型

妖精的绣舞 提交于 2020-03-06 00:15:31

泛型是Swift中重要的类型之一,意为不确定的类型。我们知道带有参数的函数是必需要指定参数的类型的,举个例子观察下面有几个同名函数实现相似的功能,但参数类型不同:

一、节点泛型

    func methodOne(param:String) {
        print("\(param)")
    }
    func methodTwo(param:Int) {
        print("\(param)")
    }
    func methodThree(param:Float) {
        print("\(param)")
    }

我们可能会根据需求调用不同的函数,但是这么定义方法显得太过冗余。泛型所带来的好处就是我们可以通过定义单个函数来实现上面的功能,使用泛型作为参数的函数叫做泛型函数,现在我们使用泛型来实现与上述功能相同函数定义:

    func method<T>(param:T) {
        print("\(param)")
    }

然后给这个泛型函数传入不同类型的值:

        method(param: 12) //输出12
        method(param: "Swift") //输出 "Swift"
        method(param: 12.34) //输出 12.34

上述的泛型函数使用是参数就是一个泛型,泛型函数在声明时使用了节点类型命名(通常情况下用字母T、U、V等这样的大写字母来表示)来代替实际类型名(如Int、String等类型),节点类型在定义时不表示任何具体的类型,在函数被调用时会根据传入的实际类型来指定自身的类型。需要注意的是:如果函数的泛型列表中只有一个T,虽然具体类型不需要指定,但是每个节点类型的参数必须是相同的类型,例如下面定义的函数:

func method<T>(param1:T, param2:T) { ... }

在调用这个函数时,两个参数必须是相同的类型:

        method(param1: 1, param2: 2)
        method(param1: "Hello", param2: "Word")

如果定义多个不同类型的泛型,则需要在尖括号中加入多个节点:<T、U、V…>,在泛型函数后面加上插入节点声明,声明会告诉Swift,尖括号中的T是method函数所定义的一个节点类型,因为T是一个节点,所以Swift不会去查找是否有一个命名为T的实际类型。

二、泛型协议

上面我们是使用节点声明泛型,除此之外还以通过关键字 associatedtype来声明泛型,且该关键字只能用于协议中:

protocol ProtocolTest {
    associatedtype Element
    
    func method1(element:Element)
    func method2(element:Element)
}

虽然没有出现节点语法,但是上面的协议却一个不折不扣的泛型协议,其中Element起到占位符的作用,指定了某种类型。根据协议的规则,协议ProtocolTest的遵守者必须要实现上面的两个方法,此时Element除了显示地体现了泛型的优势外,还隐性地约束了两个方法的参数必须是相同类型的。这里我们不用可以指定Element的具体类型,编译器会根据实现方法时传入的参数类型确定Element的具体类型:

struct StructTest:ProtocolTest {    
    func method1(element: String) {
    }
    func method2(element: String) {
    }
}

在实现的时候不能直接用Element,Element只存在于具体实现之前,如果尝试使用它,编译器会报错。在结构体 StructTest 中,由于传入了 String,所以泛型Element的实际类型就是String类型,如果你尝试让两个参数的类型不同,那么编译器同样会报错。

类似于associatedtype的还有Self关键字,适用于比较这类方法,其必须传入另一个相同的类型才有意义,下面定义一个Self类型的方法:

protocol CanCompare {
    func isBigger(other:Self) -> Bool
}

struct BoxInt:CanCompare {
    var intValue:Int
    func isBigger(other: BoxInt) -> Bool {
        return self.intValue == other.intValue
    }
}

测试方法调用:

print(BoxInt(intValue: 5).isBigger(other: BoxInt(intValue: 4))) // 输出 true

三、泛型对象

关联类型和Self关键字都是协议层面的泛型,此外还有对象层面的泛型,比如我们常用的数组就是用对象层次的泛型定义的,如果不使用协议,一个泛型的对象风格的结构体定义如下:

struct StructTest<T> {
    func method(element:T) {
        print("\(element)")
    }
}

let test = StructTest<Int>()
test.method(element: 10)

需要注意的是,泛型应该用在声明中,真的调用时版本中的泛型已经被**“特化”**成具体的类型。泛型协议依靠遵守者中协议方法的具体实现来明确泛型的类型,而泛型对象则通过构造器初始化时明确泛型的类型,这些类型都是具体的。如果你尝试在实现时“嵌套”一个泛型,那么会导致泛型无法被特化。比如数组本身是泛型的,在声明数组类型时传入另一个泛型,那么你将无法初始化该数组:

struct StructTest<T> {
    var array:[T] = [1,2]
}

你可能认为1、2都可以放进数组中,符合array声明的上面文,但是忽略了数组本身就是泛型的,传入一个泛型T后就会造成泛型的嵌套,是的数组无法被初始化。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!