Swift文档小结-Generics

本文基于Swift5.0版本官方文档,阅读大概须要20min,能够对泛型有一个清晰的认识。html

什么是泛型(Generics)

泛型:指的就是在你定义的时候放置一个占位符类型名,告诉系统使用的类型如今不肯定,我先占个位置。这样编译的时候系统不会报错。而后在你使用的时候才会真正地肯定类型。swift

语言表达起来可能不是那么直观,让咱们看一段代码直观的了解一下什么是泛型。数组

//定义一个打印任意类型变量的函数
func printSomething<T>(value1: T, value2: T) {
    print(value1, value2)
}

printSomething(value1: 1, value2: 2) // Int: 1 2
printSomething(value1: "a", value2: "b") //String: a b
复制代码

经过上面的代码咱们能够看出,经过在函数名字后面添加<T>来代表添加了一个泛型类型,<>告诉编译器T是一个占位符类型,不须要真正的查找叫作T的类型。安全

Note

  • <T>中的T能够是任意字符或者单词,可是要使用大写字母或者大写开头的驼峰命名法(如:VUMyTypeParameter)。
  • <>里面不止能够写一个类型占位符,也能够写多个:<T, U>

如今咱们初步了解泛型是什么,那么确定会有人问道:咱们为何要是使用泛型呢?下面咱们看一下为何要使用泛型。bash

为何要用泛型

泛型类型函数

在平常工做中,咱们会常常遇到在某些条件下交换两个变量的值的状况。若是须要交换两个Int的值得话,咱们能够很轻易的实现下面的函数:数据结构

func swapTwoIntValue(_ num1: inout Int, _ num2: inout Int) {
    (num1, num2) = (num2, num1)
}

var num1 = 10
var num2 = 20

swapTwoIntValue(&num1, &num2)
print(num1, num2) // 20 10
复制代码

这个函数很简洁,也很正确,可是若是咱们还须要交换StringDouble等等类型的变量呢,再写swapTwoStringValue(_:_:)swapTwoDoubleValue(_:_:)的函数吗?再定义两个这样的函数固然没有问题,可是咱们会发现这三个函数内部实现都是同样的,区别只是参数的类型不一样。这时候就轮到泛型出马了,咱们能够用泛型写一个使用任意Swift基本类型的交换函数:app

func swapTwoValue<T>(_ num1: inout T, _ num2: inout T) {
    (num1, num2) = (num2, num1)
}

var num1 = 10
var num2 = 20

swapTwoValue(&num1, &num2)
print(num1, num2) // 20 10

var str1 = "hello"
var str2 = "world"

swapTwoValue(&str1, &str2)
print(str1, str2) // world hello
复制代码

小结-为何要用泛型

  • 能够写出更加灵活可复用的函数。
  • 使代码更加简洁明了。

Note

  • 上面的swapTwoValue(_:_:)函数只是举个例子说明泛型类型函数的用法,若是你想使用交换两个变量的值得功能,你可使用官方的swap(_:_:)函数。
  • 注意交换函数两个变量的类型都是T,虽然T能够表示任意类型,但两个变量必须是同一类型,Swift不容许两个不一样类型的变量交换值,由于Swift是一门类型安全的语言。

咱们如今知道能够经过定义泛型类型的函数来达到减小代码冗余的问题,那么泛型的用处仅仅如此吗?做为Swift最强大的特性之一,确定不会只是实现一个泛型类型函数这么简单的。下面让咱们看一下泛型还能作什么?ide

咱们能用它作什么

实现泛型的数据结构

咱们能够经过泛型来实现一个支持多种类型的栈。具体代码以下:函数

struct Stack<Element> {
    var items = [Element]()
    
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop(_ item: Element) -> Element {
        return items.removeLast()
    }
}
复制代码

Stack能够放入Int/String等多种类型的数据。此处有个地方要注意:在咱们给Stack扩展计算属性或者方法的时候,不须要咱们在声明类型参数,Stack中的泛型在extension中依然有效。具体代码以下:ui

// 不须要再用<Element>来声明泛型
extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}
复制代码

经过类型约束(Type Constraints)来实现遵照protocol的任意类型参数的函数

平常开发中,咱们会常常须要实如今一个数组中查找某个值的索引的功能,若是咱们将数组的元素类型写死的话,咱们声明的函数只能用于某一种类型,这时候咱们应该怎么办呢?对了,就是将类型声明为泛型类型。经过上面的介绍,咱们可能会写下如下代码:

func findIndex<T>(_ target: T, _ array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == target {
            return index
        }
    }
    return nil
}
复制代码

这个函数建立的很不错,可是很惋惜它编译会报错:Binary operator '==' cannot be applied to two 'T' operands,该报错表示咱们声明的类型占位符T不能使用==运算符。那么如何正确的实现该函数呢?这就要使用类型约束(Type Constraints)来实现了。具体的作法就是将findIndex<T>改成<T: Equatable>,这句话的意思是T只支持实现了Equatable协议的类型使用。具体代码以下:

func findIndex<T: Equatable>(_ target: T, _ array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == target {
            return index
        }
    }
    return nil
}

if let index = findIndex("person0", arr) {
    print("person0 index is \(index)") //person0 index is 0
}
复制代码

Protocol中的联合类型

如今咱们知道能够在函数参数中使用泛型,那么我能在protocol中实现相似的功能吗?答案是:固然能够。咱们能够用associatedtype关键字来告诉编译器该类型为泛型,在真正使用的时候再检查它的类型。

假如咱们要实现一个Container的Protocol,该协议包含了append(_)添加元素的函数、获取长度的计算属性count、根据下标获取元素的函数subscript(_)。这时候若是咱们将Item的类型写死的话就说明了只有这一种类型可以遵照该Protocol,那么如何让更多的类型可以遵照呢?这时候就轮到associatedtype出场了。下面为具体代码:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
复制代码

咱们可使Stack遵照该协议,看一下具体使用。代码以下:

extension Stack: Container {
    mutating func append(_ item: Element) {
        push(item)
    }
    
    var count: Int {return items.count }
    
    subscript (_ i: Int) -> Element {
        return items[i]
    }
}

var s1 = Stack(items: [1,2,3,4])
var s2 = Stack(items: ["a", "b", "c", "d"])
s1.append(5)
print(s1.items) //[1,2,3,4,5]
print(s1.count) //5
print(s1[2])    // 3

s2.append("f")
print(s2.items) //["a", "b", "c", "d", "f"]
print(s2.count) //5
print(s2[2])    //"f"
复制代码

Protocol中的联合类型添加类型约束

在上面咱们看到Protocol中可使用联合类型来实现泛型,那么咱们也能够给联合类型添加类型约束来实现泛型遵照某个Protocol、或者遵照某种条件(好比类型相同等)。具体代码以下:

protocol Container {
    //该行代码表示Item必须是遵照Equatable的类型
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(_ i: Int) -> Item { get }
}
复制代码

Protocol中的联合类型添加多个类型约束

咱们知道能够给联合类型添加类型约束,能够用associatedtype Item: Equatable来使Item遵照Equatable协议,那若是我想让Item遵照Equatable的同时,又约束它必须是某一种类型呢?这时候咱们可使用where语句来实现。具体代码以下:

protocol SuffixableContainer: Container {
    //该行代码表示Suffix必须遵照SuffixableContainer,而且它的Item类型必须和Container的Item类型一致
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
//Stack至关于上面的Suffix,它遵照了SuffixableContainer协议
extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
}


var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30
复制代码

上面的代码SuffixableContainer实现了一个获取某个位置到最后的一段数据。

Extension中使用类型约束

同Protocol,咱们也能够在Extension中经过where来实现类型约束。 若是咱们不让Element遵照Equatable协议的话,是会编译错误的,由于在该函数中咱们使用了 == 操做符。代码以下:

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."
复制代码

固然,咱们也能够在扩展Protocol的时候来使用类型约束。代码以下:

extension Array: Container where Element: Equatable { }

// 扩展Container,而且Item是遵照Equatable协议的
extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
//"Starts with something else."
复制代码

除了强制泛型元素遵照某个协议外,咱们也能够强制泛型元素为特定的某个类型。代码以下:

extension Array: Container where Element: Equatable { }

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average()) //648.9
复制代码

总结

上面就是关于泛型的讲解,下面来看一下关于泛型的总结。

  • 类型占位符要使用大写的字母或者大写开头的驼峰命名法。
  • 泛型使代码更加灵活可复用、更加简洁明了。
  • 类型参数有类型约束的参数能够在泛型函数、泛型下标、泛型类型中使用。
  • 泛型的where语句可使你的联合类型必须遵照某个协议或者知足某些条件。

到这里,关于泛型的讲解就结束了。但愿你们经过本文能对泛型有一个全新的、深入的认识。让咱们在项目中愉快的使用泛型吧!

参考

相关文章
相关标签/搜索