Swift 4 泛型:如何在你的代码或App里应用泛型

原文连接:swift.gg/2018/08/28/…
做者:Andrew Jaffee
译者:BigLuo
校对:numbbbbb,muhlenXi
定稿:CMB
html

问题 1:我可否写一个 Swift 函数用于查找在任意数组中存储的任意类型任何实例对象的位置\索引。算法

问题 2:我可否写一个 Swift 函数用于肯定在任意数组中存储的任意类型任何实例对象的类型。数据库

我所说的 "任何类型",包括自定义类型,好比咱们本身定义的 Class 类型。提示:我知道我可以用 Swift Array 类型的内置方法,如 index 和 contains,但今天我将会用简单代码实例来讲明 Swift 泛型中的一些特性。express

通常来讲,我将泛型编程做以下定义:编程

… a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by ML in 1973, permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication.swift

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

特别的指出,来自苹果Swift文档 关于"泛型"话题的说明:安全

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.闭包

Generics are one of the most powerful features of Swift , and much of the Swift standard library is built with generic code. … For example, Swift ’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift . Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be. …app

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

泛型是 Swift 中最强大的特性之一,大量的 Swift 标准库使用了泛型编码。例如, Swift 的数组和字典都是泛型集合。你能够建立一个存有整型值或者字符串值的数组,有必要的话,还能够建立一个任何 Swift 支持类型的数组。相似的,你也能够建立一个字典用于存储任意指定类型的值。

我一直提倡构建可复用,简洁,可维护的代码,对于 Swift 中的泛型,若是运用恰当,能某种程度上帮助我实现上面提到的效果。因此对于上面两个问题,个人答案是 "YES"。

生活在一个特定类型编码的世界

让咱们写一个 Swift 的方法来讲明在一个字符串数组中是否存在特定的一个字符串:

func existsManual(item:String, inArray:[String]) -> Bool
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1
        }
    }
    
    if found
    {
        return true
    }
    else
    {
        return false
    }
}
复制代码

让咱们测试这个方法:

let strings = ["Ishmael", "Jacob", "Ezekiel"]
 
let nameExistsInArray = existsManual(item: "Ishmael", inArray: strings)
// returns true
 
let nameExistsInArray1 = existsManual(item: "Bubba", inArray: strings)
// returns false
复制代码

在建立了用于查找 String 数组的 existsManual 函数后。假如我决定想要一些相似的函数用于搜索 IntegerFloat,和 Double 数组 — 甚至用于查找数组中自定义类呢?我最终花费了宝贵的时间写了不少作一样事情的函数。我须要写不少代码来实现。若是我发现了一个新的/更快的搜索算法呢?又若是在个人搜索算法有一个 bug 呢?我不得不改变我全部的查找方法的版本。我发现这简直是个复用地狱:

func existsManual(item:String, inArray:[String]) -> Bool
...
func existsManual(item:Int, inArray:[Int]) -> Bool
...
func existsManual(item:Float, inArray:[Float]) -> Bool
...
func existsManual(item:Double, inArray:[Double]) -> Bool
...
// "Person" is a custom class we'll create
// "Person" 是咱们将要建立的自定义的类
func existsManual(item:Person, inArray:[Person]) -> Bool
复制代码

问题

咱们已经厌烦了活在一个处理类型的世界里,不得不为每一个咱们想要查找的数组类型建立新的方法。终究这给咱们带来了大量的技术负债。因为现代软件难以置信的复杂性,像你我这样的开发者须要使用更好地实践,更好的技术,更好的方法,用咱们的神经元最大程度的控制这种混乱。据估计 Window 7 包含大约 4 千万行代码而 macOS 10.4 (Tiger) 包含大约 8.5 千万行代码,预估像这样的系统潜在行为次数都是不可能的。

泛型的解决之道

(再次紧记学习泛型的目的,咱们依旧假设 Swift 的数组类型的内置的函数,indexcontains ,不存在。)

让咱们先尝试写这样一个 Swift 函数,判断 Swift 的标准类型(例如 StringIntegerFloatDouble)的一个特定实例是否存在于这个 Swift 标准类型的数组中。怎么作呢?

让咱们切换到 Swift 泛型,特别是泛型函数,类型参数,类型约束以及 Equatable 协议。在没有定义任何术语前,我写了一些代码,思考一下你看到的。

func exists<T: Equatable>(item: T, inArray: [T]) -> Bool
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1
        }
    }
    
    if found
    {
        return true
    }
    else
    {
        return false
    }
}
复制代码

让咱们测试下我新写的泛型方法

let myFriends:[String] = ["John", "Dave", "Jim"]
 
let isOneOfMyFriends = exists(item: "Dave", inArray: myFriends)
// returns true
 
let isOneOfMyFriends1 = exists(item: "Laura", inArray: myFriends)
// returns false
 
let myNumbers:[Int] = [1,2,3,4,5,6]
 
let isOneOfMyNumbers = exists(item: 3, inArray: myNumbers)
// returns true
 
let isOneOfMyNumbers1 = exists(item: 0, inArray: myNumbers)
// returns false
 
let myNumbersFloat:[Float] = [1.0,2.0,3.0,4.0,5.0,6.0,]
 
let isOneOfMyFloatNumbers = exists(item: 3.0000, inArray: myNumbersFloat)
// returns true
复制代码

我新写 exists 方法是一个泛型函数,这个方法“能正常工做在任何参数类型上”,此外,让咱们看看它的函数签名。

func exists<T: Equatable >(item: T, inArray: [T]) -> Bool
复制代码

咱们看到 那个 函数使用一个占位符类型名字(名叫 T, 在这个案例)而不是真正的类型名(好比:IntStirng,或 Double)占位符类型名没有指定 T 必须是什么,但他说明了 [item][inArray] 必须是相同的类型 T 不管 T 表明什么,每当 [exists(_:_:)] 函数被调用时,真实的类型用于替代 T 被肯定下来。

这个 exists 函数中的占位符类型 T 被称为类型参数

指定和命名了占位符的类型,直接写在函数名称的后面,在一对尖括号之间(好比 )。

一旦你指定一个类型参数你能够用它来定义函数参数的类型(好比:[item] and [inArray] [exists(_:_:) 函数)或者做为函数返回值的类型,在任何条件下,当函数被调用的时候类型参数会被真实类型替代。

为了强化咱们目前已经学到的,下面是一个 Swift 函数,该函数可以找到存储在数组中任何类型实例的索引。

func find<T: Equatable>(item: T, inArray: [T]) -> Int?
{
    var index:Int = 0
    var found = false
    
    while (index < inArray.count && found == false)
    {
        if item == inArray[index]
        {
            found = true
        }
        else
        {
            index = index + 1
        }
    }
    
    if found
    {
        return index
    }
    else
    {
        return nil
    }
}
复制代码

让咱们测试下它

let myFriends:[String] = ["John", "Dave", "Jim", "Arthur", "Lancelot"]
 
let findIndexOfFriend = find(item: "John", inArray: myFriends)
// returns 0
 
let findIndexOfFriend1 = find(item: "Arthur", inArray: myFriends)
// returns 3
 
let findIndexOfFriend2 = find(item: "Guinevere", inArray: myFriends)
// returns nil
复制代码

关于 Equatable 协议

exists 函数中 <T: Equatable > 标注是什么呢?它叫作类型约束,它规定了"那个类型参数必须继承自一个具体的类,或者遵照一个特定的协议或是协议组合。我指定了 exists 函数参数,item: TinArray: [T], 必须是类型 T, 而类型 T 必须遵照协议 Equatable 协议,为何是这样的呢?

全部的 Swift 内置类型已经被构建支持 Equatable 协议。来自 [Apple docs](developer.apple.com/documentati… Swift / Equatable): “遵照 Equatable 协议的类型进行相等比较,使用等于运算符(==)判断相等,或者使用不等运算符(!=)判断不等”。这就是为何个人泛型函数 "exists" 可以在 Swift 的类型(如 StringIntegerFloatDouble)上正常工做。全部这些类型都定义了 ==!= 运算符。

自定义类型和泛型

假如我声明了一个新的类叫作 “BasicPerson” 以下所示。我能用个人 exists" 函数来找出在数组中是否有 "BasicPerson" 类的一个实例的类型么?不行!为何不行?看看下面这个代码,咱们接下来讨论它:

class BasicPerson {
    var name: String
    var weight: Int
    var sex: String
    
    init(weight: Int, name: String, sex: String)
    {
        self.name = name
        self.weight = weight
        self.sex = sex
    }
}
 
let Jim = BasicPerson(weight: 180, name: "Jim Patterson", sex: "M")
let Sam = BasicPerson(weight: 120, name: "Sam Patterson", sex: "F")
let Sara = BasicPerson(weight: 115, name: "Sara Lewis", sex: "F")
 
let basicPersons = [Jim, Sam, Sara]
 
let isSamABasicPerson = exists(item: Sam, inArray: basicPersons)
复制代码

看到最后一行,由于它有一个编译错误:

error: in argument type '[BasicPerson]', 'BasicPerson' does not conform to expected type 'Equatable'
let isSamABasicPerson = exists(item: Sam, inArray: basicPersons)
复制代码

image-20180813173212026

这很糟糕了, 在 "BasicPerson" 类型的数组里面,你不能使用 Swift 数组的内建函数 indexcontains。(你必须定义一个闭包,每当你想使用那两个方法 blah,blah,blah… 这个我就不提了。)

再次回到问题,为何报错?

由于 "BasicPerson" 类没有遵照 Equeatable 协议(这是一个提示,请看下文咯)

遵照 Equatable 协议

为了容许个人 "BasicPerson" 类是可使用个人 "exists" 和 "find" 泛型方法,全部我须要作的是:

  • 让类遵照 Equatable 协议
  • 重载类实例的 == 操做符

注意[这个](developer.apple.com/documentati… Swift / Equatable )"Swift 标准库为全部遵循 Euqatable 协议的类型提供了不等于(!=) 操做符的实现。经过调用自定义的 == 函数获取它的取反结果。

若是你对操做符重载不熟悉,我建议你阅读这些主题,连接在这里这里的.相信我,你会想知道操做符重载的。

提示:我重命名 "BasicPerson" 类为 "Person" 让他们在相同的 Swift Playground 文件能共存,接着咱们来到 "Person" 类。

我将实现 == 操做符,因此它能比较 "Person" 类不一样实例间的 "name", "weight", 和 "sex" 属性。若是两个 "Person" 类的实例有相同的的三个属性。则他们是相等的。若是有一个属性不一样,则他们是不相等的(!=)。这就是为何个人 "Person" 类遵照了 Equatable 协议:

lass Person: Equatable 
{
    var name:String
    var weight:Int
    var sex:String
    
    init(weight: Int, name: String, sex: String)
    {
        self.name = name
        self.weight = weight
        self.sex = sex
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool
    {
        if lhs.weight == rhs.weight &&
            lhs.name == rhs.name &&
            lhs.sex == rhs.sex
        {
            return true
        }
        else
        {
            return false
        }
    }
}
复制代码

注意上面的 == 重载方法,这须要让 "Person" 遵照 Equatable 协议。注意 == 重载方法中的 lhsrhs 参数。这是通用的,当重载操做符时,代码中等号两边的对象应该与参数中的物理位置一致,如:

lhs == rhs
left-hand side == right-hand side
复制代码

它实用吗?

若是你跟随着个人指南,你能建立像我写的 "exists" 和 "find" 泛型函数用于任何你建立的新类型,如类或者结构体。让你自定义的类和结构体集合类型遵照 Equatable 协议,像 Swift 里面 Array 中的内置函数 indexcontains。他们确实有用:

let Joe = Person(weight: 180, name: "Joe Patterson", sex: "M")
let Pam = Person(weight: 120, name: "Pam Patterson", sex: "F")
let Sue = Person(weight: 115, name: "Sue Lewis", sex: "F")
let Jeb = Person(weight: 180, name: "Jeb Patterson", sex: "M")
let Bob = Person(weight: 200, name: "Bob Smith", sex: "M")
 
let myPeople:Array = [Joe, Pam, Sue, Jeb]
 
let indexOfOneOfMyPeople = find(item: Jeb, inArray: myPeople)
// returns 3 from custom generic function
// 返回 3 源自自定义泛型函数
 
let indexOfOneOfMyPeople1 = myPeople.index(of: Jeb)
// returns 3 from built-in Swift member function
// 返回 3 源自 Swift 内建成员函数
 
let isSueOneOfMyPeople = exists(item: Sue, inArray: myPeople)
// returns true from custom generic function
// 返回 true 源自自定义泛型函数
 
let isSueOneOfMyPeople1 = myPeople.contains(Sue)
// returns true from built-in Swift member function
// 返回 true 源自 Swift 内建成员函数
 
let indexOfBob = find(item: Bob, inArray: myPeople)
// returns nil from custom generic function
// 返回 nil 源自自定义泛型函数
 
let indexOfBob1 = myPeople.index(of: Bob)
// returns nil from built-in Swift member function
// 返回 nil 源自 Swift 内建成员函数
 
let isBobOneOfMyPeople1 = exists(item: Bob, inArray: myPeople)
// returns false from custom generic function
// 返回 false 源自自定义泛型函数
 
let isBobOneOfMyPeople2 = myPeople.contains(Bob)
// returns false from built-in Swift member function
// 返回 false 源自 Swift 内建成员函数
 
if Joe == Pam
{
    print("they're equal")
}
else
{
    print("they're not equal")
}
// returns "they're not equal"
复制代码

扩展阅读

苹果提示关于 Equatable 协议的更多好处:

Adding Equatable conformance to your custom types means that you can use more convenient APIs when searching for particular instances in a collection. Equatable is also the base protocol for the Hashable and Comparable protocols, which allow more uses of your custom type, such as constructing sets or sorting the elements of a collection.

让你的自定义类型遵循 Equatable 协议意味着你可使用许多系统提供的 API 来让你在一个集合里面查找特定一个实例变得更加方便。

Equatable 协议也是 Hashable 协议和 Comparable 协议的基础协议。这容许你使用更多的自定义类型,好比构建集合或者排序集合中的元素。

好比,若是你遵照了 comparable 协议,你能重载和使用 <><=>= 操做符,这真的很 Cool。

须知

想一下咱们的 "Person" 类,假如咱们有一些相似下文所示的实例:

let Joe = Person(weight: 180, name: "Joe Patterson", sex: "M")
let Pam = Person(weight: 120, name: "Pam Patterson", sex: "F")
let Sue = Person(weight: 115, name: "Sue Lewis", sex: "F")
let Jeb = Person(weight: 180, name: "Jeb Patterson", sex: "M")
let Bob = Person(weight: 200, name: "Bob Smith", sex: "M")
let Jan = Person(weight: 115, name: "Sue Lewis", sex: "F")
 
if Jan == Sue
{
    print("they're equal")
}
else
{
    print("they're not equal")
}
// returns "they're equal" for 2 different objects
// 返回 "they're equal" 对于两个不一样的对象 
复制代码

看最后一行,由于这些 "Person" 对象中 "Jan" 和 "Sue" 对象是绝对相等的。即便他们是两个不一样的实例对象。你的软件好坏仅仅取决于你的设计。在数据库的术语体系里, "Person" 类集合中,你会须要一个"主键" — 或许在类的设计中,能够添加一个索引变量。好比一个社会安全码、或者你熟知的其余的惟一值来保证 "Person" 类实例在集合 (Array) 中的惟一性,固然啦,你也可使用 === 操做符。

享用吧!

相关文章
相关标签/搜索