Swift 泛型

一、定义

什么是泛型?

网络上对泛型编程的定义是这样的:

  • 泛型编程是一种算法机制为types to-be-specified-later(类型确定滞后)的计算机编程风格,当具体的类型作为参数传入后,该算法机制会对类型进行实例化。这个方法由ML在 1973 年开创。可以用共有的函数和类型来表示一个类型集合从而来减少函数操作的重复。

苹果Swift官方文档 关于泛型是这么说明的:

  • 泛型编码能让你写出符合需求、支持任意类型,灵活、可重用的函数。你能够编写避免重复和编程风格抽象、清晰、优雅的代码。

  • 泛型是Swift最强大的特性之一,许多Swift标准库是通过泛型代码构建的。例如,SwiftArrayDictionary都是泛型集合。你可以创建一个Int数组,也可创建一个String数组,甚至可以是任意其他Swift类型的数组。同样的,你也可以创建存储任意指定类型的字典。

泛型就是具体类型的占位符,这个占位符只有在调用的时候才会确定类型,泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。

所以,如果泛型使用得当,能编写出可复用简洁可维护的代码。

二、初体验

那我们为什么要使用泛型呢,单纯的概念说明总是枯燥的,而且也难以理解,我们通过一个例子来初体验一下使用泛型的好处:

tips: 例子的功能实现使用Set是最好的选择,这里只是为了举例说明泛型的使用。

这里有一个需求:剔除Int数组中的重复元素

func removeIntSameElement(_ originArr: [Int]) -> [Int] {
    return originArr.reduce([Int]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
// 打印结果:[3, 2, 4, 5, 6]
print(removeIntSameElement([3, 2, 3, 4, 5, 2, 6, 3]))

tips: 这里使用的是Swift高阶函数实现,如果不了解可以查看:
https://www.jianshu.com/p/26a011cd6055

然后,又来了一个需求,这次需要的是要求剔除String数组中的重复的元素,那我们再写一个函数

func removeStringSameElement(_ originArr: [String]) -> [String] {
    return originArr.reduce([String]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
// 打印结果:["a", "b", "d", "e", "c"]
print(removeStringSameElement(["a", "b", "a", "d", "e", "d", "b", "c"]))

然后,如果是要实现其他类型的呢,比如DoubleFloat...,那我们就要写更多的函数来满足需求,这...

我们能够注意到,removeIntSameElementremoveStringSameElement以及我们将要写的满足其他类型的函数功能都是一样的,唯一的区别就是入参的变量类型不同。

当我们硬着头皮写完这些功能相同的函数之后,我们通常会碰到两种问题:

  1. 万一功能实现出了点问题,需要进行修复
  2. 完全确保了功能的实现没有问题,但是...,但是改需求了

那我们就要去把所有这些功能相同的函数实现都修改一遍,这个时候就会有一种很崩溃的感觉,自然就会想会不会有更灵活的方式来实现这些功能相同但入参的变量类型不一致的函数,于是泛型来了,完美解决这个问题。

func removeSameElement<T: Equatable>(_ originArr: [T]) -> [T] {
    return originArr.reduce([T]()) { $0.contains($1) ? $0 : $0 + [$1] }
}

这个函数的函数名removeSameElement后面跟着用尖括号括起来占位类型名(<T>)。这个尖括号告诉Swift那个TremoveSameElement函数定义内的一个占位类型名,因此Swift不会去查找名为T的实际类型。

removeSameElement使用了(T)来代替实际类型名(例如:IntStringDouble)。占位类型名没有指明T必须是什么类型,但是它指明了originArr 必须是T的数组,无论T代表什么类型。只有removeSameElement函数在调用时,才会根据所传入的实际类型决定T所代表的类型。

  • T: Equatable 表示占位类型名(T)必须遵循Equatable协议,这个后面会说,暂时忽略。

removeSameElement函数现在可以像removeStringSameElement那样调用,不同的是它能接受的是任意类型的数组。removeSameElement函数被调用时,T所代表的类型都会由传入的值的类型推断出来。

行不行,看疗效,调用试试,可以看到,调用结果与指定类型的函数得到的结果一致。

两次调用的T分表代表IntString

// 泛型打印结果:[3, 2, 4, 5, 6]
print(removeSameElement([3, 2, 3, 4, 5, 2, 6, 3]))

// 泛型打印结果:["a", "b", "d", "e", "c"]
print(removeSameElement(["a", "b", "a", "d", "e", "d", "b", "c"]))
  • <T>表示是类型参数,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来,一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型,或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 removeSameElement的例子中,当函数第一次被调用时,TInt替换,第二次调用时,被String替换。)

  • 类型参数不止可以定义一个,与函数的参数一样,根据具体的需求,可提供多个类型参数,将它们都写在尖括号中,用逗号分开。

三、基本使用

泛型在开发中的使用场景主要在以下几方面:

    1. 泛型函数
    1. 泛型类型
    1. 泛型约束
    1. 泛型下标
A. 泛型函数

泛型函数指的是:函数的参数或返回值类型使用泛型,而不是具体的类型

泛型函数的格式:

func 函数名<泛型1, 泛型2, …>(形参1, 形参2, ...) -> 返回类型 {
    函数体
}

需求:实现Swift高阶函数reduce功能的一个函数

func myReduce<T, U>(arr: [T], initialValue: U, partialResult: (U, T) -> U) -> U {
    var result = initialValue
    arr.forEach {
        result = partialResult(result, $0)
    }
    return result
}

myReduce这个函数有两个占位类型,T和U:

  • T作为形参arr的数组元素占位类型和形参partialResult闭包的第二个入参占位类型
  • U不仅作为了形参initialValue和形参partialResult闭包的第一个参数占位类型,也作为闭包partialResult的返回类型和整个函数的返回类型

泛型函数真的可以支持所有的类型吗?看疗效:

let array = ["1", "2", "3", "4", "5"]
let r1 = myReduce(arr: array, initialValue: 0) { $0 + (Int($1) ?? 0) }
// 打印结果:转为Int后的求和结果:15
print("转为Int后的求和结果:\(r1)")

let nums = [1, 2, 3, 4, 5]
let r2 = myReduce(arr: nums, initialValue: []) { $1 % 2 == 0 ? $0 + ["\($1)"] : $0 }
// 打印结果:偶数转为字符串数组:["2", "4"]
print("偶数转为字符串数组:\(r2)")

这个泛型函数正常工作了:

  • 第一个例子,T代表StringU代表Int,代码意思是将数组中的每个String元素转换成Int然后累计求和,将StringInt分别代入myReduce函数的TU,就能明白了

  • 第二个例子,T代表IntU代表[String],代码意思是将数组中每个Int元素的偶数取出来转成一个新的String数组,将Int[String]本别代入myReduce函数的TU,分析一下,就明白了

B. 泛型类型

除了泛型函数,Swift还允许定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于ArrayDictionary

// Array 的定义
public struct Array<Element> {
    // ...
}
// Dictionary 的定义
public struct Dictionary<Key, Value> where Key : Hashable {
    // ...
}
  • Array后面尖括号中的Element就是Array的定义的泛型类型
  • Dictionary尖括号中的KeyValue就是Dictionary定义的泛型类型
// Array 的使用
let a1: Array<String> = ["a", "b"]
let a2: Array<Int>    = [1, 2]

// Dictionary 的使用
let d1: Dictionary<String, String> = ["a": "b"]
let d2: Dictionary<String, Int>    = ["a": 1]
  • a1Stringa2Int就是Array定义的泛型Element的具体类型
  • d1StringStringd2StringInt就是Dictionary的泛型KeyValue的具体类型

不过一般写代码的时候,我们都是使用如下的方式:

let a: [String] = ["a", "b"]
let d: [String: String] = ["a": "b"]

所以对泛型的感知并不强。

泛型类型的使用和泛型函数差不多,就是在类型名后面加上<泛型1, 泛型2, …>,然后在类型里面直接使用泛型。

举个例子,实现一个简易版的迭代器:

struct CustomIterator<Element> {
    var elements: [Element] = []
    var num = 0
    mutating func next() -> Element? {
        if num == elements.count {
            num = 0
            return nil
        }
        num += 1
        return elements[num - 1]
    }
    
    init(elements: [Element], num: Int = 0) {
        self.elements = elements
        self.num = num
    }
}

CustomIterator结构体名字后面的Element就是泛型类型,对CustomIterator来说,它实现的是一个简易迭代的功能,与类型无关,只要能装进Array的类型都可以使用这个功能。

Element为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过Element来引用。在这个例子中Element在如下三个地方被用作占位符:

  • 指定init方法的第一个参数必须是Element的数组
  • 创建elements属性,使用Element类型的空数组对其进行初始化
  • 指定next方法的返回值,指定是可选的Element

与泛型函数类似,都是在调用的时候会确定泛型的具体类型。

var cusIterStr = CustomIterator<String>(elements: ["2", "3", "6"])
while let ele = cusIterStr.next() {
    print(ele)
}
/*
 打印结果:
 2
 3
 6
 */

var cusItemrDouble = CustomIterator<Double>(elements: [22.13, 45.67, 98.12])
while let ele = cusItemrDouble.next() {
    print(ele)
}
/*
 打印结果:
 22.13
 45.67
 98.12
 */

第一个例子Element代表的是String,第二个例子Element代表的是Double,它们分别是在CustomIterator<String>CustomIterator<Double>初始化的时候确定了泛型的具体类型。

扩展泛型类型

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

下面我们扩展泛型类型CustomIterator,为其添加了一个名为firstElement的只读计算型属性,返回一个CustomIterator中数组的第一个元素:

extension CustomIterator {
    var firstElement: Element? {
        return elements.first
    }
}

firstElement会返回一个Element类型的可选值。当elements为空的时候,firstElement会返回nil;当elements不为空的时候,firstElement会返回elements中的第一个元素。

注意,这个扩展并没有定义一个类型参数列表。相反的,CustomIterator类型已有的类型参数名称Element,被用在扩展中来表示计算型属性firstElement的可选类型。

计算型属性firstElement现在可以用来访问任意CustomIterator实例的第一个元素:

var cusIter = CustomIterator<String>(elements: ["hello", "你好", "hi"])
// 打印结果:hello
print(cusIter.firstElement ?? "")
C. 泛型约束

myReduceCustomIterator都可以作用任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合,或者符合一些什么条件。

所以,泛型约束大致分为以下几种:

  1. 继承约束,泛型类型必须是某个类的子类类型
  2. 协议约束,泛型类型必须遵循某些协议
  3. 条件约束,泛型类型必须满足某种条件

接下来我们来逐个说明:

  • 协议约束

协议约束,工作中高频率使用的Dictionary的定义的泛型KeyValue中的Key就是有协议约束的:

// Dictionary 的定义
public struct Dictionary<Key, Value> where Key : Hashable {
    // ...
}

泛型Key必须遵循Hashable协议,所以字典的键的类型必须是可哈希(hashable)的。也就是说,必须有一种方法能够唯一地表示它。Dictionary 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,Dictionary将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。

当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,可比较的等等,而不是它们的显式类型。

函数的协议约束的语法:

// 函数的协议约束
func 函数名<泛型: 协议, ...>(形参: 泛型, ...) -> 返回值 {
    // 函数体
}
func 函数名<泛型, ...>(形参: 泛型, ...) -> 返回值 where 泛型: 协议, ... {
    // 函数体
}

// 函数的协议约束简单例子
func f1<T: Equatable, U: Hashable>(p1: T, p2: U) -> U {
    return p2
}
func f2<T, U>(p1: T, p2: U) -> U where T: Equatable, U: Hashable {
    return p2
}

以上两种写法都是可以的,f1f2表达的意思一样,T需要遵循Equatable协议,U需要遵循Hashable协议。

到这就可以解释初体验的例子中T: Equatable是什么意思了,这是对泛型T的一个协议约束,必须要遵循Equatable协议,因为函数体内调用了contains方法,而contains要求数组中的元素必须遵循Equatable协议。

类或结构体的协议约束的语法:

// 类或结构体的泛型约束
class / struct 类名/结构体名: 泛型:协议, ... {
    // ...
}
class / struct 类名/结构体名: 泛型, ... where 泛型:协议, ... {
    // ...
}

class A1<T: Equatable, U: Hashable> {
    // ...
}
class B1<T, U> where T: Equatable, U: Hashable {
    // ...
}
struct S1<T: Equatable, U: Hashable> {
    // ...
}
class S2<T, U> where T: Equatable, U: Hashable {
    // ...
}

我们来为泛型类型中的例子CustomIterator添加一个extension,然后在extension中添加一个方法,判断某个元素是否存在在CustomIteratorelements中:

extension CustomIterator {
    func isExist(element: Element) -> Bool {
        // 错误:Argument type 'Element' does not conform to expected type 'Equatable'
        return elements.contains(element)
    }
}

按正常思路,判断一个元素是否存在数组中,自然会想到调用contains函数即可,但很可惜,编译器是报错的,因为这里使用的是泛型,也就是任意类型,而contains函数要求数组内的元素必须遵循Equatable协议,而泛型的任意类型自然不能保证都遵循了Equatable协议。

所以正确的写法如下:

// 写法1.1
struct CustomIterator<Element: Equatable> {
    // ...
}
// 写法1.2
struct CustomIterator<Element> where Element: Equatable {
    // ...
}

// 写法2
extension CustomIterator where Element: Equatable {
    func isExist(element: Element) -> Bool {
        return elements.contains(element)
    }
}

写法1.1和1.2的效果是一样的,我们可以对结构体或类中的泛型进行协议约束,但写法1和写法2是区别的:

  • 写法1:要使用CustomIterator这个结构体中的任意功能,Element就必须要遵循Equatable协议
  • 写法2:如果要使用CustomIterator扩展中的isExist函数时,Element才必须遵循Equatable协议

也就是说,将协议约束加在不同的地方,约束的范围是不一样的:

  1. 加在类名或结构体名之后,要使用这个类或者协议,就必须遵循这个协议,而加
  2. 加在类或结构体的extension之后,只有要使用这个extension中的函数时,才需要遵循这个协议,使用本体或其他没有加约束的extension中的函数或属性时,并不需要遵循这个协议

约束的范围就看具体的需求了~

  • 继承约束

顾名思义,就是说泛型必须是某个类的子类。

语法与协议约束一样,只是把协议换成具体的父类的类名。

就拿说方言(中国方言)来当例子,来自中国不同地方的人会说当地特有的方言,用程序来体现就是这样的:

// 中国人
class Chinese {
    func speak() {
        print("中国人说普通话~")
    }
}
// 湖南人 继承自 中国人
class HuNan: Chinese {
    override func speak() {
        print("湖南人说湖南话~")
    }
}
// 广东人 继承自 中国人
class GuangDong: Chinese {
    override func speak() {
        print("广东人说广东话~")
    }
}

// 定义泛型函数(中国人说话),接受一个泛型参数,自然要求该泛型类型必须继承Chinese
func chineseSpeak<T: Chinese>(_ chinese: T) {
    chinese.speak()
}

// 打印结果:湖南人说湖南话~
//         广东人说广东话~
chineseSpeak(HuNan())
chineseSpeak(GuangDong())

通过代码可以看到,继承约束与协议约束非常类似,只是泛型的限制条件一个是具体的父类,一个是协议。

  • 条件约束

在说条件约束之前,我们得先说说另一个概念,那就是关联类型

定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。

这样的协议称之为泛型协议,可以通过associatedtype关键字来指定关联类型。

下面例子定义了一个MyProtocol协议,该协议定义了一个关联类型MyType

protocol MyProtocol {
    associatedtype MyType
    mutating func append(_ item: MyType)
}

MyProtocol定义了一个append方法,添加一个新元素。MyProtocol协议需要指定任何通过append方法添加到容器中的元素和容器中的元素是相同类型。

为了达到这个目的,MyProtocol协议声明了一个关联类型MyType,写作 associatedtype MyType。这个协议无法定义MyType是什么类型的别名,这个信息将留给遵循协议的类型来提供。尽管如此,MyType别名提供了一种方式来引用 MyProtocol中元素的类型,并将之用于append方法,从而保证append方法能够正如预期地被执行。

下面是上文的CustomIterator简易版迭代器)遵循MyProtocol协议:

struct CustomIterator<Element>: MyProtocol {
    
    var elements: [Element] = []
    var num = 0
    mutating func next() -> Element? {
        if num == elements.count {
            num = 0
            return nil
        }
        num += 1
        return elements[num - 1]
    }
    
    init(elements: [Element], num: Int = 0) {
        self.elements = elements
        self.num = num
    }
    
    // MARK: - MyProtocol
    mutating func append(_ item: Element) {
        elements.append(item)
    }
}

由于Swift的类型推断,并不需要指定MyProtocolMyType的具体类型,CustomIterator只需通过append方法的item参数类型,就可以推断出 MyType的具体类型。

上述代码中append的实现,占位类型参数Element被用作append方法的item 参数。Swift可以据此推断出Element的类型即是MyType的类型。

说完了关联类型,接下来说条件约束。

通过where子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。可以通过将where关键字紧跟在函数体或者类型的大括号后面来定义where子句,where子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。

下面的例子定义了一个名为isAllItemsMatch的泛型函数,用来检查两个自定义迭代器实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回true,否则返回false

func isAllItemsMatch<A1: MyProtocol, A2: MyProtocol>(_ ite1: A1, _ ite2: A2) -> Bool where A1.MyType == A2.MyType, A1.MyType: Equatable {
    
    if ite1.count != ite2.count { return false }
    
    for i in 0..<arr1.count {
        if ite1[i] != ite2[i] {
            return false
        }
    }
    
    return true
}

// 以下为配套代码

protocol MyProtocol {
    associatedtype MyType
    mutating func append(_ item: MyType)
    var count: Int { get }
    subscript(i: Int) -> MyType { get }
}
struct CustomIterator<Element>: MyProtocol {
    
    ...
    
    subscript(i: Int) -> Element {
        return elements[i]
    }
    
    var count: Int {
        return elements.count
    }
}

这个函数接受ite1ite2两个参数。参数ite1的类型为A1,参数ite2的类型为A2A1A2是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。

这个函数的类型参数列表还定义了对两个类型参数的要求:

  1. A1必须符合MyProtocol协议(写作A1: MyProtocol)。
  2. A2必须符合MyProtocol协议(写作A2: MyProtocol)。
  3. A1MyType必须和A2MyType类型相同(写作A1.MyType == A2.MyType)。
  4. A1MyType必须符合Equatable协议(写作A1.MyType: Equatable),同时也就意味着A2.MyType也必须遵循Equatable协议。

下面演示了isAllItemsMatch函数的使用:

let cusIte1 = CustomIterator(elements: [1, 2, 3, 4])
let cusIte2 = CustomIterator(elements: [1, 2, 3, 4])

let cusIte3 = CustomIterator(elements: [1, 2, 3])
let cusIte4 = CustomIterator(elements: [1.1, 2.2, 3.3])
let cusIte5 = CustomIterator(elements: ["a", "b", "c"])

// true
print(isAllItemsMatch(cusIte1, cusIte2))
// false
print(isAllItemsMatch(cusIte1, cusIte3))
// 报错,类型不符
//print(isAllItemsMatch(cusIte1, cusIte4))
// 报错,类型不符
//print(isAllItemsMatch(cusIte1, cusIte5))
// 报错,类型不符
//print(isAllItemsMatch(cusIte4, cusIte5))

还有一种用法,就是在扩展泛型协议的时候,对关联类型加上约束:

// 两个测试协议
protocol TestProtoclol1 {}
protocol TestProtoclol2 {}

// 遵循两个测试协议的 测试类
class TestClass1: TestProtoclol1 {}
class TestClass2: TestProtoclol2 {}

// 泛型协议
protocol TestContainer {
    associatedtype ItemType
}

// 扩展泛型协议,并使泛型 ItemType 遵循 TestProtoclol1 协议
extension TestContainer where ItemType: TestProtoclol1 {
    var id: Int {
        return 0
    }
}
// 扩展泛型协议,并使泛型 ItemType 遵循 TestProtoclol2 协议
extension TestContainer where ItemType: TestProtoclol2 {
    var id: Int {
        return 1
    }
}

// 遵循泛型协议的测试类1
class TestTest1: TestContainer {
    typealias ItemType = TestClass1
    // 换为 TestClass2 ,测试一下,看看结果
//    typealias ItemType = TestClass2
}
// 遵循泛型协议的测试类2
class TestTest2: TestContainer {
    typealias ItemType = Int
}

let test1 = TestTest1()
// 打印:0
print(test1.id)
let test2 = TestTest2()
// 报错,调用不到,因为 Int 并没有遵循 TestProtoclol1 或 TestProtoclol2 协议
//print(test2.id)

上述例子,简单地解释就是,泛型协议扩展中的属性或函数,如果扩展对泛型做了其他的约束,想要调用到扩展中的属性或函数,泛型就必须满足这个约束。

如例子中,test1能获取到id属性,是因为指定了ItemTypeTestClass1TestClass1是遵循了TestProtoclol1协议的,所以获取到的id的值就是TestContainer第一个扩展中的id0test2无法获取id值,是因为指定了ItemTypeInt类型,而Int并未遵循TestProtoclol1TestProtoclol2协议。

看明白了这个例子,就可以思考一下,我们使用RxSwift时,调用rx的方法,例如:button.rx.tap...,总会有.rx这种签名的标识,RxSwift是怎么做到呢,原理就是关联类型和对泛型的扩展。

我们来尝试实现一下,我想调用自己对StringURL的扩展方法带上.shanzhu的标识:

public struct Shanzhu<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

public protocol ShanzhuCompatible {
    // 关联类型
    associatedtype CompatibleType
    
    // 实例
    var shanzhu: Shanzhu<CompatibleType> { get set }
    
    // 静态
    static var shanzhu: Shanzhu<CompatibleType>.Type { get set }
}

public extension ShanzhuCompatible {
    var shanzhu: Shanzhu<Self> {
        get {
            return Shanzhu(self)
        }
        set {}
    }
    
    static var shanzhu: Shanzhu<Self>.Type {
        get {
            return Shanzhu<Self>.self
        }
        set {}
    }
}

extension String: ShanzhuCompatible {}
extension URL: ShanzhuCompatible {}

// String 的扩展
extension Shanzhu where Base == String {
    func exTest() {
        print("String exTest suc~~ value:\(self)")
    }
}

// URL 的扩展
extension Shanzhu where Base == URL {
    func exTest() {
        print("URL exTest suc~~ value:\(self)")
    }
}

// 打印结果:String exTest suc~~ value:Shanzhu<String>(base: "a")
"a".shanzhu.exTest()
// 打印结果:URL exTest suc~~ value:Shanzhu<URL>(base: http://www.baidu.com)
URL(string: "http://www.baidu.com")?.shanzhu.exTest()

// 不带 .shanzhu 的标识,调用不到,为什么呢?
//"a".exTest()
//URL(string: "a")?.exTest()

这段代码的意思:

  1. 定义了一个名为Shanzhu的结构体,且定义了泛型Base,结构体有一个属性baseBase类型,并提供一个初始化方法init
  2. 定义了一个泛型协议ShanzhuCompatible,关联类型为CompatibleType,且有都名为shanzhu的一个实例变量和一个静态变量,均为可读可写,类型都是Shanzhu<CompatibleType>结构体;
  3. 扩展了ShanzhuCompatible泛型协议,并实现了名为shanzhu的一个实例变量和一个静态变量,get方法返回对应的实例变量和静态变量;
  4. StringURL遵循ShanzhuCompatible泛型协议;
  5. 扩展Shanzhu结构体,分别约束泛型BaseStringURL,都添加一个exTest方法。

String为例,"a".shanzhu.exTest()能调用成功:

  1. "a"String的实例,由于String遵循了ShanzhuCompatible协议,所以就有一个shanzhu的实例属性;
  2. "a".shanzhu时,ShanzhuCompatible的关联类型CompatibleType就会推导出是String类型,同时也就会推导出shanzhu实例属性是Shanzhu<String>类型;
  3. 由于我们扩展了Shanzhu结构体,且约束了BaseString类型,其中有一个exTest方法,那么Shanzhu<String>类型恰好是满足条件的,所以能调用到exTest方法。

URL也是一个道理,就不详细说明了。

分析完了,有没有一种代理的感觉,没错,就是代理,我们并不是直接对StringURL做的扩展吧,而是对Shanzhu结构体进行扩展并约束泛型BaseString类型,为了能调用到扩展中的方法或属性,我们又让String遵循了ShanzhuCompatible协议,这个协议中有Shanzhu结构体的实例变量和静态变量,而协议的关联类型就是Shanzhu结构体的泛型类型,当.shanzhu时,就能调用到Shanzhu结构体的扩展中与当前具体类型一致的方法或属性。

说到这,不带.shanzhu的标识,调用不到的原因就很清晰了。

D. 泛型下标

下标能够是泛型的,他们能够包含泛型where子句。你可以把占位符类型的名称写在 subscript后面的尖括号里,在下标代码体开始的标志的花括号之前写下泛型where子句。

extension CustomIterator {
    subscript<Indices: Sequence>(indices: Indices) -> [Element]
        where Indices.Iterator.Element == Int {
            var result = [Element]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}

这个CustomIterator的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:

这个CustomIterator的扩展添加了一个下标:下标是一个序列的索引,返回的则是索引所在的项目的值所构成的数组。这个泛型下标的约束如下:

  1. 在尖括号中的泛型参数Indices,必须是符合标准库中的Sequence协议的类型。
  2. 下标使用的单一的参数,indices,必须是Indices的实例。
  3. 泛型where子句要求Sequence(Indices)的迭代器,其所有的元素都是 Int类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的。

综合一下,这些约束意味着,传入到indices下标,是一个整型的序列。

我们来看一下使用效果:

let subIte = CustomIterator(elements: [1, 2, 3, 4])
// 打印结果:[1, 3]
print(subIte[[0, 2]])

使用泛型下标后,使用下标访问数组元素时,就不仅限于访问某一个,而是可以访问一组了。

到这,本篇文章要说的就是这些了,泛型远远不止这些内容,本篇文章算是一个抛砖引玉吧,如有说的不准确的地方,欢迎指正~

--

参考文档:

https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Reactive.swift
http://www.swift51.com/swift4.0/chapter2/23_Generics.html
https://swift.gg/2018/08/28/swift-generics/
https://www.cnblogs.com/ming1025/p/6072715.html
https://www.cnblogs.com/pengser/p/4986657.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 泛型的概念 泛型代码可根据自定义需求,写出适用于任何类型、灵活且可重用的函数和类型,避免重复的代码,用一种清晰和抽...
    伯wen阅读 389评论 0 2
  • 泛型代码可以确保你写出灵活的,可重用的函数和定义出任何你所确定好的需求的类型。你的可以写出避免重复的代码,并且用一...
    iOS_Developer阅读 789评论 0 0
  • 原文:Generics Manifesto -- Douglas Gregor 译者注 在我慢慢地深入使用 Swi...
    kemchenj阅读 1,904评论 0 6
  • 泛型(Generics) 泛型代码允许你定义适用于任何类型的,符合你设置的要求的,灵活且可重用的 函数和类型。泛型...
    果啤阅读 663评论 0 0
  • 本文转载自http://blog.csdn.net/youshaoduo/article/details/5486...
    desunire阅读 1,928评论 0 0