Swift透明类型

在函数返回值的位置使用some protocol 表示返回某一个特定的类型。这个类型对方法调用者不可见,但是保留了类型信息。如下代码的makeTrapezoid函数的返回值就是透明类型。

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")
    }
}
struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
struct Square: Shape {
    var size: Int
    func 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())
// *
// **
// **
// **
// **
// *

some Shape表示某种遵循Shape的类型,这一类型是特定的,单一的。makeTrapezoid函数不能有条件语句来判断不同条件下返回不同的遵循Shape的类型,只能是一种类型。

如果去掉some关键字,makeTrapezoid函数返回的就是遵循Shape协议的任何类型。只要遵循Shape协议就可以。可以在函数体内使用条件判断返回不同的类型。使用协议作为返回值类型会使得某些依赖类型信息的操作无法执行。比如

func protoFlip<T: Shape>(_ shape: T) -> Shape {
    if shape is Square {
        return shape
    }

    return FlippedShape(shape: shape)
}
let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing  // Error

Shape协议不包含==方法。如果添加一个==方法,==方法需要知道左右两边的类型信息。这就和Shape作为返回值的本意相违背。

有些场景下不能使用协议作为返回值,就需要使用透明类型。如以下代码。

protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }

// Error: Protocol with associated types can't be used as a return type.
func makeProtocolContainer<T>(item: T) -> Container {
    return [item]
}

// Error: Not enough information to infer C.
func makeProtocolContainer<T, C: Container>(item: T) -> C {
    return [item]
}

第一种情况是带有关联类型的协议不能作为函数返回值,第二种情况是被有关联类型的协议限制的范型不能作为返回值。
两种情况都无法推导出关联类型的类型,所以不能作为函数返回值。这两种情况下使用透明类型是合适的。