Swift 隐藏宝藏:“逆天改命”调整方法重载(function overloading)优先级

在这里插入图片描述

概览

在 Swift 语言中有很多隐藏“宝藏”悄悄深埋在不为人知的角落,静静等待着有缘秃头码农们的大力挖掘。

在这里插入图片描述

而在这里,我们将介绍 Swift 语言中一个非常有用的秘技:方法重载优先级判断以及如何改变它。

在本篇博文中,您将学到如下内容:

相信学完本课后,小伙伴们一定会对 Swift 语言的博大精深所折服,并将本课学到的这一隐秘技能发扬光大!

那还等什么呢?让我们马上开始宝藏的深入挖掘之旅吧!

Let‘s Go!!!😉


function_overloading_18">1. 什么是方法重载function overloading)?

方法重载function overloading)是很多开发语言都支持的一种特性,它被用来多样化同名方法或函数的调用。使用方法重载可以大大增加同名方法或函数调用的灵活性。

比如,在以下代码中三个 test 函数互为重载:

swift">func function-definition function">test() {
    function">print(#function)
}

func function-definition function">test(value: Int) {
    function">print(#function)
}

func function-definition function">test(value: Int, needChanged: Bool) {
    function">print(#function)
}

方法重载的一般要求为方法名相同,但方法签名会有所不同。值得说明的是,只考虑签名中的返回类型在某些情况下会产生模棱两可的语义。

swift">func function-definition function">test() {
    function">print(#function)
}

func function-definition function">test() -> String {
    function">print(#function)
    return "test"
}

/* 直接调用 test() 会产生歧义!
error: Test.playground:13:1: error: ambiguous use of 'test()'
*/
function">test()

如上代码所示,如果只有方法返回类型不同则会让编译器“不知所措”,我们必须显式告诉编译器我们的“精确”意图:

swift">// 会调用返回 String 版本的 test() 方法
let text: String = function">test()

注意,不要将方法重载和方法重写(overriding)相混淆:后者被用来支持对象中相同方法的多态性;前者在编译时被考虑,后者则在运行时被考虑。

2. Swift 中方法重载优先级判断原则

上面我们了解了 Swift 方法重载的不同形式。那么,对于名称相同的方法来说,如果在调用时满足其中多个重载语义,那么到底实际会使用哪个重载方法呢?

swift">func function-definition function">test() {
    function">print(#function)
}

func function-definition function">test(value: Int = 11) {
    function">print(#function)
}

// 实际会调用上面哪个重载方法呢?
function">test()

如上例所示,我们创建了名为 test 的两个重载方法,如果我们进行 test() 调用,到底会执行上面哪个方法呢?

前 Swift 核心组成员 Jordan Rose 曾在不经意间透露过解决这种情况的“秘诀”:

The general principle is that the most specific overload should win.

那就是:具体胜过一般原则!

这意味着,当方法的多个重载同时满足调用语义时,越具体的重载越会被优先考虑;约束条件越少的重载越会被优先考虑。

swift">func function-definition function">test() {}
func function-definition function">test(value: Int = 11) {}

还拿上面的例子来说,对于 test() 语义来说,第一个方法会被调用,因为它不需要假设用户选择了默认参数,约束条件更少。

类似的,对于下面的例子,由于不需要假设用户在调用时选择单个参数 test 方法的不定参数重载版本,所以实际 test() 采用的是 test(_ value: Int) 这一约束更少、更简单的重载调用:

swift">func function-definition function">test(_ value: Int) {
    function">print(#function)
}

func function-definition function">test(_ values: Int...) {
    function">print("不定参数(Variadic):\(#function)")
}

// 会调用第一个方法重载
function">test(11)

同理,如果非要在普通重载和泛型重载中选择一个的话,编译器将会毫不犹豫的选择前者:

swift">func function-definition function">test(_ value: Int) {
    function">print(#function)
}

func function-definition function">test<N: Numeric>(_ value: N) {
    function">print("泛型:\(#function)")
}

// 将会调用非泛型版本的重载
function">test(11)

俗话说得好:“我命由我不由天”!小伙伴们的疑问是:我们能不能改变编译器对于多个重载版本的选择偏好呢?

答案是肯定的!

3. 改变游戏规则!

在某些情况下,我们希望让本该“不受人待见”的重载方法重新“翻身做主人”。比如,对于之前泛型重载的那个例子,如果我们希望泛型版本的重载优先被调用,该如何是好呢?

swift">func function-definition function">test(_ value: Int) {
    function">print(#function)
}

func function-definition function">test<N: Numeric>(_ value: N) {
    function">print("泛型:\(#function)")
}

// 如果希望调用泛型版本的重载,该如何是好?
function">test(11)

在 Swift 语言中,幕后秃头大佬们提供了一种机制,让我们可以“降低”某一种方法重载的“偏爱程度”。这可以通过在其方法定义前增加 @_disfavoredOverload 修饰符来搞定:

swift">@_disfavoredOverload
func function-definition function">test(_ value: Int) {
    function">print(#function)
}

func function-definition function">test<N: Numeric>(_ value: N) {
    function">print("泛型:\(#function)")
}

// 现在会调用泛型版本的重载方法
function">test(11)

如上代码所示,我们向编译器挑明了小伙伴们更“讨厌”非泛型版本重载的强烈情感,所以编译器会非常知趣的选择泛型版本的重载调用。

最后再举一个“栗子”。对于以下 3 种 test() 方法的重载来说,默认会选择第一种重载;当第一种重载被 @_disfavoredOverload 修饰后,才会选择第二种重载;只有当前两种重载都被“嫌弃”之后,才轮到最后的泛型重载闪亮登场:

swift">@_disfavoredOverload
func function-definition function">test(_ value: Int) {
    function">print(#function)
}

@_disfavoredOverload
func function-definition function">test(_ values: Int...) {
    function">print("不定参数(Variadic):\(#function)")
}

func function-definition function">test<N: Numeric>(_ value: N) {
    function">print("泛型:\(#function)")
}

// 只有前两种重载都被“嫌弃”之后,才轮到最后一种重载!
function">test(11)

现在,小伙伴们对于 Swift 语言中多个重载方法到底该选谁?以及如何进一步“暗箱操作,逆天改命”修改默认重载规则的方法都了然于胸了吧!棒棒哒!💯


@_disfavoredOverload 修饰符前面有下划线,这意味着它是 Swift 语言的一个内部特性,在将来很可能会被丢弃,所以如果大家依赖于它则需要特别留意。

所幸的是,在最新 Swift 5.9 中 @_disfavoredOverload 仍然可用。


总结

在本篇博文中,我们先是介绍了 Swift 语言中当方法的多个重载同时满足调用语义时编译器的选择之道,并且随后进一步讨论了如何“逆天改命”修改这一默认的选择法则。

感谢观赏,再会!😎


http://www.niftyadmin.cn/n/5365260.html

相关文章

PostgreSQL解决序列(自增id)自动增长冲突

背景 一般表的id主键我们都是设置为自增序列。 但是如果我们在插入一些数据的时候手动指定id&#xff0c;那么自增序列不会跟随我们手动设置的id增长。 就会出现下次不设置id的时候自增到我们手动指定的id导致主键冲突bug 举个例子 现在数据有 id123 现在我们手动插入数…

2024美赛数学建模D题思路源码

赛题目的 赛题目的&#xff1a; 问题描述&#xff1a; 解题的关键&#xff1a; 问题一. 问题分析 问题解答 问题二. 问题分析 问题解答 问题三. 问题分析 问题解答 问题四. 问题分析 问题解答 问题五. 问题分析 问题解答

分布式延时消息的另外一种选择 Redisson (推荐使用)

前言 目录 前言 基本使用 内部数据结构介绍 基本流程 发送延时消息 获取延时消息 初始化延时队列 总结 因为工作中需要用到分布式的延时队列&#xff0c;调研了一段时间&#xff0c;选择使用 Redisson DelayedQueue&#xff0c;为了搞清楚内部运行流程&#xff0c;特记…

Rust 本地文档的使用:rustup doc

Rust 是一种系统级编程语言&#xff0c;以其安全性、速度和内存控制能力而闻名。为了方便开发者更好地了解并利用 Rust 标准库和工具链中的功能&#xff0c;Rust 提供了一种内置的文档浏览方式——通过 rustup doc 命令。 安装 rustup 在查阅 Rust 文档之前&#xff0c;确保你…

.gitlab-ci.yml文件参数

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

NLP_Bag-Of-Words(词袋模型)

文章目录 词袋模型用词袋模型计算文本相似度1.构建实验语料库2.给句子分词3.创建词汇表4.生成词袋表示5.计算余弦相似度6.可视化余弦相似度 词袋模型小结 词袋模型 词袋模型是一种简单的文本表示方法&#xff0c;也是自然语言处理的一个经典模型。它将文本中的词看作一个个独立…

2024.1.26力扣每日一题——边权重均等查询

2024.1.26 题目来源我的题解方法一 使用dfs对每一组查询都求最近公共祖先&#xff08;会超时&#xff0c;通不过&#xff09;方法二 不需要构建图&#xff0c;直接在原始数组上进行求最大公共祖先的操作。 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2846 我的题解 …

Spring IOC 之深入分析 Aware 接口

&#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是小徐&#x1f947;☁️博客首页&#xff1a;CSDN主页小徐的博客&#x1f304;每日一句&#xff1a;好学而不勤非真好学者 &#x1f4dc; 欢迎大家关注&#xff01; ❤️ &#xfeff;AbstractAutowireCapableBeanFacto…