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中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1

MySQL 索引简介及常见的索引类型有哪些

《MySQL索引简介及常见的索引类型有哪些》MySQL索引是加速数据检索的特殊结构,用于存储列值与位置信息,常见的索引类型包括:主键索引、唯一索引、普通索引、复合索引、全文索引和空间索引等,本文介绍... 目录什么是 mysql 的索引?常见的索引类型有哪些?总结性回答详细解释1. MySQL 索引的概念2

Java获取当前时间String类型和Date类型方式

《Java获取当前时间String类型和Date类型方式》:本文主要介绍Java获取当前时间String类型和Date类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录Java获取当前时间String和Date类型String类型和Date类型输出结果总结Java获取

SpringBoot改造MCP服务器的详细说明(StreamableHTTP 类型)

《SpringBoot改造MCP服务器的详细说明(StreamableHTTP类型)》本文介绍了SpringBoot如何实现MCPStreamableHTTP服务器,并且使用CherryStudio... 目录SpringBoot改造MCP服务器(StreamableHTTP)1 项目说明2 使用说明2.1