理解Swift 5.7中的“some”和“any”关键字

本文介绍了Swift5.7中some和any关键字的更新,它们在函数参数中的使用变得更灵活,同时解锁了在具有关联类型的协议上动态调度的新能力,尽管存在类型有一些限制,但总体提升了代码的可读性和灵活性。
摘要由CSDN通过智能技术生成

理解Swift 5.7中的“some”和“any”关键字 在这里插入图片描述

someany关键字在Swift中并不新鲜。some关键字是在Swift 5.1中引入的,而any关键字是在Swift 5.6中引入的。在Swift 5.7中,苹果对这两个关键字都进行了又一次重大改进。我们现在可以在函数的参数位置使用这两个关键字!

func doSomething(with a: some MyProtocol) {
    // Do something
}

func doSomething(with a: any MyProtocol) {
    // Do something    
}

这一改进不仅使范型函数看起来更干净,而且还解锁了一些在Swift中编写范型代码的令人兴奋的新方法。剧透-我们现在可以告别以下错误消息:

protocol can only be used as a generic constraint because it has Self or associated type requirements

想知道更多吗?请继续阅读!

准备工作

在进入细节之前,我们先定义在本文中将使用的协议。

protocol Vehicle {
   var name: String { get }
   associatedtype FuelType
    func fillGasTank(with fuel: FuelType)
}

之后,我们将定义符合Vehicle协议的汽车Car和公共汽车Bus结构, 其中每种都需要不同种类的燃料。

struct Car: Vehicle {

    let name = ”car“

    func fillGasTank(with fuel: Gasoline) {
        print(Fill \(name) with \(fuel.name))
    }
}

struct Bus: Vehicle {

    let name = ”bus“

    func fillGasTank(with fuel: Diesel) {
        print(Fill \(name) with \(fuel.name))
    }
}

struct Gasoline {
    let name = ”gasoline“
}

struct Diesel {
    let name = ”diesel“
}

请注意,fillGasTank(with:)函数的CarBus 的参数数据类型不同,汽车需要汽油(Gasoline),而公共汽车需要柴油(Diesel)。这就是为什么我们需要在我们的车辆协议中定义一个名为FuelType的相关类型。

理解“some”关键词

some关键字是在Swift 5.1中引入的。它与协议一起使用,以创建一个不透明类型,该类型表示符合特定协议的东西。当用于函数的参数位置时,这意味着函数正在接受一些符合特定协议的具体类型。

在这个阶段,你可能想知道,我们不是已经能做到了吗?

事实上,你是对的。在函数的参数位置使用some关键字与在函数签名处使用角括号(< >)或尾随的where子句完全相同。

// 以下3个函数签名是相同的。
func wash<T: Vehicle>(_ vehicle: T) {
    // Wash the given vehicle
}

func wash<T>(_ vehicle: T) where T: Vehicle {
    // Wash the given vehicle
}

func wash(_ vehicle: some Vehicle)  {
    // Wash the given vehicle
}

当我们在变量上使用some关键字时,我们告诉编译器我们正在处理一个特定的具体类型,因此不透明类型的底层类型必须针对变量的范围进行固定。

var myCar: some Vehicle = Car()
myCar = Bus() // 🔴 Compile error: Cannot assign value of type ’Bus‘ to type ’some Vehicle‘

需要注意的一点是,编译器也禁止将相同具体类型的新实例分配给变量。

var myCar: some Vehicle = Car()
myCar = Car() // 🔴 Compile error: Cannot assign value of type ’Car‘ to type ’some Vehicle‘


var myCar1: some Vehicle = Car()
var myCar2: some Vehicle = Car()
myCar2 = myCar1 // 🔴 Compile error: Cannot assign value of type ’some Vehicle‘ (type of ’myCar1‘) to type ’some Vehicle‘ (type of ’myCar2‘)

考虑到这一点,在将其与数组一起使用时,我们必须遵循相同的规则。

// ✅ No compile error
let vehicles: [some Vehicle] = [
    Car(),
    Car(),
    Car(),
]
// 🔴 Compile error: Cannot convert value of type ’Bus‘ to expected element type ’Car‘
let vehicles: [some Vehicle] = [
    Car(),
    Car(),
    Bus(),
]

函数的底层返回类型也是如此。

// ✅ No compile error
func createSomeVehicle() -> some Vehicle {
    return Car()
}

// 🔴 Compile error: Function declares an opaque return type ‘some Vehicle’, but the return statements in its body do not have matching underlying types

func 
createSomeVehicle(isPublicTransport: Bool) -> some Vehicle {
    if isPublicTransport {
        return Bus()
    } else {
        return Car()
    }
}

仅此而已。现在让我们探讨any关键字,看看它们之间有什么区别。

理解 “Any” 关键词

any关键字是在Swift 5.6中引入的。引入它是为了创建一个存在的类型。在Swift 5.6中,在创建存在的类型时,any关键字不是强制性的,但在Swift 5.7中,如果您没有这样做,您将收到编译错误。

let myCar: Vehicle = Car() // 🔴 Compile error in Swift 5.7: Use of protocol ’Vehicle‘ as a type must be written ’any Vehicle‘ 
let myCar: any Vehicle = Car() // ✅ No compile error in Swift 5.7

// 🔴 Compile error in Swift 5.7: Use of protocol ’Vehicle‘ as a type must be written ’any Vehicle‘ 
func wash(_ vehicle: Vehicle)  {
    // Wash the given vehicle
}

// ✅ No compile error in Swift 5.7
func wash(_ vehicle: any Vehicle)  {
    // Wash the given vehicle
}

正如苹果工程师所解释的那样,存在类型就像一个包含符合特定协议的东西的盒子

在这里插入图片描述
如上图所示,不透明类型和存在类型之间的主要区别是“盒子”。 “盒子”使我们能够在其中存储任何具体类型,只要底层类型符合指定的协议,从而允许我们做不透明类型不允许我们做的事情。

// ✅ No compile error when changing the underlying data type
var myCar: any Vehicle = Car()
myCar = Bus()
myCar = Car()

// ✅ No compile error when returning different kind of concrete type 
func createAnyVehicle(isPublicTransport: Bool) -> any Vehicle {
    if isPublicTransport {
        return Bus()
    } else {
        return Car()
    }
}

最棒的是,在Swift 5.7中,我们现在可以将any关键字用于具有相关类型的协议!这意味着使用具有相关类型的协议创建异构数组不再是一个限制!

// 🔴 Compile error in Swift 5.6: protocol ’Vehicle‘ can only be used as a generic constraint because it has Self or associated type requirements
// ✅ No compile error in Swift 5.7
let vehicles: [any Vehicle] = [
    Car(),
    Car(),
    Bus(),
]

那有多酷?😃

这种改进不仅消除了“protocol can only be used as a generic constraint because it has Self or associated type requirements” 错误,而且还使在具有关联类型的协议上完成动态调度变得更加简单!但那将是另一天的一篇文章。

提示:

可在此处了解在具有关联类型的协议上实现动态调度是多么容易。

“any” 关键字限制

尽管看起来不错,但使用any关键字创建的存在类型仍然有自己的局限性。一个主要限制是,我们不能使用==运算符来比较2个存在类型的实例。

// Conform `Vehicle` protocol to `Equatable`
protocol Vehicle: Equatable {

    var name: String { get }

    associatedtype FuelType
    func fillGasTank(with fuel: FuelType)
}


let myCar1 = createAnyVehicle(isPublicTransport: false)
let myCar2 = createAnyVehicle(isPublicTransport: false)
let isSameVehicle = myCar1 == myCar2 // 🔴 Compile error: Binary operator ’==‘ cannot be applied to two ’any Vehicle‘ operands


let myCar1 = createSomeVehicle()
let myCar2 = createSomeVehicle()
let isSameVehicle = myCar1 == myCar2 // ✅ No compile error

如果你仔细想想,这实际上是有道理的。如前所述,存在类型可以将任何具体类型存储在其“盒子”中。对编译器来说,存在类型只是一个“盒子”,它不知道盒子里有什么。因此,当编译器无法保证“盒子”的内容具有相同的底层具体类型时,编译器无法进行比较。

您应该注意的另一个限制是,存在类型不如不透明类型(使用一些关键字创建)有效。Donny Wals有一篇很棒的文章,详细讨论了这个问题,我强烈建议你去看看。

因此,尽管苹果对any关键字做了很多改进,但如果不透明类型可以完成工作,仍然建议使用some关键字。

结论

Swift 5.7中对someany关键字的改进绝对是值得欢迎的。一方面,它极大地改善了范型代码的语法和可读性。另一方面,它为我们以更有效的方式编写范型代码开辟了新的方法。

我希望这篇文章能让您很好地了解anysome关键字。

感谢您的阅读。👨🏻‍💻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值