阅读更多

0顶
0踩

移动开发

转载新闻 当 Swift 中的协议遇到泛型

2016-01-27 16:07 by 副主编 mengyidan1988 评论(2) 有7631人浏览
Swift 中的协议如果需要泛型化,可以通过抽象类型成员的方式实现,而不是在参数类型上做文章。至此,协议本身可以不再被当成是一个类型,而是看做一个通用的约束。英文原文

问题
如果你曾将一个泛型协议当做类型使用:
protocol GenericProtocol {  
    typealias AbstractType
    func magic() -> AbstractType
}
let list : [GenericProtocol] = []  

很快 Xcode 会给你报个错:
引用

Protocol 'GenericProtocol' can only be used as a generic constraint because it has Self or associated type requirements. 

如果你不熟悉泛型,可能会奇怪,为什么泛型协议不能当做类型使用呢?答案很简单:泛型协议的名称,即:GenericProtocol 表示一组类型,并不是一个单一类型。比如你有一个关于 GenericProtocol 的随机数组,并不能确定每个元素的 magic() 方法返回的类型到底是什么,因为数组中每个元素可能都是不同的。

这个问题在 StackOverflow 也有很好的讨论

类型成员 VS 参数化
在 Swift 中,泛型化协议可以通过使用抽象类型成员来达成(使用关键字 typealias),而类、结构体、方法和函数则通过使用类型参数化来达成泛型。(观察下面的例子)
func genericFunc<T>(ts : [T]) -> Bool {  
    return true; // not very exciting
}

事实证明,一般情况下,只要任何语言同时支持这两种表达方式,那么类型成员和参数化最后产生的效果是一样的。我们可以想象 Swift 在未来的迭代中支持通过参数化来达成泛型,那么下面的代码可以这么写:
protocol GenericProtocolWithParam<T> {  
    func magic() -> T
}
let list : [GenericProtocolWithParam<String>] = []  

如果你对类型参数化和抽象类型成员之间的权衡利弊感兴趣,请看官方开发者论坛的这个帖子,以及 Scala 是如何处理这个问题的。

解决方法
我们可以使用 thunk 来解决在协议中缺少类型参数化的问题。
  • 首先定义一个结构体,该结构体实现了协议的所有方法。
  • 在具体的实现方法中,再转发给(调用)『实现协议的抽象类型』。
  • 在结构体的初始化过程中,这个实现了协议的抽象类型会被当做参数传入(依赖注射)

在实际的操作中,我们都是通过闭包来完成的:
引用

维基百科指出:一个 thunk 通常是一个子程序,被创造出来自动地,协助调用其他的子程序。阮一峰也有篇介绍文章

protocol GenericProtocol {  
    typealias AbstractType
    func magic() -> AbstractType
}

struct GenericProtocolThunk<T> : GenericProtocol {  
    // closure which will be used to implement `magic()` as declared in the protocol
    private let _magic : () -> T

    // `T` is effectively a handle for `AbstractType` in the protocol
    init<P : GenericProtocol where P.AbstractType == T>(_ dep : P) {
        // requires Swift 2, otherwise create explicit closure
        _magic = dep.magic
    }

    func magic() -> T {
        // any protocol methods are implemented by forwarding
        return _magic()
    }

一旦我们拥有一个 thunk,我们可以把他当做类型使用(需要我们自己提供具体的类型)
struct StringMagic : GenericProtocol {  
    typealias AbstractType = String
    func magic() -> String {
        return "Magic!"
    }
}

// we can now create arrays of thunks if we specify the type param
let magicians : [GenericProtocolThunk<String>] = [GenericProtocolThunk(StringMagic())]  
magicians.first!.magic() // returns "Magic!"  

是否需要 Self
在协议的上下文声明中,Self 表示遵守协议的类型。这使得协议更加通用,self 可以被看做是针对便利类型成员的方法糖。例如:
protocol EquatableSelf {  
    func equals(other : Self) -> Bool
}

// By adopting `EquatableSelf`, every occurrence of `Self` in the protocol gets
// semantically replaced with `ImplicitStruct`
struct ImplicitStruct : EquatableSelf {  
    var val : Int64
    func equals(other: ImplicitStruct) -> Bool {
        return self.val == other.val;
    }
}

如果我们没有能力使用 Self,可以采用下面的方式达到同样的目的:
protocol EquatableTypealias {  
    typealias EquatableType
    func equals(other : EquatableType) -> Bool
}

struct ExplicitStruct : EquatableTypealias {  
    typealias EquatableType = ExplicitStruct
    var val : Int64
    func equals(other: ExplicitStruct) -> Bool {
        return self.val == other.val;
    }
}

面向协议编程
协议在 Swift 中扮演中流砥柱的角色,正如 Dave Abrahams 在 WWDC 2015 上 Session 408 中说的那样:
引用
... When we made Swift, we made the first protocol-oriented programming language.

如果你对 Swift 中的面向协议编程感兴趣,那么我们强烈推荐去观摩完整视频。尤其下面这张幻灯片很好地阐述了『何时需要添加 Self』:



泛型和类型变异
引入泛型而来的一个问题就是泛型类型的变异(variance)。简单的说,他涉及以下问题:
class Animal {  
    // Animal-specific ivars + methods
}

class Cat : Animal {  
    // Cat-specific ivars + methods
}

var cats : [Cat] = [Cat()]

// Here, we're typecasting from [Cat] to [Animal].
// Such typecasts could be unsafe depending on the details.
var animals : [Animal] = cats  

我希望以后能有机会和你们探讨这个问题,建议先从维基百科这个页面 入手了解一下类型变异(主要指类型的协变和逆变 covariance-and-contravariance)
引用
SwiftGG 也有一篇关于逆变和协变不错的科普文,通过此文我们可以知道 Swift 中的泛型是「不变的(Invariance)」
结论:optionals,arrays,dictionaries 在 Swift 中都是协变(covariant)的,而泛型以及自定义的类型都是不变的(Invariance),function 则视具体情况而定


题图:『When Harry Met Sally』:)

本文转自:http://chengway.in/
  • 大小: 127.6 KB
0
0
评论 共 2 条 请登录后发表评论
2 楼 再现东北豹 2016-02-16 11:25
要任何语言同时支持这两种表达方式,那么类型成员和参数化最后产生的效果是一样的。我们可以想象 Swift 在未来的迭代中支持通过参bnavfjadsa.blog.com数化来达成泛型
1 楼 再现东北豹 2016-01-27 17:36
如果你不熟悉泛型,可能会奇怪,为什么泛型协议不能当做类型使用呢?答案很linshi06757586gon.blog.com简单:泛型协议的名称,即:GenericProtocol 表示一组类型,并不是一个单一类型。比如你有一个关于 GenericProtocol 的随机数组,并不能确定每个元素的 magic() 方法返回的类型到底是什么

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 11、Swift中的泛型

    12、Swift中的泛型1、泛型函数1.1 泛型函数基础1.2 带有类型约束的泛型函数2、泛型类型3、泛型扩展 1、泛型函数 1.1 泛型函数基础 func swapTwoValues&lt;T&gt;(a:inout T,b:inout T){ (b,a) = (a,b) } var a:...

  • 如何在Swift中使用泛型

    在Swift中,数组可以保存任何类型的数据。 如果需要整数,字符串或浮点数的数组,则可以使用Swift标准库创建一个数组。 声明数组时应定义的类型。 数组是使用泛型的常见示例。 如果要实现自己的集合,则肯定要使用...

  • Swift 运用协议泛型封装网络层

    swift 版本: 4.1 Xcode 版本 9.3 (9E145) 基于 Alamofire 和 Moya 再封装 代码 Github 地址: MoyaDemo 一、前言 最近进入新公司开展新项目,我发现公司项目的网络层很 OC ,最让人无法忍受的是数据解析是在...

  • 聊一聊Swift泛型

    泛型是Swift最强大的特性之一,而且大部分Swift标准库都是用泛型代码构建的。事实上,您在整个语言指南中都使用了泛型,即使您没有意识到这一点。例如,Swift的数组和字典类型都是泛型集合。您可以创建一个包含Int值...

  • 如何在Swift中掌握协议

    在本文中,我们将深入讨论使用Swift 5.3的协议Protocol。让我们以协议为基础开始。 符合协议 协议允许您将相似的方法,功能和属性分组。斯威夫特,您可以指定这些接口的保证class,struct和enum类型。只有class类型...

  • Swift-使用泛型范围获取数组或区间中随机元素

    实际开发中,通常会遇到获取一个随机数 随机产生一个数字 随机获取数组中的一个元素 随机获取一种颜色 … 简单介绍:一、最简单操作:给定一个最大值,随机获取一个0到最大值之间的数字let int = arc4random_uniform...

  • 移动开发—Swift 进阶;泛型

    实际上,甚至你都没有意识到在语言指南中一直在使用泛型。例如,Swift的 Array和 Dictionary 类型都是泛型集合。 你可以创建一个容纳 Int 值的数组,或者容纳String 值的数组,甚至容纳任何 Swift 可以创建的其他...

  • 如何在Swift协议中定义可选方法?

    Is it possible in Swift? 在Swift中有可能吗? If not then is there a workaround to do it? 如果没有,那么是否有解决方法?

  • swift 两数相加 分析_让我们剖析泛型和Swift中Any类型之间的区别

    swift 两数相加 分析Swift is one of the topmost type-safe languages nowadays. ??? Swift是当今最顶级的类型安全语言之一。 ??? 哦,等等! 语言是类型安全的,这意味着什么? ? (Ohhh wait!! What does it ...

  • Swift 中的幻象类型

    本周,让我们来看看一种技术,它可以让我们利用 Swift 的类型系统在编译时执行更多种类的数据验证——消除更多潜在的歧义来源,并帮助我们在整个代码库中保持类型安全——通过使用幻象类型(phantom types)。...

  • Swift协议与关联类型

    作者丨狐友技术团队来源丨搜狐技术产品(ID:sohu-tech)本文字数:9972字预计阅读时间:25分钟Swift协议与关联类型目录前言问题关联协议的限制使用关联协议需要做泛型改造使用关联协议失去了动态类型派发的能力关联...

  • # Swift学习 # Swift中容易忽略的小知识

    协议中是不可以使用泛型的,但是我们还可以在协议中约定一个typealias 要求必须实现,这样也就可以在一定范围内,对协议进行约束 protocol GeneratorType { associatedtype Generator func doSth(demo: String) -&gt;...

  • swift:入门知识之泛型

    在尖括号里写一个名字来创建一个泛型函数或者类型...例如,要限定实现一个协议的类型,需要限定两个类型要相同,或者限定一个类必须有一个特定的父类 先给一个具体举例如下: //泛型函数 func repeat&lt;ItemType...

  • Swift3.0 - 泛型

    Swift3.0 - 真的很简单 Swift3.0 - 数据类型 Swift3.0 - Array Swift3.0 - 字典 Swift3.0 - 可选值 Swift3.0 - 集合 Swift3.0 - 流控制 Swift3.0 - 对象和类 Swift3.0 - 属性 Swift3.0 - 函数和闭包 Swift3.0 - 初始...

  • 如何在Swift中使用Result

    尽管有很多不同的方法可以对Result类型进行建模,但是Swift标准库中内置的方法被声明为通用枚举,它针对结果可能包含的成功值以及遇到的任何错误进行了强类型化。看起来像这样: enum Result&lt;Success, Failure&gt...

  • iOS — Swift高级分享:SWIFT协议的替代方案

    毫无疑问,协议是SWIFT总体设计的主要部分-并且可以提供一种很好的方法来创建抽象、分离关注点和提高系统或功能的整体灵活性。通过不强烈地将类型绑定在一起,而是通过更抽象的接口连接代码库的各个部分,我们通常会...

  • Swift 周报 第二十九期

    最无情的永远不是环境,而是缺乏勇气的内心。Swift社区与你一起,赤胆平乱世,长枪定江山!

  • Swift 周报 第三十四期

    恰似烈日灼身,清风缕缕慰我清静。恰似无边心海,Swift社区渡我心安!

  • 基于springboot大学生就业信息管理系统源码数据库文档.zip

    基于springboot大学生就业信息管理系统源码数据库文档.zip

  • 基于java的驾校收支管理可视化平台的开题报告.docx

    基于java的驾校收支管理可视化平台的开题报告

Global site tag (gtag.js) - Google Analytics