<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 不透明類型 具有不透明返回類型的函數或方法會隱藏返回值的類型信息。函數不再提供具體的類型作為返回類型,而是根據它支持的協議來描述返回值。在處理模塊和調用代碼之間的關系時,隱藏類型信息非常有用,因為返回的底層數據類型仍然可以保持私有。而且不同于返回協議類型,不透明類型能保證類型一致性 —— 編譯器能獲取到類型信息,同時模塊使用者卻不能獲取到。 ## 不透明類型解決的問題 {#the-problem-that-opaque-types-solve} 舉個例子,假設你正在寫一個模塊,用來繪制 ASCII 符號構成的幾何圖形。它的基本特征是有一個 `draw()` 方法,會返回一個代表最終幾何圖形的字符串,你可以用包含這個方法的 `Shape` 協議來描述: ```swift 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()) // * // ** // *** ``` 你可以利用泛型來實現垂直翻轉之類的操作,就像下面這樣。然而,這種方式有一個很大的局限:翻轉操作的結果會暴露我們用于構造結果的泛型類型: ```swift 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>` 結構體,能將幾何圖形垂直拼接起來。如果拼接一個翻轉三角形和一個普通三角形,它就會得到類似于 `JoinedShape<FlippedShape<Triangle>, Triangle>` 這樣的類型。 ```swift 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()) // * // ** // *** // *** // ** // * ``` 暴露構造所用的具體類型會造成類型信息的泄露,因為 ASCII 幾何圖形模塊的部分公開接口必須聲明完整的返回類型,而實際上這些類型信息并不應該被公開聲明。輸出同一種幾何圖形,模塊內部可能有多種實現方式,而外部使用時,應該與內部各種變換順序的實現邏輯無關。諸如 `JoinedShape` 和 `FlippedShape` 這樣包裝后的類型,模塊使用者并不關心,它們也不應該可見。模塊的公開接口應該由拼接、翻轉等基礎操作組成,這些操作也應該返回獨立的 `Shape` 類型的值。 ## 返回不透明類型 {#returning-an-opaque-type} 你可以認為不透明類型和泛型相反。泛型允許調用一個方法時,為這個方法的形參和返回值指定一個與實現無關的類型。舉個例子,下面這個函數的返回值類型就由它的調用者決定: ```swift func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... } ``` `x` 和 `y` 的值由調用 `max(_:_:)` 的代碼決定,而它們的類型決定了 `T` 的具體類型。調用代碼可以使用任何遵循了 `Comparable` 協議的類型,函數內部也要以一種通用的方式來寫代碼,才能應對調用者傳入的各種類型。`max(_:_:)` 的實現就只使用了所有遵循 `Comparable` 協議的類型共有的特性。 而在返回不透明類型的函數中,上述角色發生了互換。不透明類型允許函數實現時,選擇一個與調用代碼無關的返回類型。比如,下面的例子返回了一個梯形,卻沒直接輸出梯形的底層類型: ```swift 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()) // * // ** // ** // ** // ** // * ``` 這個例子中,`makeTrapezoid()` 函數將返回值類型定義為 `some Shape`;因此,該函數返回遵循 `Shape` 協議的給定類型,而不需指定任何具體類型。這樣寫 `makeTrapezoid()` 函數可以表明它公共接口的基本性質 —— 返回的是一個幾何圖形 —— 而不是部分的公共接口生成的特殊類型。上述實現過程中使用了兩個三角形和一個正方形,還可以用其他多種方式重寫畫梯形的函數,都不必改變返回類型。 這個例子凸顯了不透明返回類型和泛型的相反之處。`makeTrapezoid()` 中代碼可以返回任意它需要的類型,只要這個類型是遵循 `Shape` 協議的,就像調用泛型函數時可以使用任何需要的類型一樣。這個函數的調用代碼需要采用通用的方式,就像泛型函數的實現代碼一樣,這樣才能讓 `makeTrapezoid()` 返回的任何 `Shape` 類型的值都能被正常使用。 你也可以將不透明返回類型和泛型結合起來,下面的兩個泛型函數也都返回了遵循 `Shape` 協議的不透明類型。 ```swift 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()) // * // ** // *** // *** // ** // * ``` 這個例子中 `opaqueJoinedTriangles` 的值和前文 [不透明類型解決的問題](#the-problem-that-opaque-types-solve) 中關于泛型的那個例子中的 `joinedTriangles` 完全一樣。不過和前文不一樣的是,`flip(-:)` 和 `join(-:-:)` 將對泛型參數的操作后的返回結果包裝成了不透明類型,這樣保證了在結果中泛型參數類型不可見。兩個函數都是泛型函數,因為他們都依賴于泛型參數,而泛型參數又將 `FlippedShape` 和 `JoinedShape` 所需要的類型信息傳遞給它們。 如果函數中有多個地方返回了不透明類型,那么所有可能的返回值都必須是同一類型。即使對于泛型函數,不透明返回類型可以使用泛型參數,但仍需保證返回類型唯一。比如,下面就是一個*非法*示例 —— 包含針對 `Square` 類型進行特殊處理的翻轉函數。 ```swift func invalidFlip<T: Shape>(_ shape: T) -> some Shape { if shape is Square { return shape // 錯誤:返回類型不一致 } return FlippedShape(shape: shape) // 錯誤:返回類型不一致 } ``` 如果你調用這個函數時傳入一個 `Square` 類型,那么它會返回 `Square` 類型;否則,它會返回一個 `FlippedShape` 類型。這違反了返回值類型唯一的要求,所以 `invalidFlip(_:)` 不正確。修正 `invalidFlip(_:)` 的方法之一就是將針對 `Square` 的特殊處理移入到 `FlippedShape` 的實現中去,這樣就能保證這個函數始終返回 `FlippedShape`: ```swift struct FlippedShape<T: Shape>: Shape { var shape: T func draw() -> String { if shape is Square { return shape.draw() } let lines = shape.draw().split(separator: "\n") return lines.reversed().joined(separator: "\n") } } ``` 返回類型始終唯一的要求,并不會影響在返回的不透明類型中使用泛型。比如下面的函數,就是在返回的底層類型中使用了泛型參數: ```swift func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection { return Array<T>(repeating: shape, count: count) } ``` 這種情況下,返回的底層類型會根據 `T` 的不同而發生變化:但無論什么形狀被傳入,`repeat(shape:count:)` 都會創建并返回一個元素為相應形狀的數組。盡管如此,返回值始終還是同樣的底層類型 `[T]`, 所以這符合不透明返回類型始終唯一的要求。 ## 不透明類型和協議類型的區別 {#differences-between-opaque-types-and-protocol-types} 雖然使用不透明類型作為函數返回值,看起來和返回協議類型非常相似,但這兩者有一個主要區別,就在于是否需要保證類型一致性。一個不透明類型只能對應一個具體的類型,即便函數調用者并不能知道是哪一種類型;協議類型可以同時對應多個類型,只要它們都遵循同一協議。總的來說,協議類型更具靈活性,底層類型可以存儲更多樣的值,而不透明類型對這些底層類型有更強的限定。 比如,這是 `flip(_:)` 方法不采用不透明類型,而采用返回協議類型的版本: ```swift func protoFlip<T: Shape>(_ shape: T) -> Shape { return FlippedShape(shape: shape) } ``` 這個版本的 `protoFlip(_:)` 和 `flip(_:)` 有相同的函數體,并且它也始終返回唯一類型。但不同于 `flip(_:)`,`protoFlip(_:)` 返回值其實不需要始終返回唯一類型 —— 返回類型只需要遵循 `Shape` 協議即可。換句話說,`protoFlip(_:)` 比起 `flip(_:)` 對 API 調用者的約束更加松散。它保留了返回多種不同類型的靈活性: ```swift func protoFlip<T: Shape>(_ shape: T) -> Shape { if shape is Square { return shape } return FlippedShape(shape: shape) } ``` 修改后的代碼根據代表形狀的參數的不同,可能返回 `Square` 實例或者 `FlippedShape` 實例,所以同樣的函數可能返回完全不同的兩個類型。當翻轉相同形狀的多個實例時,此函數的其他有效版本也可能返回完全不同類型的結果。`protoFlip(_:)` 返回類型的不確定性,意味著很多依賴返回類型信息的操作也無法執行了。舉個例子,這個函數的返回結果就不能用 == 運算符進行比較了。 ```swift let protoFlippedTriangle = protoFlip(smallTriangle) let sameThing = protoFlip(smallTriangle) protoFlippedTriangle == sameThing // 錯誤 ``` 上面的例子中,最后一行的錯誤來源于多個原因。最直接的問題在于,`Shape` 協議中并沒有包含對 == 運算符的聲明。如果你嘗試加上這個聲明,那么你會遇到新的問題,就是 == 運算符需要知道左右兩側參數的類型。這類運算符通常會使用 `Self` 類型作為參數,用來匹配符合協議的具體類型,但是由于將協議當成類型使用時會發生類型擦除,所以并不能給協議加上對 `Self` 的實現要求。 將協議類型作為函數的返回類型能更加靈活,函數只要返回遵循協議的類型即可。然而,更具靈活性導致犧牲了對返回值執行某些操作的能力。上面的例子就說明了為什么不能使用 == 運算符 —— 它依賴于具體的類型信息,而這正是使用協議類型所無法提供的。 這種方法的另一個問題在于,變換形狀的操作不能嵌套。翻轉三角形的結果是一個 `Shape` 類型的值,而 `protoFlip(_:)` 方法的則將遵循 `Shape` 協議的類型作為形參,然而協議類型的值并不遵循這個協議;`protoFlip(_:)` 的返回值也并不遵循 `Shape` 協議。這就是說 `protoFlip(protoFlip(smallTriange))` 這樣的多重變換操作是非法的,因為經過翻轉操作后的結果類型并不能作為 `protoFlip(_:)` 的形參。 相比之下,不透明類型則保留了底層類型的唯一性。Swift 能夠推斷出關聯類型,這個特點使得作為函數返回值,不透明類型比協議類型有更大的使用場景。比如,下面這個例子是 [泛型](./22_Generics.md) 中講到的 `Container` 協議: ```swift protocol Container { associatedtype Item var count: Int { get } subscript(i: Int) -> Item { get } } extension Array: Container { } ``` 你不能將 `Container` 作為方法的返回類型,因為此協議有一個關聯類型。你也不能將它用于對泛型返回類型的約束,因為函數體之外并沒有暴露足夠多的信息來推斷泛型類型。 ```swift // 錯誤:有關聯類型的協議不能作為返回類型。 func makeProtocolContainer<T>(item: T) -> Container { return [item] } // 錯誤:沒有足夠多的信息來推斷 C 的類型。 func makeProtocolContainer<T, C: Container>(item: T) -> C { return [item] } ``` 而使用不透明類型 `some Container` 作為返回類型,就能夠明確地表達所需要的 API 契約 —— 函數會返回一個集合類型,但并不指明它的具體類型: ```swift func makeOpaqueContainer<T>(item: T) -> some Container { return [item] } let opaqueContainer = makeOpaqueContainer(item: 12) let twelve = opaqueContainer[0] print(type(of: twelve)) // 輸出 "Int" ``` `twelve` 的類型可以被推斷出為 `Int`, 這說明了類型推斷適用于不透明類型。在 `makeOpaqueContainer(item:)` 的實現中,底層類型是不透明集合 `[T]`。在上述這種情況下,`T` 就是 `Int` 類型,所以返回值就是整數數組,而關聯類型 `Item` 也被推斷出為 `Int`。`Container` 協議中的 `subscipt` 方法會返回 `Item`,這也意味著 `twelve` 的類型也被能推斷出為 `Int`。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看