- 場景:返回值使用泛型,會暴露具體類型
- 不同于返回一個協(xié)議類型的值荧止,不透明類型保持了類型的身份——編譯器可以訪問類型的信息,但模塊的客戶端不能
不透明類型解決的問題
- 泛型例子-使用 ASCII 繪制圖像
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")
}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
- 使用泛型:翻轉(zhuǎ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")
}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
- 定義了一個 JoinedShape<T: Shape, U: Shape> 結(jié)構(gòu)體阶剑,把兩個圖形垂直地結(jié)合在一起
JoinedShape<FlippedShape<Triangle>, Triangle>
struct JoinedShape<T: Shape, U: Shape>: Shape {
var top: T
var bottom: U
func draw() -> String {
return top.draw() + "\n" + bottom.draw()
}
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
- 弊端:暴露了返回值類型跃巡,而且調(diào)用接口需要關(guān)心返回值類型
返回不透明類型
- 不透明類型當(dāng)作泛型反義詞
- 下面函數(shù)返回類型由調(diào)用時決定
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
- x 、 y 值的類型決定了T 的具體類型
- 不透明類型:函數(shù)返回了一個梯形而沒有暴露圖形的類型
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牧愁,函數(shù)返回一個遵循 Shape 協(xié)議的類型素邪,而不需要標(biāo)明具體類型,只要類型遵循 Shape 協(xié)議
泛型 + 不透明返回類型
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())
// *
// **
// ***
// ***
// **
// *
函數(shù)代碼有多處 return 時猪半,保證都是相同類型
返回類型也可用泛型類型參數(shù)兔朦,但必須保證單一類型
例子:包含方塊特殊處理的圖形翻轉(zhuǎn)函數(shù)的錯誤版本:
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
}
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
return Array<T>(repeating: shape, count: count)
}
無論什么圖形傳入,repeat(shape:count:) 都會創(chuàng)建和返回這個圖形的數(shù)組
依舊滿足返回不透明類型的函數(shù)必須返回同一類型的約束
不透明類型和協(xié)議類型的區(qū)別
與使用協(xié)議類型作為函數(shù)返回類型非常相似
-
區(qū)別于它們是否保存類型特征
- 不透明類型引用為特定的類型(調(diào)用者不能看到)
- 議類型可以引用到任何遵循這個協(xié)議的類型
比如磨确,這里有一個版本的 flip(_:) 它返回一個協(xié)議類型的值而不是不透明類型:
func protoFlip<T: Shape>(_ shape: T) -> Shape {
return FlippedShape(shape: shape)
}
- protoFlip(_:) 返回的值并不要求總是返回相同的類型——只要遵循 Shape 協(xié)議就好
- protoFlip(:) 使得 API 要求遠(yuǎn)比 flip(:) 要松沽甥。它保留了返回多種類型的彈性
func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
// 函數(shù)返回的兩個翻轉(zhuǎn)過的圖形可能擁有完全不同的類型
protoFlip(_:) 具有更少的特定返回類型信息,意味依賴類型信息的操作無法完成
比如乏奥, == 運(yùn)算符無法比較函數(shù)返回的結(jié)果
let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing // Error
-
錯誤原因
- Shape 并沒有 == 作為自身協(xié)議的需求
- 嘗試添加摆舟,會遇到 == 運(yùn)算符需要知道左手實(shí)際參數(shù)和右手實(shí)際參數(shù)的類型
彈性的代價就是返回值無法使用某些運(yùn)算
另一個問題是圖形轉(zhuǎn)換不能嵌套
相反,不透明類型保持了具體類型的特征邓了。Swift 可以推斷相關(guān)類型
protocol Container {
associatedtype Item
var count: Int { get }
subscript(i: Int) -> Item { get }
}
extension Array: Container { }
- 不能使用 Container 作為函數(shù)的返回類型恨诱,因?yàn)檫@個協(xié)議有一個關(guān)聯(lián)類型
- 也不能使用它作為范型返回類型的約束,因?yàn)樗诤瘮?shù)體外沒足夠信息推斷它到底成為什么范型類型
// 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]
}
- 使用不透明類型 some Container 作為返回類型則能夠表達(dá)期望的 API 約束——函數(shù)返回一個容器骗炉,但不指定特定的容器類型:
func makeOpaqueContainer<T>(item: T) -> some Container {
return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// Prints "Int"
- 不透明容器的具體類型是 [T]