我正在使用 SwiftUI 开发一个项目。从一开始,swiftUI 就展示了面向协议编程的强大功能和应用。所以我开始研究和扩展我对协议和不透明类型的知识。
在文档中Swift 编程语言在页面末尾明确指出:
这种方法的问题是形状变换不嵌套。翻转三角形的结果是 Shape 类型的值,并且 protoFlip(:) 函数采用符合 Shape 协议的某种类型的参数。然而,协议类型的值不符合该协议; protoFlip(:) 返回的值不符合 Shape。这意味着像 protoFlip(protoFlip(smallTriange)) 这样应用多个变换的代码是无效的,因为翻转的形状不是 protoFlip(_:) 的有效参数
但是......当我尝试嵌套该函数时,它工作得很好。那么文档是错误的还是这是语言的最新更新并且文档有点落后?
这是代码(我在操场上运行的)。
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
//return type is a protocol
func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
//Testing if nested function works
let smallTriangle = Triangle(size: 3)
let testNest = protoFlip(protoFlip(smallTriangle))
我认为这本书用了一个不好的例子来说明问题。一般来说,您的代码将正确编译并运行。但是协议方法中的一些快速功能,例如关联类型和自我要求,打破了我们过去习惯的多态性概念。
为了总结我所说的内容,请看一下这段代码:
protocol Shape: Equatable {
func draw() -> String
func doSomething(with other: Self) // self requirement
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
func doSomething(with other: Self) {
print("Tri size: \(other.size)")
}
}
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
func doSomething(with other: Self) {
print("Other filled shape: \(other.draw())")
}
}
struct Rect: Shape {
var width: Int
var height: Int
func draw() -> String {
let line = String(repeating: "*", count: width)
let result = Array<String>(repeating: line, count: height)
return result.joined(separator: "\n")
}
func doSomething(with other: Self) {
print("W: \(other.width) H:\(other.height)")
}
}
func protoFlip<T: Shape>(_ shape: T) -> Shape { //Compiler emits error: Use of protocol 'Shape' as a type must be written 'any Shape'
return FlippedShape(shape: shape)
}
let smallTriangle = Triangle(size: 3)
let protoFlippedTriangle = protoFlip(protoFlip(protoFlip(smallTriangle)))
print(protoFlippedTriangle.draw())
如果没有不透明类型,此代码将无法编译,因为在这种情况下,编译器需要检查我们传递给函数的形状是否与返回的形状相同。因为我们不能在除相同类型的形状之外的任何其他形状上调用 doSomething