Swift5 17.不透明类型Opaque Types, Memory Safety

2023-10-16 03:59

本文主要是介绍Swift5 17.不透明类型Opaque Types, Memory Safety,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • Opaque Types
    • 返回Opaque Types(待研究)
    • Opaque Types 和 Protocol Types的区别
  • Memory Safety
    • 对In-Out参数的访问冲突
    • Methods中的self获取冲突
    • 访问属性冲突

Opaque Types

返回值类型不透明的函数或方法将隐藏其返回值的类型信息。

protocol Shape {func draw() -> String
}struct Triangle: Shape {var size: Intfunc draw() -> String {var result = [String]()for length in 1...size {result.append(String(repeating: "*", count: length))}return result.joined(separator: "\n")}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***struct FlippedShape<T: Shape>: Shape {var shape: Tfunc draw() -> String {let lines = shape.draw().split(separator: "\n")return lines.reversed().joined(separator: "\n")}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *struct JoinedShape<T: Shape, U: Shape>: Shape {var top: Tvar bottom: Ufunc draw() -> String {return top.draw() + "\n" + bottom.draw()}
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *

返回Opaque Types(待研究)

可以认为不透明类型就像是通用类型的逆向。具有不透明返回类型的函数必须仅返回单个类型的值。

struct Square: Shape {var size: Intfunc draw() -> String {let line = String(repeating: "*", count: size)let result = Array<String>(repeating: line, count: size)return result.joined(separator: "\n")}
}func makeTrapezoid() -> some Shape {let top = Triangle(size: 2)let middle = Square(size: 2)let bottom = FlippedShape(shape: top)let trapezoid = JoinedShape(top: top,bottom: JoinedShape(top: middle, bottom: bottom))return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// *
// **
// **
// **
// **
// *func flip<T: Shape>(_ shape: T) -> some Shape {return FlippedShape(shape: shape)
}
func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {JoinedShape(top: top, bottom: bottom)
}let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *func invalidFlip<T: Shape>(_ shape: T) -> some Shape {if shape is Square {return shape // Error: return types don't match}return FlippedShape(shape: shape) // Error: return types don't match
}struct FlippedShape<T: Shape>: Shape {var shape: Tfunc draw() -> String {if shape is Square {return shape.draw()}let lines = shape.draw().split(separator: "\n")return lines.reversed().joined(separator: "\n")}
}

Opaque Types 和 Protocol Types的区别

返回不透明类型看起来与使用协议类型作为函数的返回类型非常相似,但是这两种返回类型在是否保留类型标识方面有所不同。不透明类型是指一种特定的类型,尽管函数的调用者无法看到哪种类型。协议类型可以指符合协议的任何类型。一般而言,协议类型为您提供它们存储的值的基础类型的更多灵活性,而不透明类型使您可以对这些基础类型做出更强的保证。

Memory Safety

默认情况下,Swift防止代码中发生不安全行为。例如,Swift确保在使用变量之前先对其进行初始化,在释放变量后不访问内存,并检查数组索引是否存在越界错误。大多数时候不需要考虑访问内存,但要了解在何处可能发生冲突,来避免编写对内存的访问有冲突的代码。

在冲突的访问环境中要考虑内存访问的三个特征:访问是读还是写,访问的持续时间以及要访问的内存位置。

  • 至少一个是写访问权限。
  • 它们访问内存中的相同位置。
  • 它们的持续时间重叠。

读和写访问之间的区别通常很明显:写访问会更改内存中的位置,但读访问不会。内存中的位置是指所访问的内容,例如,变量,常量或属性。内存访问的持续时间是瞬时的或长期的。如果在访问开始之后但结束之前不可能运行其他代码,则访问是瞬时的。从本质上讲,两个瞬时访问不能同时发生。大多数内存访问是瞬时的。

对In-Out参数的访问冲突

函数可以对其所有In-Out参数进行长期写访问。在对所有非In-Out参数进行评估之后,将开始对In-Out参数进行写访问,并持续该函数调用的整个过程。如果有多个In-Out参数,则写入访问的开始顺序与参数出现的顺序相同。
这种长期写访问的结果是,即使作用域规则和访问控制允许这样做,您也无法访问以in-out形式传递的原始变量-对原始文件的任何访问都会产生冲突。例如:

var stepSize = 1func increment(_ number: inout Int) {number += stepSize
}increment(&stepSize)
// Error: conflicting accesses to stepSize

对stepSize的读取访问与对的number写入访问重叠,所以报错
在这里插入图片描述
解决此冲突的一种方法是显式复制stepSize:

// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2

长期对in-out参数进行写访问的另一个结果是,将单个变量作为同一函数的多个in-out参数的参数传递会产生冲突。例如:

func balance(_ x: inout Int, _ y: inout Int) {let sum = x + yx = sum / 2y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore)  // OK
balance(&playerOneScore, &playerOneScore)
// Error: conflicting accesses to playerOneScore

用playerOneScore和playerTwoScore作为参数调用不会产生冲突,这时有两个时间重叠的写访问,但是它们访问内存中的不同位置。相反,传递两个playerOneScore参数的值会产生冲突,因为它试图同时对内存中的同一位置执行两次写访问。

由于运算符是函数,因此他们也可以长期访问其输入输出参数。例如,如果balance(_:_:)是一个名为的运算符<^>,则写入playerOneScore <^> playerOneScore将导致与balance(&playerOneScore, &playerOneScore)相同的冲突。

Methods中的self获取冲突

struct Player {var name: Stringvar health: Intvar energy: Intstatic let maxHealth = 10mutating func restoreHealth() {health = Player.maxHealth}
}extension Player {mutating func shareHealth(with teammate: inout Player) {balance(&teammate.health, &health)}
}var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria)  // OK

在这里插入图片描述

oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar

mutation方法需要self在该方法的持续时间内进行写访问,而in-out参数需要teammate在相同的持续时间内进行写访问。在该方法中,两个self和都teammate指向内存中的同一位置-如下图所示。这两个写访问引用相同的内存,并且它们重叠,从而产生冲突。
在这里插入图片描述

访问属性冲突

诸如结构,元组和枚举之类的类型由各个组成值组成,例如结构的属性或元组的元素。因为这些是值类型,所以对值的任何部分进行更改都会对整个值进行更改,这意味着对属性之一的读或写访问要求对整个值的读或写访问。例如,对元组元素的重叠写访问会产生冲突:

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation

下面的代码显示,对存储在全局变量中的结构的属性进行重叠的写访问时,会出现相同的错误。

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // Error

实际上,对结构属性的大多数访问都可以安全地重叠。例如,如果将holly上面示例中的变量更改为局部变量而不是全局变量,则编译器可以证明对结构的存储属性的重叠访问是安全的:

func someFunction() {var oscar = Player(name: "Oscar", health: 10, energy: 10)balance(&oscar.health, &oscar.energy)  // OK
}

保留内存安全性并非始终需要限制对结构属性的重叠访问。内存安全是理想的保证,但是独占访问是比内存安全更严格的要求-这意味着即使某些代码违反了对内存的独占访问,某些代码仍可以保留内存安全。如果编译器可以证明对内存的非独占访问仍然是安全的,则Swift允许使用此内存安全代码。具体来说,如果满足以下条件,则可以证明重叠访问结构的属性是安全的:

  • 仅访问实例的存储属性,而不访问计算的属性或类属性。
  • 结构是局部变量的值,而不是全局变量的值。
  • 该结构要么没有被任何闭包捕获,要么仅被不冒号的闭包捕获。

如果编译器无法证明访问是安全的,则它不允许访问。

这篇关于Swift5 17.不透明类型Opaque Types, Memory Safety的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/218901

相关文章

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET

Python中异常类型ValueError使用方法与场景

《Python中异常类型ValueError使用方法与场景》:本文主要介绍Python中的ValueError异常类型,它在处理不合适的值时抛出,并提供如何有效使用ValueError的建议,文中... 目录前言什么是 ValueError?什么时候会用到 ValueError?场景 1: 转换数据类型场景

C# dynamic类型使用详解

《C#dynamic类型使用详解》C#中的dynamic类型允许在运行时确定对象的类型和成员,跳过编译时类型检查,适用于处理未知类型的对象或与动态语言互操作,dynamic支持动态成员解析、添加和删... 目录简介dynamic 的定义dynamic 的使用动态类型赋值访问成员动态方法调用dynamic 的

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s