Swiftのタイプ消去

8167 ワード

まずは本文のDemoをお届けします

標準ライブラリのタイプ消去


Swiftのタイプ消去を聞いたことがあるかどうかはわかりませんが、タイプ消去とは、ある実例の実際のタイプを暴露せず、外部に必要なタイプだけを暴露することです.例えば、私たちがclassまたはstructを作成してプロトコルを実現したとき、私たちがこの例を対外的に提供したとき、これがプロトコルを実現したことを外部に知らせたいだけですが、このプロトコルを実現したclassまたはstructがどのタイプなのかを外部に知られたくありません.このとき、私たちはタイプ消去を使用する必要があります.実際には、Swiftの標準ライブラリにはこのようなものがあります.
struct FibsIterator: IteratorProtocol {
    var status = (0, 1)
    mutating func next() -> Int? {
        guard status.0 < 100 else { return nil }
        let num = status.0
        self.status = (status.1, status.0 + status.1)
        return num
    }
}

struct SquareIterator: IteratorProtocol {
    var status = 1
    mutating func next() -> Int? {
        guard status < 100 else { return nil }
        defer {
            status = status * 2
        }
        return status
    }
}
// fibsIterator type: AnyIterator
let fibsIterator = AnyIterator(FibsIterator())
// squareIterator type: AnyIterator
let squareIterator = AnyIterator(SquareIterator())
// anyIteratorArray type: [AnyIterator]
let anyIteratorArray = [fibsIterator, squareIterator]
// fibsSequence type: AnySequence
let fibsSequence = AnySequence { return FibsIterator() }
//  Collection 
// c type: AnyCollection
let c = AnyCollection(Array())
IteratorProtocolSequenceCollectionについてはここでは紹介しませんが…
私たちがフィボラッチの反復器を実現した後、私たちは外部にこの反復器を使ってフィボラッチシーケンスを生成させたいと思っていますが、私は私がFibsIteratorこのstructで実現したことを外部に知られたくありません.そこで私たちはAnyIteratorAnySequenceAnyCollectionで包装し、外部の使用者はこれを反復するときにIntタイプの反復器、シーケンス、または集合を生成することしか知らない.これにより、FibsIteratorのタイプを非表示にします.

車輪を作ってみる


タイプ消去という小さなテクニックを知っている以上、車輪の作り方を学びましょう.まず、標準ライブラリを真似て消去タイプの反復器を実装します.まず、上記の手順に従ってプロトコルを定義し、2つの反復器を実装します.
protocol MyIteratorProtocol {
    associatedtype Element
    mutating func next() -> Self.Element?
}

struct FibsIterator: MyIteratorProtocol {
    var status = (0, 1)
    mutating func next() -> Int? {
        guard status.0 < 100 else { return nil }
        let num = status.0
        self.status = (status.1, status.0 + status.1)
        return num
    }
}

struct SquareIterator: MyIteratorProtocol {
    var status = 1
    mutating func next() -> Int? {
        guard status < 100 else { return nil }
        defer {
            status = status * 2
        }
        return status
    }
}

次に、反復器包装の箱を実現し、対外的に反復器next()方法で戻されたタイプ(ここではIntタイプを例に挙げる)だけを暴露し、実装された反復器タイプを暴露しないで、最初はこのようなコードを書く可能性があります.
class IteratorBox {
    var iterator: I
    init(_ iterator: I) {
        self.iterator = iterator
    }
    func next() -> I.Element? {
        return iterator.next()
    }
}

// fibsIteratorBox type: IteratorBox
let fibsIteratorBox = IteratorBox(FibsIterator())
// squareIteratorBox type: IteratorBox
let squareIteratorBox = IteratorBox(SquareIterator())
//  ! , Boxs [Any]
let Boxs = [fibsIteratorBox, squareIteratorBox]❌

ここで2つの問題が発生しました.1つ目の問題は、私たちが定義した汎用Iが直接暴露されたことです.これは私たちの初心に反して、私たちが外部に見たいのはI.Elementのタイプで、Iを暴露するタイプではありません.2つ目の問題は,これらIteratorBoxを配列で格納すると,汎用型が異なるためエラーが報告されることである.

方法1:属性保存反復器メソッドの実装


まず、MyIteratorProtocolを実装するタイプが露出することを防止するために、IteratorBoxの汎用型はMyIteratorProtocolElementタイプであり、MyIteratorProtocolのタイプではないことを明らかにした.次に、IteratorBox実装MyIteratorProtocolプロトコルも必要です.IteratorBoxに完全な機能が含まれていることを確認します.
struct IteratorBox: MyIteratorProtocol {
private var nextIMP: () -> A?
init(_ iterator: I) where I.Element == A {
var iteratorCopy=iterator//Swiftのパラメータは にletと されます
self.nextIMP = { iteratorCopy.next() }
}
func next() -> A? {
return nextIMP()
}
}
//fibsIteratorBoxのtype:IteratorBox
let fibsIteratorBox = IteratorBox(FibsIterator())
//squareIteratorBoxのtype:IteratorBox
let squareIteratorBox = IteratorBox(SquareIterator())
//Boxのtype:[IteratorBox]
let Boxs = [fibsIteratorBox, squareIteratorBox]

このような方法は実現が非常に簡単で便利であり,標準ライブラリでも同様の効果を実現している.ただし、MyIteratorProtocolは、複数のメソッドごとに1つの属性を追加して保存し、initメソッドでも追加のコードを記述する必要があるため、メソッドの少ないタイプにのみ適用されます.MyIteratorProtocolメソッドの数が多い場合、MyIteratorProtocolを実装する反復器を上記のように保存し、反復器によって呼び出すしかありません.しかし、上の車輪のところは試したのではないでしょうか.しかも問題が多いですね...次の方法を見てみましょう

方法2:オブジェクト向けの継承とマルチステート特性を利用する


まず、さっきお話ししたニーズを明確にしなければなりません.我々が実装したIteratorBoxの汎用型はMyIteratorProtocolElementタイプであるべきであり、実装MyIteratorProtocolの反復器を保存するために、汎用型を実装MyIteratorProtocolのタイプに制約する必要があり、これにより衝突をもたらす.1つのクラスがこの2つのタイプを同時に満たすことができない以上、私たちは2つのクラスで実現します.
class IteratorBox: MyIteratorProtocol {
func next() -> A? {
fatalError("This method is abstract, you need to implement it!")
}
}

まず,IteratorBox実装MyIteratorProtocolのすべての方法を実装し,方法の実装ではそれをクラッシュさせるだけである.次に、IteratorBoxHelperを実装して反復器を保存し、プロトコル内の方法を実装します.
class IteratorBoxHelper {
    var iterator: I
    init(_ iterator: I) {
        self.iterator = iterator
    }
    func next() -> I.Element? {
        return iterator.next()
    }
}

最後に、IteratorBoxHelperIteratorBoxから継承します.
class IteratorBoxHelper: IteratorBox {
    var iterator: I
    init(_ iterator: I) {
        self.iterator = iterator
    }
    override func next() -> I.Element? {
        return iterator.next()
    }
}
IteratorBoxを継承する場合、汎用AのタイプをIteratorBoxHelperIElementのタイプに制約します.反復器をIteratorBoxHelperでパッケージした後、このタイプをIteratorBoxと宣言し、いわゆるアップシフト(サブクラスタイプを親クラスタイプと宣言)について、これらの反復器を呼び出し、メソッドを呼び出すと、多態性の特徴に基づいて実際のタイプのメソッドが呼び出されます.
// fibsIteratorBox type: IteratorBox
let fibsIteratorBox: IteratorBox = IteratorBoxHelper(FibsIterator())
// squareIteratorBox type: IteratorBox
let squareIteratorBox: IteratorBox = IteratorBoxHelper(SquareIterator())
// Boxs type: [IteratorBox]
let Boxs = [fibsIteratorBox, squareIteratorBox]

print(" fibsIteratorBox next ")
while let num = fibsIteratorBox.next() {
    print(num)
}
print(" squareIteratorBox next ")
while let num = squareIteratorBox.next() {
    print(num)
}

パッケージ後に値の意味がなくなりました


ここで、私たちのFibsIteratorSquareIteratorはすべてstructで、値の意味を持っていますが、別の変数に値を割り当てた後、反復して書き込みを行うとコピーされます.
var fib1 = FibsIterator()
print(fib1.next()!) // 0
print(fib1.next()!) // 1
print(fib1.next()!) // 1
var fib2 = fib1
print(fib1.next()!) // 2
print(fib1.next()!) // 3
print(fib2.next()!) // 2
print(fib2.next()!) // 3

方法1については、IteratorBoxのタイプは、標準ライブラリのAnyIteratorと同じstructであるが、両方とも書き込み時にコピーする特性を備えていない場合、IteratorBoxの閉パケット属性はiteratorCopyを取得し、別の変数に値を割り当てても、閉パケット属性は1つのiteratorCopyを参照する.方法2では、IteratorBoxIteratorBoxHelperが両方classタイプであるため、参照の意味となり、別の変数に値を割り当てるときはポインタコピーのみとなる.
let fibsIteratorBox: IteratorBox = IteratorBoxHelper(FibsIterator())
print(fibsIteratorBox.next()!) // 0
print(fibsIteratorBox.next()!) // 1
print(fibsIteratorBox.next()!) // 1
let fibsBox2 = fibsIteratorBox
print(fibsIteratorBox.next()!) // 2
print(fibsIteratorBox.next()!) // 3
print(fibsBox2.next()!)        // 5
print(fibsBox2.next()!)        // 8

これにより,Swift標準ライブラリにおけるAnyIteratorの実装を模倣することに成功した.間違いがあれば、よろしくお願いします.