Swift 中的类型擦除(上)—— 为什么 & 怎么做
探索类型擦除的原理和实现,常见的类型橡皮擦有:AnyView、AnyShape、AnyCollection…
1 | protocol Tool { |
通过这个协议(或者叫接口)可以创建一个 Tool
1 | struct Pen: Tool { |
接下来,我要创建一个 ToolController
来管理我的“工具们”,
1 | class ToolController { |
我们先来获取所有工具,但是遇到一个问题,
这里虽然只有三个工具,可以一个个写,
但是这只是个例子,在实际应用中这里的数量是未知的,
应该如何获取所有的工具呢?大致思路是这样的。
1 | let tools: [Tool] = [Pen(), Pencil(), Ruler()] |
但是,Tool
中有 associatedtype
,因此 Tool
不能当作类型来使用,
好消息是,在 Swift 5.7 或更新版本中,可以使用 any Tool
来作为类型,
但是之后的所有结果都带有 any
,可能不会是你预期的结果。
比如
any View
不符合View
协议…
除了这样的数组创建和使用上可能会受阻,在多分支返回是也会出问题。
1 | class ToolController { |
some
关键字表示不透明的类型(opaque type),我们只需要知道他是一个符合
Tool
协议的东西,但是具体是什么不知道,也不关心是什么,交给编译器去推断。
因为在三个 case 中返回的结果类型不一致,编译时无法推断 Tool
的类型,
而三个分支中使用的 Tool 是完全不同的东西,不能直接替换。
如果还有更多分支的话情况会更加复杂,
因此,若想统一类型,不妨创建一个类型橡皮擦来擦除原本的类型,
这时候每一个分支返回的类型都是“橡皮擦”的类型,也就没问题了。
类型擦除的原理以及实现
- 创建橡皮擦(以下称之为 Eraser),Eraser 要符合你的
Protocol
橡皮擦的名称一般是
Any
+ 协议名
1 | struct AnyTool: Tool { |
- 初始化中传入一个要被擦除类型的 Tool (下面称之为Type)
1 | init<T: Tool>(erasing tool: T) { |
- 创建中间变量来传递 Type 中的必要的方法和属性
1 | internal let _name: S |
- 最后,用
name
来填补 Eraser 中需要的方法
1 | struct AnyTool: Tool { |
在这里,我们将类型从 some StringProtocol
变成了 String
如果是
View
,一般会把他替换成AnyView
这里如果不能将
tool.name()
转换成String
的话就返回IDK.
这样一来,我们就可以将 Pen
、Pencil
、Ruler
都变成 AnyTool
,解决了以上两个问题。
1 | class ToolController { |
Swift 中的类型擦除(上)—— 为什么 & 怎么做