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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/469886.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

容器数据卷

目录 一、容器数据卷概念 二、使用数据卷 2.1直接使用命令来挂载 三、实战测试 四、具名挂载和匿名挂载 4.1匿名挂载举例&#xff1a; 4.2具名挂载举例&#xff1a; 五、数据卷容器 一、容器数据卷概念 数据&#xff1f;如果数据都在容器中&#xff0c;那么容器删除&am…

使用Anthenticator验证github

下载 各应用商城都有。 准备扫描 启动应用&#xff0c;点击加号&#xff0c;选择其他账户&#xff0c;进入扫描状态。 打开github的二维码 https://github.com/settings/security 下滚&#xff1a; 如图 扫描&#xff0c;添加&#xff0c;完成

SSH服务

目录 一. 熟悉SSH服务 1.1 何为SSH协议 1.2 SSH服务优点 1.3 常见的SSH协议 1.4 SSH服务的功能 1.5 为何使用SSH服务 1.6 SSH服务的工作原理 1.6.1 公钥传输原理 1.6.2 ssh加密通讯原理 1.7 SSH服务的最佳应用场景 1.8 SSH服务远程登录的方式 1.8.1 方法一&#…

逻辑数据平台的 NoETL 之道(内含QA)

作者简介&#xff1a; 余俊&#xff0c;Aloudata 合伙人 & 技术副总裁。拥有 18 年互联网技术和大数据平台相关架构经验。作为主架构师及核心研发主导并完成了 Alibaba B2B 首个海量分布式 KV 存储系统&#xff0c;作为网站架构师负责 Aliexpress 全球买全球卖交易系统的第…

基于Springboot的船运物流管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的船运物流管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

springboot企业级抽奖项目-整体展示

项目地址 GitHub - kiorr/lottery: 企业红包雨项目 star截图q&#xff1a;3353441618可以领取资料 原型效果 前台 后台 业务分析 项目介绍 项目概述 京东的红包雨大家可能都参与过&#xff0c;在某段时间内随机发放不同的红包 本项目为一个通用的红包雨模式抽奖系统&…

全国人口密度分布数据

数据福利是专门为关注小编博客及公众号的朋友定制的&#xff0c;未关注用户不享受免费共享服务&#xff0c;已经被列入黑名单的用户和单位不享受免费共享服务。参与本号发起的数据众筹&#xff0c;向本号捐赠过硬盘以及多次转发、评论的朋友优先享有免费共享服务。 对人口数量、…

ARM开发板实现24位BMP图片缩放

ARM开发板实现24位BMP图片缩放 一、linux平台bmp图片缩放 最近想在ARM开发板实现BMP图片的缩放&#xff0c;查看了一些资料&#xff0c;大家部分理论知识可参考&#xff1a; akynazh博主 &#xff0c;这位博主程序以window平台为主进行显示&#xff0c;发现在linux平台下编译…

后端系统开发之——接口参数校验

今天难得双更&#xff0c;大家点个关注捧个场 原文地址&#xff1a;后端系统开发之——接口参数校验 - Pleasure的博客 下面是正文内容&#xff1a; 前言 在上一篇文章中提到了接口的开发&#xff0c;虽然是完成了&#xff0c;但还是缺少一些细节——传入参数的校验。 即用户…

51、CR-GCN:EEG通道拓扑结构+脑功能连接捕获EEG通道关系,用于情感识别[我处理的是原始EEG数据哦]

文章&#xff1a; CR-GCN: Channel-Relationships-Based Graph Convolutional Network for EEG Emotion Recognition 单位&#xff1a; 上海大学计算机学院、上海工业计算机、喀什大学计算机学院。提出CR-GCN&#xff0c;使用GCN的邻接矩阵提取情感数据中的特征用于分类。 2…

分布式搜索引擎elasticsearch专栏三

1.数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f; 这些手机的平均价格、最高价格、最低价格&#xff1f; 这些手机每月的销售情况如何&#xff1f; 实现这些…

【双指针】算法例题

目录 二、双指针 25. 验证回文数 ① 26. 判断子序列 ① 27. 两数之和II - 输入有序数组 ② 28. 盛最多水的容器 ② 29. 三数之和 ② 二、双指针 25. 验证回文数 ① 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一…

面试六分钟,难题显真章

职场&#xff0c;这个充满机遇与挑战的舞台&#xff0c;总会在不经意间上演着意想不到的转折。我从一家小公司转投到另一家&#xff0c;原本期待着新的工作环境和更多的发展机会&#xff0c;然而现实却给了我一个不小的打击。 新公司的加班文化&#xff0c;如同一个巨大的漩涡…

服务器端(Debian 12)配置jupyter与R 语言的融合

融合前&#xff1a; 服务器端Debian 12,域名&#xff1a;www.leyuxy.online 1.安装r-base #apt install r-base 2.进入R并安装IRkernel #R >install.packages(“IRkernel”) 3.通过jupyter notebook的Terminal执行&#xff1a; R >IRkernel::installspec() 报错 解决办…

leetcode 303

leetcode 303 题目 例子 思路 使用数组存储[0, i] 的vals 值之和&#xff0c; sum[i] 表示 第0个元素到第(i-1)个元素之和。 代码 class NumArray {vector<int> sum; public:NumArray(vector<int>& nums) {int n nums.size();sum.resize(n1);for(int i0; …

springboot项目讲解

技术栈 vue(前端) springboot(后端主框架) mybatis&#xff08;ORM&#xff0c;用于后端和数据库的映射&#xff0c;即java对象转换成表&#xff09; mysql (关系型数据库) 顶层结构 .idea&#xff1a; idea缓存文件(不需要管) src&#xff1a;代码核心文件夹 —main&#xf…

进程间通信 之 共享内存

目录 什么是共享内存&#xff1f; 共享内存的系统调用接口 共享内存 进程间通信的本质及前提&#xff1a;让不同的进程看到同一份资源&#xff01; 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&a…

知识学习app

管理端&#xff1a; &#xff08;1&#xff09;登录 &#xff08;2&#xff09;首页数据报表&#xff1a;1.数据概括2.一周数据走势 &#xff08;3&#xff09;内容管理&#xff1a; 1.分类管理&#xff1a;新增&#xff0c;修改&#xff0c;删除&#xff0c;排序 2.八股文&…

基础监控理论

文章目录 监控流程架构体系监控分类 监控发展和技术企业中监控发展阶段通用技术和工具 监控流程架构体系 监控流程架构体系是确保信息系统健康、稳定运行的重要组成部分&#xff0c;它包括监控系统的设计、搭建、数据分析、数据采集、稳定性测试、自动化集成、部署上线以及图形…

LAMP 世界上使用最广泛的框架(安装LAMP框架)快照

说是框架就不只是一个东西。L:Linux&#xff0c;一种操作系统类型&#xff0c;专为服务器领域服务. A:Apache&#xff0c;web 服务器。 M:MySQL&#xff0c;数据库&#xff0c;存储项目的元数据&#xff0c;真实数据会存放在硬盘中。 P:PHP&#xff0c;一种编程语言&#xff0…