Swift中 any some的作用

前言

在学习Swift ui看到一个函数返回了some viewview我可以理解那some是什么?

//ContentView.swift
struct ContentView_Previews: PreviewProvider{
   static var previews: some View{
       ContentView()
   }
}

如果你仔细看一些官方文档甚至还有any关键字,也可以用于替换some。那么两则区别是什么?本文为博主笔记不具备说教性。

术语

为了弄清问题需要梳理相关术语。

  • 协议(Protocol)
    Swift中用于定义实例应具有哪些属性和方法。但是这些方法返回的数据类型应是明确的,也就是说不支持协议内嵌泛型。但提供了关联类型(associatetype)以支持类型泛化问题。
protocol Animal{
    var name:String{get}
    var kind:String{get}
    func run()
}
  • 泛型 (generic)

大多数现代语言提供一种提供泛化数据类型进而完成一个通用性强代码

假设你需要提供一个函数打印其传入参数的类型和一些修饰的字符串给用户,你利用泛型编写类似如下代码

//打印传入的参数类型,因为利用泛型所以可以适配所有类型
func printObj<T>(_ param :T) ->String{
    return "type of \(type(of: param))"
}
//传入int
print(printObj(1))
//传入double
print(printObj(1.9))
  • 泛型约束(generic constraint)

约束泛型取值范围。用于缩小模板函数的适用范围

//限制泛型入参为NSObject子类型
func printObj<T>(_ param :T)->String where T : NSObject  {
    return "type of \(type(of: param))"
}
class MyClass :NSObject{}
//compile success 传入int
print(printObj(MyClass()))
//compile error:Global function 'printObj' requires that 'Double' inherit from 'NSObject'
//print(printObj(1.9))
  • 类型擦除(type erasure)
    概念和Java类似可参阅博主其他文献:
    java 泛型擦除

  • 关联类型(associatetype)
    为了解决协议支持泛型问题Swift提供 关联类型(associatetype) 以完善其语法体系。

(例子来自参考文献)

//定义一个汽车类,由于每个汽车加入的燃料是不同的,
//所以在协议中无法明确其类型
protocol Vehicle {
    var name: String { get }
    associatedtype FuelType
    func fillGasTank(with fuel: FuelType)
}
struct Gasoline {
    let name = "gasoline"
}
struct Car: Vehicle {
    let name = "car"

    func fillGasTank(with fuel: Gasoline) {
        print("Fill \(name) with \(fuel.name)")
    }
}
struct Diesel {
    let name = "diesel"
}
struct Bus: Vehicle {
    let name = "bus"
    func fillGasTank(with fuel: Diesel) {
        print("Fill \(name) with \(fuel.name)")
    }
}
  • 自要求(self requirement)

在Swift中,Self要求是指协议中的方法、属性或者是返回类型,要求返回遵循该协议的类型本身

protocol MyPro{
    func isEqual(to other: Self) -> Bool
}
  • 静态分派(static dispatch)

我们调用一个函数需要明确其函数地址,那么这个地址的索引是在编译时确定的我们就成为静态分派。(不严谨描述)

网上有较多 JAVA,C++案例 。后文会用汇编说明Swift下的情况。

  • 动态分派(dynamic dispatch)

我们调用一个函数需要明确其函数地址,那么这个地址的索引是在运行时确定的我们就成为动态分派。(不严谨描述)

可参阅C++中利用虚表实现多态。

  • 不透明类型(opaque type)

以some修饰的协议(protocl)。 主要在协议(protocl)存在自要求(self requirement)和关联类型(associatetype)中讨论。

注意不透明类型特性:

  1. 不透明类型(opaque type)经常作为某个函数的返回值。对于调用者来说返回的对象具体的实例化类是不清楚,只知道其实现类对应的协议具体类。

  2. 对于编译器来说 声明了不透明类型(opaque type) 将在编译层面固化其具体的实现子类而不能有二义性。因为不存在二义性编译器对于函数的调用将采用静态分派(static dispatch)

我们看看一个小例子:

protocol Animal{
    func isEqual(to other: Self) -> Bool
}
class Dog:Animal{
    func isEqual(to other: Dog) -> Bool {
        return true;
    }
}
class Cat:Animal{
    func isEqual(to other: Cat) -> Bool {
        return true;
    }
}
//因为返回的协议含有自要求或关联类型所以必须要声明为some MyPro或者any MyPro方允许编译
//some MyPro称为不透明类型。因为对于调用者来说只知道返回了MyPro而不知道具体的实例子类
func myFunc(flag:Bool)->some Animal{
    
//    注意if 和else代码是不能通过编译的。因为这里可能返回Dog和Cat让编译器无法判别具体类型进而无法优化代码
//compil error:Function declares an opaque return type 'some Animal', but the return statements in its body do not have matching underlying types
//    if flag {
//        return Dog()
//    }else{
//        return Cat()
//    }
    //compil success.这里函数体只返回Dog类型不存在其他实现Animal的类返回
    return Dog();
}

//对于ret来说只知道是Animal类型但是不清楚到底实现的类是Dog还是Cat
var ret = myFunc(myflag)

我们看一个比较晦涩难懂的any 和some 影响的实际代码。

protocol Flyable {
    func fly()
    associatedtype types
}
struct Bird: Flyable {
    typealias types = Int
    
    var name:String
    init(_ name:String){
        self.name=name
    }
    func fly() {
        print("Bird is flying\(name)")
    }
}
struct Airplane: Flyable {
    typealias types = Int
    func fly() {
        print("Airplane is flying")
    }
}

func generateSomeFlyable()->some Flyable{
    //由于some Flyable未不透明类型那么告诉编译器这里返回的一定是确定的实例对象Bird。
    return Bird("bird2")
}

func takeOff() {
    //flyable2由于不透明类型那么编译器知道他一定是Bird实现的,所以调用flyable2.fly时可以肯定知道方法是Bird的
    //因为在编译期间就可以flyable2.fly()调用函数位置
    let flyable2:some Flyable = generateSomeFlyable()
    //any+protocl称为存在类型Existential type。后文讲解
    //存在类型告诉编译器这里返回的对象不能随意推测 只知道是Flyable实现类,他可能是Bird也可能是Airplane
    //所以flyable3.fly调用时只有在运行时确定fly函数是Bird还是Airplane
    let flyable3: any Flyable = Bird("bird3")
    //line 12 静态分派 在编译期间就知道具体函数地址.
    flyable2.fly()
    //动态分派 需要经过运行时查找函数地址
    flyable3.fly()
}

takeOff()

我们查看 flyable2.fly()对应函数调用汇编指令
在这里插入图片描述
最后我们看跳转到实际的Bird.fly函数
在这里插入图片描述

flyable3.fly()对应函数调用汇编指令

在这里插入图片描述

在这里插入图片描述

你会发现flyable3的函数调用经过了大量的计算和中间跳转。性能较差。因为flyable3被告知编译器他是未知的实现类需要动态计算。
flyable2的函数调用非常简介明了高效,因为some声明告诉编译器他是明确的的实现类,所以可以进行大量的编译器优化

  • 存在类型(Existential type)

用any 去修饰的protocol类型。告知编译器他实现类是未知的,不能轻易优化。

func generateSomeFlyable(flag:Bool)->any Flyable{
   //由于any Flyable告诉了编译器 这个返回类型是不确定实现类。所以你可以随意返回Flyable子类型
   if flag {
       return Bird("bird2")
   }else {
       return Airplane()
   }
}

为什么需要any和some

如果protocol存在自要求(self requirement)关联类型(associatetype) 需要开发者告知业务情况方便进行编译优化。(博主擅自猜测 不具备权威性)

综述

结论1:
在Swift中所有包含自要求(self requirement)关联类型(associatetype) 的协议必须要使用any或者some去修饰以告知编译器如何去优化,否则只能用于用作泛型约束。

protocol Flyable {
    func fly()
    associatedtype types
}
struct Bird: Flyable {
    typealias types = Int
    
    var name:String
    init(_ name:String){
        self.name=name
    }
    func fly() {
        print("Bird is flying\(name)")
    }
}
struct Airplane: Flyable {
    typealias types = Int
    func fly() {
        print("Airplane is flying")
    }
}

//compile error:Use of protocol 'Flyable' as a type must be written 'any Flyable'
var myVar:Flyable = Airplane();
//compile success
func myFunc<T:Flyable>(_ myparam:T){
}

结论2:
some 比any 更高效。some函数调用采用静态分派,而any为动态分派。

结论3:
由于any为动态分派所以可以进行类JAVA泛型操作

protocol Flyable {
    func fly()
    associatedtype types
}
struct Bird: Flyable {
    typealias types = Int
    
    var name:String
    init(_ name:String){
        self.name=name
    }
    func fly() {
        print("Bird is flying\(name)")
    }
}
struct Airplane: Flyable {
    typealias types = Int
    func fly() {
        print("Airplane is flying")
    }
}
//compile success.因为any修饰的编译器指关心是其子类即可
var myArr :[any Flyable] = [Bird("1"),Airplane()];
//compile error:Conflicting arguments to generic parameter 'τ_0_0' ('Bird' vs. 'Airplane')
//由于some需要明确子类,但是这里集合包含Bird和Airplane存在二义性。myArr2[0].fly()时无法进行静态分派,因此无法编译
var myArr2 :[some Flyable] = [Bird("1"),Airplane()];
//compile error:Function declares an opaque return type 'some Flyable', but the return statements in its body do not have matching underlying types
func generateAnyFlyable(flag:Bool)->some Flyable{
    //由于some声明存在二义性无法通过编译器
    if flag {
        return Bird("bird2")
    }else {
        return Airplane()
    }
}
//compile success
func generateSomeFlyable(flag:Bool)->any Flyable{
    //由于any Flyable告诉了编译器 这个返回类型是不确定实现类。所以你可以随意返回Flyable子类型
    if flag {
        return Bird("bird2")
    }else {
        return Airplane()
    }
}

参考

https://swiftsenpai.com/swift/understanding-some-and-any/

相关推荐

  1. Swift

    2024-03-20 07:02:04       50 阅读
  2. SwiftWebView

    2024-03-20 07:02:04       34 阅读
  3. Swift整型

    2024-03-20 07:02:04       41 阅读
  4. Swift布尔型

    2024-03-20 07:02:04       37 阅读
  5. Swift枚举

    2024-03-20 07:02:04       39 阅读
  6. Swift结构体

    2024-03-20 07:02:04       34 阅读
  7. SwiftTableView原理

    2024-03-20 07:02:04       28 阅读
  8. SwiftTableView使用

    2024-03-20 07:02:04       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-20 07:02:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-20 07:02:04       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-20 07:02:04       87 阅读
  4. Python语言-面向对象

    2024-03-20 07:02:04       96 阅读

热门阅读

  1. [论文笔记] Open-Sora 4、sora复现训练过程 (新repo)

    2024-03-20 07:02:04       40 阅读
  2. OpenCV 单目相机光平面标定

    2024-03-20 07:02:04       42 阅读
  3. 【ML】深度学习演进与神经网络反向传播推导 2

    2024-03-20 07:02:04       34 阅读
  4. 高效日志为服务器保驾护航

    2024-03-20 07:02:04       41 阅读
  5. Python实战:爬虫基础与Scrapy框架入门

    2024-03-20 07:02:04       45 阅读
  6. Hive 使用 LIMIT 指定偏移量返回数据

    2024-03-20 07:02:04       42 阅读