在 Swift 中使用元组作为轻量级类型

原文:Using tuples as lightweight types in Swift | Swift by Sundell

Swift 的一个真正有趣的功能是能够使用元组(Tuples)创建轻量级容器。这个概念很简单 -- 元组让你轻松地将任何数量的对象或值组合在一起,而无需创建一个新类型。但是,尽管这是一个简单的概念,它却开启了一些非常酷的机会,无论是在 API 设计方面还是在结构化代码方面。

在标准库中,元组被用于形成 (key, value) 类型的键值对,当迭代一个字典时,或者当返回插入一个 Set 的结果时(就像我们上周在Swift 中集合的力量中使用的那样)。本周,让我们来看看我们如何在自己的代码中使用元组,以及它们使我们能够使用的一些技术。

轻量级类型

描述元组的一种方法是,它们是轻量的、内联的类型。如果你有两个值 -- 比方说一个 title 和一个subtitle -- 你可以快速地将它们组合成一个新的类型,在你要使用它的地方完全内联,就像这样:

swift 复制代码
class TextView: UIView {
    func render(_ texts: (title: String, subtitle: String)) {
        titleLabel.text = texts.title
        subtitleLabel.text = texts.subtitle
    }
}

上述方法的一个替代方案是创建一个 struct。虽然在你会在多个地方使用这种类型对的情况下,这可能是更好的选择,但能够在你实际使用它的地方保持内联的类型定义,确实有助于保持事情的简单。它也使得添加额外的属性像改变函数签名一样简单:

swift 复制代码
class TextView: UIView {
    func render(_ texts: (title: String, subtitle: String, description: String)) {
        titleLabel.text = texts.title
        subtitleLabel.text = texts.subtitle
        descriptionLabel.text = texts.description
    }
}

使用类似上述的方法可能看起来微不足道,但我个人发现,如果我可以在同一个地方输入东西,而不是在代码库中的多个地方跳来跳去来做这样一个简单的改变,这确实对生产力有很大影响。

不过,上述方法的一个缺点是,如果属性列表开始增加,事情就会变得有点混乱。解决这个问题的一个方法是使用一个 typealias 来为元组创建一个轻量级的类型定义,同时仍然让事情保持简单:

swift 复制代码
class TextView: UIView {
    // 使用 typealias 为元组创建轻量级类型定义
    typealias Texts = (title: String, subtitle: String, description: String)

    func render(_ texts: Texts) {
        titleLabel.text = texts.title
        subtitleLabel.text = texts.subtitle
        descriptionLabel.text = texts.description
    }
}

做到这一点后,如果我们将来想把元组提取成一个明确的类型 -- 比如一个 struct -- 也会变得超级容易,因为我们的代码现在是用 Texts 来引用它。

简化的调用栈点

关于元组的另一个很酷的事情是它们对某个 API 的调用栈点的影响。尽管元组可以有标签,但在创建一个实例时,你总是可以自由地忽略这些标签。这可以帮助使调用栈点看起来非常漂亮和干净,例如在处理矢量类型,如坐标时。

比方说,我们的应用程序有一个在基于块状的地图上存储位置的模型,我们选择使用一个元组来定义地图上的坐标,像这样:

swift 复制代码
struct Map {
    private let width: Int
    private var tiles: [Tile]

    subscript(_ coordinate: (x: Int, y: Int)) -> Tile {
        return tiles[coordinate.x + coordinate.y * width]
    }
}

由于我们使用了一个元组,我们现在可以选择在创建坐标时包含或省略 xy 标签:

swift 复制代码
let tileA = map[(x: 1, y: 0)]
let tileB = map[(2, 1)]

等同性

在检查多个值是否相等时,元组也是非常有用的。尽管它们不符合 Equatable 协议(或任何协议),但 Swift 标准库定义了 == 重载,用于包含本身是相等的值的元组。

假设我们正在构建一个视图控制器,让用户在给定范围内搜索其他用户(例如,他们可以选择只在他们的朋友中搜索)。由于我们不想浪费资源去搜索已经被显示出来的结果,我们可以很容易地使用一个元组来跟踪当前的搜索标准,并验证它们自上次搜索后是否真的发生了变化,就像这样:

swift 复制代码
class UserSearchViewController: UIViewController {
    enum Scope {
        case friends
        case favorites
        case all
    }

    // 这里将用户的搜索关键字、搜索区域声明成一个元组类型
    private var currentCriteria: (query: String, scope: Scope)?

    func searchForUsers(matching query: String, in scope: Scope) {
        if let criteria = currentCriteria {
            // If the search critera matches what is already rendered, we can
            // return early without having to perform an actual search
            // 如果搜索标准与已经呈现的内容相符,我们可以提前返回,而不需要进行实际的搜索
            guard (query, scope) != criteria else {
                return
            }
        }

        currentCriteria = (query, scope)

        // Perform search
        ...
    }
}

不需要定义额外的类型,也不需要编写任何 Equatable 实现(尽管希望这很快就会成为过去,因为 Swift 很快就会开始为我们合成大部分的实现🎉)。

与一等类函数结合

将元组与 Swift 支持一等类函数的事实相结合,可以让我们做一些非常有趣的事情。事实证明,任何闭包的参数列表实际上都可以用一个元组来描述,而且由于一等类函数的存在,所有的函数也都是闭包,我们实际上可以用一个元组来向一个函数传递参数。我们所要做的就是让 Swift 把一个函数当作一个闭包。为了做到这一点,我们可以定义一个调用函数,该函数接收任何函数,并将其所需的参数应用于它,像这样:

swift 复制代码
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
    return function(input)
}

现在我们假设要实例化一组视图类,它们都可以使用相同类型的参数列表进行初始化:

swift 复制代码
class HeaderView: UIView {
    init(font: UIFont, textColor: UIColor) {
        ...
    }
}

class PromotionView: UIView {
    init(font: UIFont, textColor: UIColor) {
        ...
    }
}

class ProfileView: UIView {
    init(font: UIFont, textColor: UIColor) {
        ...
    }
}

使用我们的 call 函数,我们现在可以简单地应用一个包含预期参数的元组(在这种情况下,我们想使用创建视图的样式),将每个视图的初始化器作为一个函数传递,并使用我们的 style 元组调用它。

swift 复制代码
let styles = (UIFont.systemFont(ofSize: 20), UIColor.red)

let headerView = call(HeaderView.init, with: styles)
let promotionView = call(PromotionView.init, with: styles)
let profileView = call(ProfileView.init, with: styles)

上面的内容有点疯狂,但它非常酷! 😀

总结

Swift 中的元组很简单,但非常强大。我们不仅可以用它们来快速定义内联类型,还可以用它们来使检查多个值是否相等这样的任务变得简单得多。最后,结合一等类函数,我们可以使用元组来将参数列表作为一个值来传递,这真的很有趣。

就像其他 Swift 功能一样,元组与更明确定义的类型(如类和结构)相比有不同的权衡。它们更容易定义,而且非常轻量级,但因此它们不能做一些事情,比如存储弱引用或在其中包含任何类型的逻辑(比如拥有方法)。一方面,这是一个缺点,但它也有助于在处理更简单的数据容器时保持简单性。

你是怎么想的?你目前是否在你的代码中使用元组,或者是你要尝试的东西?让我知道,以及你可能有的任何其他问题、评论或反馈,请在 Twitter 上 @johnsundell。

谢谢你的阅读!🚀

相关推荐
gqkmiss5 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃11 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰15 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye22 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm24 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You1 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生1 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互