利用扩展来约束泛型

利用扩展来约束泛型

原文:Specialized extensions using generic type constraints

将Swift强大的泛型系统与任何Swift类型都可以与新的API和功能扩展这一事实相结合,使我们能够编写有针对性的扩展,在类型或协议符合特定要求时有条件地向其添加新功能。

这一切都从where关键字开始,它允许我们在一系列不同情况下应用泛型类型约束。在本文中,让我们看看该关键字如何应用于扩展,以及可以通过这样做解锁哪种模式。

基于泛型类型限制扩展

我们可以使用更具体的API扩展泛型类型或协议的方法之一是对扩展本身应用基于where的类型约束。例如,在这里,我们正在扩展标准库的Sequence协议(ArraySet等集合符合该协议),这是一个方便的API,该API允许我们通过直接在序列上调用render()渲染一系列符合Renderable的类型——只要该序列包含此类Renderable元素:

protocol Renderable {
    func render(using renderer: inout Renderer)
}

extension Sequence where Element == Renderable {
    func render() -> UIImage {
        var renderer = Renderer()

        for member in self {
            member.render(using: &renderer)
        }

        return renderer.makeImage()
    }
}

要了解有关上述inout关键字的更多信息,以及它如何使我们能够在某些上下文中传递对值的引用,请查看“在Swift中使用值语义”。

上述模式的好处是,它使我们添加的render方法的内部实现完全类型安全(因为我们有编译时保证,我们将始终使用Renderable元素),而且我们现在无论何时想呈现符合Renderable的值数组,都可以访问整洁的便利API:

class CanvasViewController: UIViewController {
    var renderables = [Renderable]()
    private lazy var imageView = UIImageView()
    ...

    func render() {
        imageView.image = renderables.render()
        ...
    }
}

另一种类型约束扩展可能非常有用的情况是,我们希望有条件地使泛型类型符合协议。例如,以下方法只能在Swift的标准Array类型包含Renderable元素时使其符合上述Renderable协议:

extension Array: Renderable where Element: Renderable {
    func render(using renderer: inout Renderer) {
        for member in self {
            member.render(using: &renderer)
        }
    }
}

有了上述功能,我们现在可以使用Renderable值的嵌套数组(这可能在分组方面有很大的好处),同时仍然可以像以前一样渲染我们的顶级renderables数组:

extension CanvasViewController {
    func userDidDrawShapes(_ shapes: [Shape]) {
        renderables.append(shapes)
        render()
    }
}

上述模式在Swift的标准库中被广泛使用(例如,当Array的元素也符合EquatableCodable等协议时,使其符合这些协议),在我们自己的代码中也可以非常有用——特别是在构建自定义库时。

自我约束

类型约束扩展还可以让我们添加默认协议实现,这些实现只能由满足特定要求的类型使用。例如,当AUIViewController子类遵守Dismissable协议的dismiss方法时,我们在这里提供了该协议的默认实现:

protocol Dismissable {
    func dismiss()
}

extension Dismissable where Self: UIViewController {
    func dismiss() {
        dismiss(animated: true)
    }
}

上述模式的好处是,它允许我们选择视图控制器为Dismissable,而不是将该方法添加到整个应用程序中的所有UIViewController实例中。与此同时,由于我们的扩展,我们不需要在每个视图控制器中实际重新实现dismiss方法,而是可以遵守我们的新协议并利用其默认实现:

class ProductViewController: UIViewController, Dismissable {
    ...
}

我们仍然可以选择在必要时提供自定义dismiss实现,对于非UIViewController子类的类型,前提是需要此类专用实现(因为这些类型无法访问我们受限扩展的默认实现):

class AlertPresenter: Dismissable {
    func dismiss() {
        ...
    }
}

对单个功能施加约束

最后,让我们看看我们如何不仅将泛型类型约束应用于整个扩展,还可以应用于此类扩展中的单个函数。例如,如果我们愿意,我们可以像这样编写之前的Sequence扩展:

extension Sequence /*where Element == Renderable*/ {
    func render() -> UIImage where Element == Renderable {
        ...
    }
}

在上述情况下,我们是将通用类型约束应用于扩展,还是直接应用于我们的功能并不重要——这两种方法都给我们的效果完全相同。然而,情况并不总是如此。为了进一步探索,假设我们正在开发一个包含以下Group协议的应用程序,该协议使用通用associatedtype使每个组能够定义它包含的Member值类型:

protocol Group {
    associatedtype Member
    var members: [Member] { get }
    init(members: [Member])
}

然后,假设我们希望创建一个简单的API,通过合并两个组members数组来合并它们——这可以在不使用任何形式的通用约束的情况下完成,例如:

extension Group {
    func combined(with other: Self) -> Self {
        Self(members: members + other.members)
    }
}

然而,上述扩展确实要求合并的两个组类型完全相同。也就是说,他们仅仅包含相同类型的Member值是不够的——组本身需要匹配,这可能不是我们想要的。

因此,为了解决这个问题,让我们修改上述扩展,以使用直接附加到我们combined方法的泛型类型约束,该约束使两个组具有不同的类型,同时仍然要求其Member类型相同:

extension Group {
    func combined<T: Group>(
        with other: T
    ) -> Self where T.Member == Member {
        Self(members: members + other.members)
    }
}

有了上述内容,我们现在可以随心所欲地轻松地组合组,只要这些组存储相同的价值观。例如,在这里,我们将ArticleGroup实例与FavoritesGroup相结合,这是可能的,因为它们都存储Article值:

let articles: ArticleGroup = ...
let favorites: FavoritesGroup = ...
let combined = articles.combined(with: favorites)

整洁!虽然上述具体示例中可以看出,在Swift中进行更高级的通用编程时,它非常有用。

结论

Swift的泛型和扩展非常有用,如果把它们组合在一起,可以使我们能搭配一些更有趣和更强大的模式。当然,并非所有代码库都需要利用这些功能–但是在编码的时候不要过度使用–,在有正当理由的情况下,类型约束的扩展可能是我们Swift开发人员技能中的绝佳工具。

希望您认为这篇文章有用,如果您有任何问题、评论或反馈,请随时通过电子邮件联系

感谢您的阅读!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值