Swift中的Type Erasure

在上一篇文章中,我写了关于将代码拆分为较小框架的文章。 但是有时候这说起来容易做起来难。 我当前的挑战是调制可重用的UI处理。 这就是为什么我创建FancyUI。 除了某些配置扩展之外,我还为主题支持实现了组件管理器。

FancyUI中的样式表和组件管理器

可样式设置是继承可样式设置协议的视图。 它们强制您实施style(colorScheme:)方法,以便您可以配置视图的颜色。 您可以在组件管理器中注册它们。 组件管理器充当观察者,而可样式元素本身就是主题更改的侦听器。 您可以通过组件管理器轻松传递新的颜色方案,以轻松管理应用程序中的主题支持。

问题

FancyUI的实现应该能够创建自定义ColorScheme类型。 因此,Styleable必须对ColorScheme使用通用类型。 可样式化是一种协议,因此ColorScheme必须是关联的类型。 组件管理器(它是一个结构)保存ColorScheme并将其传递给已注册的styleables。 这就是问题所在:因为组件管理器是struct,所以它具有通用类型GenericSchemeType而Styleable具有关联的类型SchemeType 。 我必须将通用SchemeType转换为关联类型SchemeType ,这是不可能迅速实现的。

解决方案:类型擦除

问题是在编译时无法推断类型。 这就是为什么我试图创建一个结构AnyStyleable。 此类型将样式函数存储为闭包,并在调用style()函数时执行它。 组件管理器现在注册AnyStyleable而不是Styleable。

注意:类型信息并没有真正删除。 它已经搬迁了。 闭包在运行时推断其类型,因此我们在这里没有问题。

该问题已解决。 但是我对解决方案有一个体系结构上的问题:样式类型必须另存为闭包,而视图本身不是Styleable。 正如我已经提到的,我喜欢提取东西。 我想象在一个大型的成长型企业应用程序中使用样式方法。 在viewDidLoad或init中设置AnyStyleable会使事情变得blo肿,因此我对Styleable协议的初衷是在单独的扩展中实现它(并让视图控制器本身作为Styleable本身)。 因为样式位于另一种类型,所以这不可能了。 但是我也有解决这个问题的方法。

替代方法:实型擦除

在我目前的方法中,原始的Styleable协议没有太大变化。 几乎是一样的,除了协议是由另一个协议继承的:AnyStyleable.AnyStyleable是一个将样式函数作为闭包的结构,现在它是一个将style(scheme:)方法定义为Any类型的协议。 这意味着:AnyStyleable是具有具体类型的协议(即使它是Any)。 这使我们可以将AnyStyleables注册到组件管理器。 魔术来了:正如我提到的,Styleable协议继承了AnyStyleable协议。 因此,我可以将Styleables注册到组件管理器,因为它正在等待AnyStyleable的类型。

您现在可能会问自己:可样式设置具有 style(colorScheme:) 方法。 当它们实际注册为AnyStyleable时如何调用?

AnyStyleable协议定义_style(colorScheme:)方法,该方法作为默认实现实现。 _style(colorScheme:)实现在运行时检查传递的类型是否与关联的类型匹配,如果成功,则调用style(colorScheme)函数。 因此,实际上,组件管理器调用_style(colorScheme:)方法,该方法在运行时委托给style(colorScheme:)方法。

这种方法的好处是实现完全不必处理擦除。 它可以只调用所使用的register(self) ,并且可以将实现的样式函数提取为扩展。

结论

我通常会尝试避免类型检查和类型转换。 但是这种方法对我来说似乎是合法的(也是因为它已用于标准库中的某些类型)。 但是,如果您有更清洁的解决方案或改进,请随时联系。 我总是愿意提高自己的素质。

您可以在此处查看我的代码:FancyUI