当前位置: 代码迷 >> 综合 >> Swift5 17.不透明类型Opaque Types, Memory Safety
  详细解决方案

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

热度:79   发布时间:2024-01-29 02:45:04.0

目录

  • 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允许使用此内存安全代码。具体来说,如果满足以下条件,则可以证明重叠访问结构的属性是安全的:

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

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

  相关解决方案