在 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。

谢谢你的阅读!🚀

相关推荐
辻戋2 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保2 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun3 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp3 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.4 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl6 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫7 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友7 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理9 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻9 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js