新视角:通过函数式编程的范畴理论来看待这个问题会更容易理解;

在低层类型无法很好表达的类型,可以将其转化为高阶类型进行表示。

将协议的实现类型转化为monad类型;

解决将具有关联类型的协议当作类型的

swift所谓的类型擦除是指通过协议的泛型化、继承、封装,将协议类型抹除的过程;

泛化的最终目的:

将实现了同一协议,并且关联类型一样的类型作为同一类型来看待。

接口类型最终目的:

1、接口类型用实例类型初始化后的行为和实例类型的行为一致;

2、实现了协议的可类型化,和实例类型的封装;

3、隐藏了底层的实现细节,合并了类型功能和封装功能;

let testGo: [_AnyCupBase<Coffee>] = [_AnyCupBox(CoffeeMug()), _AnyCupBox(CoffeeCup())]

终极限制:关联类型的一致性必须的到满足;

涉及到的元素:

关联类型的可指定类型;

协议的实例类型;

协议的泛化类型:协议的可类型化;用于做类型使用;用于确保关联类型的一致性;用于将容器类型从关联类型的功能中解放出来

容器类型、泛化类型的继承:1、用于包装协议的实例类型;2、用于给协议的泛化类型指定类型;

接口类型:1、接口层面只包含泛型化的关联类型;2、将协议的泛化类型作为类型来引用容器类型;3、初始化容器类型,赋值给协议的泛化类型类型;

协议的泛化是协议可类型化的基础;

消息的传递通过类似与代理机转发机制来实现。

Introduction

If you have ever tried to use Swift protocols with associated types as types, you probably had (just like me) a very rough first (and possibly second) ride.  Things that should be intuitively doable are just not and the compiler gives infuriating error messages.  It comes down to the fact that the language is not yet “finished” (and, as a result, protocols are not really usable as types — yet).  The good news is that problems have workarounds and one such workaround is “type erasure” (not the Java compiler feature, something else that goes by the same name).  Apple uses the type erasure pattern every time you read Any<Something> and cleverer people than myself described it well (more information in the references at the end).  This is my attempt at understanding the pattern.

So what’s the problem?

Imagine that you have a protocol Cup  that describes a generic cup, with an associated type Content  that describes the cup’s content.  Imagine that this protocol has some methods and some properties.  For fun (and for realism) let’s make these methods mutating  and returning and instance of Self  (the specific instance of Cup we are dealing with).

 
1
2
3
4
5
6
7
8
protocol Cup {
    // Have an associated type
    associatedtype Content
    var description: String { get }
    // Use this associated type
    mutating func replaceContent(with newContent: Content) -> Self
    func showContent() -> Content?
}

Let’s define some concrete types for Content .

 
1
2
3
4
5
6
7
8
9
struct Tea {
    let description = "tea"
    let hasMilk: Bool
}
 
struct Coffee {
    let description = "coffee"
    let isDecaf: Bool
}

Let’s also add some concrete types for the protocol Cup . A cup of tea and a cup of coffee perhaps.  Honouring the spirit of Swift, I’ll make them value types ( struct ) but it would work in the same way for a class , without any further adjustment.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct TeaMug: Cup {
    // Assign a concrete type to the associated type as your first action. This is not necessary but will unleash the power of autocomplete.
    typealias Content = Tea
    let description = "tea mug"
    private var content: Tea?
    // Please note that after specialising Content, these lines will autocomplete nicely
    mutating func replaceContent(with newContent: Tea) -> TeaMug {
        content = newContent
        return self
    }
    func showContent() -> Tea? {
        return content
    }
}
 
struct CoffeeMug: Cup {
    typealias Content = Coffee
    let description = "coffee mug"
    private var content: Coffee?
    mutating func replaceContent(with newContent: Coffee) -> CoffeeMug {
        content = newContent
        return self
    }
    func showContent() -> Coffee? {
        return content
    }
}
 
struct CoffeeCup: Cup {
    typealias Content = Coffee
    let description = "coffee cup"
    private var content: Coffee?
    mutating func replaceContent(with newContent: Coffee) -> CoffeeCup {
        content = newContent
        return self
    }
    func showContent() -> Coffee? {
        return content
    }
}

We can nicely create different instances of cups with different contents.

 
1
2
3
4
5
var myCoffeeCup = CoffeeCup()
myCoffeeCup.replaceContent(with: Coffee(isDecaf: false))
 
var myTeaMug = TeaMug()
myTeaMug.replaceContent(with: Tea(hasMilk: true))

And here are the problems that I invariably face when I start thinking of Cup  as a type (it’s not a type! It’s a protocol!).

(1) Collection of protocol types

 
1
2
3
4
let cupboardOne: [Cup] = [CoffeeMug(), CoffeeCup()]
let cupboardTwo: [Cup] = [TeaMug(), TeaMug()]
// The following works but it's probably not what you want, the type is not generic and is a concrete [TeaMug] rather than a generic [Cup]
let cupboardThree = [TeaMug(), TeaMug()]

(2) Returning a protocol type in a function

 
1
2
3
func giveMeACup() -> Cup? {
    return cupboardOne.first
}

(3) Taking a protocol type as a parameter  to a function

 
1
2
3
func describe(cup: Cup) {
    print(cup.description)
}

(4) Using a protocol type as a type for class or struct properties

 
1
2
3
class mySuperDuperClass {
    var cup: Cup
}

The error message

The error message, in all cases above, is always the same: protocol ‘Cup’ can only be used as a generic constraint because it has Self or associated type requirements.  In other words, if a protocol has associated types you cannot use such protocol as a type. It makes sense, the Swift compiler will not know what exact protocol type you are dealing with unless the associated type is specified.  A nice mnemonic way to put could be:

Swift does not like multidimensional type uncertainty, but one dimensional type uncertainty is OK.

If we can somehow fix the associated type Content , we can make it work.  There are individual workarounds that work for problems (2) to (4) but they have different limitations: we are not going to discuss them here.  We are going to present a more general solution that takes care of all four problems.

The Solution

The architecture for the solution requires a few steps and a lot of patience to follow.  I will try my best to be clear.  Here we go.

(1) Base class mocking the protocol

First let’s define a base class that describes the protocol Cup .  The associated type Content  is now turned into a generic parameter for the class.  The class cannot be used directly because it cannot be initialised.  Furthermore, every property and every method throw a fatal error upon access or call: they must be overridden, all of them.  Why do we need this base class?  It’s a trick.  As we will see later, we need to be able to store a subclass of it but we cannot refer directly to an instance of such subclass.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private class _AnyCupBase<Content>: Cup {
 
    // All the properties in the Cup protocol go here.
    var description: String {
        get { fatalError("Must override") }
    }
    
    // Let's make sure that init() cannot be called to initialise this class.
    init() {
        guard type(of: self) != _AnyCupBase.self else {
            fatalError("Cannot initialise, must subclass")
        }
    }
    
    // All the methods in the Cup protocol here
    func replaceContent(with newContent: Content) -> Self {
        fatalError("Must override")
    }
    func showContent() -> Content? {
        fatalError("Must override")
    }
}

(2) A box containing a concrete instance of the protocol

Let’s define a box, where the concrete type for protocol Cup  is stored: such box will inherit from our previously define abstract class.  By inheriting from _AnyBase , it also follows the Cup  protocol.  The generic parameter ConcreteCup  also follows the protocol Cup .  All methods and properties of ConcreteCup  are wrapped by this class. Note that when referring to this class we need to specify a concrete type for Cup . For example: _AnyCupBox<TeaCup> .  That is not what we want.  When referring to its super class instead, we only need to specify a concrete type for its Content .  For example: _AnyCupBase<Tea> .  This should shed some light on why we need a base abstract class to inherit from and this trick lays at the heart of the technique.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private final class _AnyCupBox<ConcreteCup: Cup>: _AnyCupBase<ConcreteCup.Content> {
    // Store the concrete type
    var concrete: ConcreteCup
    
    // Override all properties
    override var description: String {
        get { return concrete.description }
    }
    
    // Define init()
    init(_ concrete: ConcreteCup) {
        self.concrete = concrete
    }
    
    // Override all methods
    override func replaceContent(with newContent: ConcreteCup.Content) -> Self {
        concrete.replaceContent(with: newContent)
        return self
    }
    override func showContent() -> ConcreteCup.Content? {
        return concrete.showContent()
    }
}

(3) The pseudo-protocol

And finally we can define the public class that we are going to use.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
final class AnyCup<Content>: Cup {
    // Store the box specialised by content.  
    // This line is the reason why we need an abstract class _AnyCupBase. We cannot store here an instance of _AnyCupBox directly because the concrete type for Cup is provided by the initialiser, at a later stage.
    private let box: _AnyCupBase<Content>
    
    // All properties for the protocol Cup call the equivalent Box proerty
    var description: String {
        get { return box.description }
    }
    
    // Initialise the class with a concrete type of Cup where the content is restricted to be the same as the genric paramenter
    init<Concrete: Cup>(_ concrete: Concrete) where Concrete.Content == Content {
        box = _AnyCupBox(concrete)
    }
    
    // All methods for the protocol Cup just call the e quivalent box method
    func replaceContent(with newContent: Content) -> Self {
        box.replaceContent(with: newContent)
        return self
    }
    func showContent() -> Content? {
        return box.showContent()
    }
}

See how this works!

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Use the generic type in a collection
 
let cupboardOne: [AnyCup<Coffee>] = [AnyCup(CoffeeMug()), AnyCup(CoffeeCup())]
let cupboardTwo: [AnyCup<Tea>] = [AnyCup(TeaMug()), AnyCup(TeaMug())]
 
// Return the generic type in a function (explicitely or implicitely)
 
func giveMeACupOfCoffee() -> AnyCup<Coffee>? {
    return cupboardOne.first
}
 
// Use the generic type as an input to a function
 
func describe(cup: AnyCup<Coffee>) {
    print(cup.description)
}
 
// Store a generic property
 
struct myShelf<Content> {
    var cup: AnyCup<Content>
}

Conclusion

While I am still not very comfortable with this pattern (a lot of boilerplate, difficult to fully grasp), I will give it a go as it seems very general.   You can also refer to two other excellent blog posts by Realm and by The Big Nerd Ranch basically outlining the same architecture and source of inspiration for this writing.

https://blog.jayway.com/2017/05/12/a-type-erasure-pattern-that-works-in-swift/

A “Type Erasure” Pattern that Works in Swift:类型域的转换的更多相关文章

  1. Breaking Down Type Erasure in Swift

    Type Erasure Pattern We can use the type erasure pattern to combine both generic type parameters and ...

  2. 关于type erasure

    哇,好久没有写blog了,再不写的话,blog的秘密都要忘记了,嘿嘿. 最近在试着参与一个开源项目,名字叫avim(A Vibrate IM),别想多了哟.地址是:https://github.com ...

  3. Java魔法堂:解读基于Type Erasure的泛型

    一.前言 还记得JDK1.4时遍历列表的辛酸吗?我可是记忆犹新啊,那时因项目需求我从C#转身到Java的怀抱,然后因JDK1.4少了泛型这样语法糖(还有自动装箱.拆箱),让我受尽苦头啊,不过也反映自己 ...

  4. java,swift,oc互相转换,html5 web开发跨平台

    java,swift,oc互相转换,html5 web开发跨平台 写一个java->swift的程序,这个程序是做跨平台系统的核心部分swift和oc到java也在考虑之列Swift->J ...

  5. Type Erasure with Pokemon---swift的类型擦除

    我感觉这个是swift的设计缺陷. 类型擦除:解决泛型类型作为公用类型的问题 是抽象的公用机制的一种实现方式. 1)类型擦除并不能解决类型不一致的兼容问题,只能解决类似继承一致性的兼容问题. 2)擦除 ...

  6. [ios][swift]Swift类型之间转换

    http://www.ruanman.net/swift/learn/4741.html

  7. Swift中如何转换不同类型的Mutable指针

    在Swift中我们拥有强大高级逻辑抽象能力的同时,低级底层操作被刻意的限制了.但是有些情况下我们仍然想做一些在C语言中的hack工作,下面本猫就带大家看一看如何做这样的事. hacking is ha ...

  8. [Swift]LeetCode756. 金字塔转换矩阵 | Pyramid Transition Matrix

    We are stacking blocks to form a pyramid. Each block has a color which is a one letter string, like ...

  9. [Swift]字符串大小写转换,同时实现本地化或设置语言环境

    在NSString中提供了3种字符串大小写转换方式:1. 转换字符串大小写2. 转换字符串大小写,并实现本地化3. 转换字符串大小写,并设置语言环境. 一. 转换字符串大小写如果只是想单纯的将字符串进 ...

随机推荐

  1. CRC16位校验

    之前有跟第三方通讯合作,应为CRC表码问题导致校验出结果不一致,纠结了很久,最后直接采用CRC计算方式校验才解决. 两种方式贴,自行对比. CRC校验计算方法 private ushort CRC_1 ...

  2. EWS 流通知订阅邮件

    摘要 查找一些关于流通知订阅邮件的资料,这里整理一下. 核心代码块 using System; using System.Collections.Generic; using System.Linq; ...

  3. 这些天C#面试有感

    为何面试 为何面试! 还用问?肯定是因为要离职啊 - -!离职原因就不说了,说来说去就是那么几个原因:这里主要讲我这些天面试遇到的问题,以及对面试的一些感受吧[断续更新

  4. 简明log4j配置教程

    先准备好log4j需要对应的开发包: apache-log4j-extras-1.2.17.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.1.jar 然后就是在项 ...

  5. 快速导出云服务器mysql的表数据

    1.许多互联网应用的数据库都布署在远程的Linux云服务器上,我们经常要编辑表数据,导出表数据. 通常的做法是ssh连接到服务器,然后命令登录,命令查询导出数据,费时费力,效率低下. 安装TreeSo ...

  6. Java - "JUC" CountDownLatch源码分析

    Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...

  7. InfoQ观察:Java EE的未来

    原创 2017-03-06 Charles Humble 聊聊架构 作者|Charles Humble编辑|薛命灯 作为InfoQ下一年编辑关注点审核工作的一部分,我们挑选了Java作为深入探讨的主题 ...

  8. AOP编程 - 淘宝京东网络处理

    现象描述 当我们打开京东 app 进入首页,如果当前是没有网络的状态,里面的按钮点击是没有反应的.只有当我们打开网络的情况下,点击按钮才能跳转页面,按照我们一般人写代码的逻辑应该是这个样子: /** ...

  9. 从零自学Java-3.在程序中存储和修改变量信息

    1.创建变量: 2.使用不同类型的变量: 3.在变量中存储值: 4.在数学表达式中使用变量: 5.把一个变量的值赋给另一个变量: 6.递增/递减变量的值. 程序Variable:使用不同类型的变量并赋 ...

  10. CAC的Debian-8-64bit安装BBR正确方式是?

    裝过三台debian 64 bit, CAC, 2歐, KVM虛擬機 做法都一樣 0. 有裝銳速記得先刪除, 免得換核心後, 銳速在扯後腿 1.換4.9版kernel 有正式版 別裝啥rc版, 4.9 ...