web-dev-qa-db-ja.com

Swiftのジェネリックの配列

さまざまな型のジェネリッククラスの配列で遊んでいます。問題をサンプルコードで説明するのが最も簡単です。

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Self { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }

    func myMethod() -> [T] {
        return values
    }
}

今のようにコンテナの配列を作成しようとすると:

var containers: [Container<MyProtocol>] = []

エラーが表示されます:

プロトコル「MyProtocol」は、自己または関連するタイプの要件があるため、一般的な制約としてのみ使用できます。

これを修正するには、[AnyObject]

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
// Explicitly stating the types just for clarity.

しかし、containersを列挙すると、別の「問題」が発生します。

for container in containers {
    if let c = container as? Container<Int> {
        println(c.myMethod())

    } else if let c = container as? Container<Double> {
        println(c.myMethod())
    }
}

上記のコードでわかるように、containerのタイプを判別した後、両方のケースで同じメソッドが呼び出されます。私の質問は:

Containerのすべての可能な型にキャストするよりも、正しい型でContainerを取得するより良い方法はありますか?または、私が見落とした何かがありますか?

51
ABakerSmith

あなたが望むことをする方法-ある種の-がある。プロトコルを使用して、型の制限を解消し、必要な結果を得る方法がありますが、それは必ずしもきれいではありません。あなたの状況でプロトコルとして私が思いついたものは次のとおりです。

_protocol MyProtocol {
    func getValue() -> Self 
}

extension Int: MyProtocol {
    func getValue() -> Int {
        return self
    }
}

extension Double: MyProtocol {
    func getValue() -> Double {
        return self
    }
}
_

プロトコル宣言に最初に配置したvalueプロパティは、オブジェクトを返すメソッドに変更されていることに注意してください。

それはあまり面白くない。

ただし、プロトコルのvalueプロパティを削除したため、MyProtocolを型制約としてだけでなく型として使用できます。 Containerクラスはもはやジェネリックである必要さえありません。次のように宣言できます。

_class Container {
    var values: [MyProtocol]

    init(_ values: MyProtocol...) {
        self.values = values
    }

    func myMethod() -> [MyProtocol] {
        return values
    }
}
_

Containerはもはやジェネリックではないので、ArraysのContainerを作成し、それらを反復処理して、myMethod()メソッドの結果を出力できます。

_var containers = [Container]()

containers.append(Container(1, 4, 6, 2, 6))
containers.append(Container(1.2, 3.5))

for container in containers {
    println(container.myMethod())
}

//  Output: [1, 4, 6, 2, 6]
//          [1.2, 3.5]
_

秘Theは、プロトコルを作成することです汎用関数のみを含み、準拠する型に他の要件はありません。それをやめることができれば、プロトコルを型として使用できます。型の制約として。

ボーナスとして(それを呼び出したい場合)、MyProtocol値の配列は、MyProtocolに適合するさまざまなタイプを混在させることさえできます。したがって、次のようにStringMyProtocol拡張子を付けると:

_extension String: MyProtocol {
    func getValue() -> String {
        return self
    }
}
_

実際には、混合型でContainerを初期化できます。

_let container = Container(1, 4.2, "no kidding, this works")
_

[警告-私はこれをオンラインの遊び場でテストしています。私はまだXcodeでテストすることができませんでした...]

編集:

それでもContainerをジェネリックにし、1つのタイプのオブジェクトのみを保持する場合は、itを独自のプロトコルに準拠させることでそれを実現できます。

_protocol ContainerProtocol {
    func myMethod() -> [MyProtocol]
}

class Container<T: MyProtocol>: ContainerProtocol {
    var values: [T] = []

    init(_ values: T...) {
        self.values = values
    } 

    func myMethod() -> [MyProtocol] {
        return values.map { $0 as MyProtocol }
    }
}
_

これでstillが_[ContainerProtocol]_オブジェクトの配列を持ち、myMethod()を呼び出してそれらを反復処理できます:

_let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]

for container in containers {
    println(container.myMethod())
}
_

それでもうまくいかないかもしれませんが、今ではContainerは単一の型に制限されていますが、ContainterProtocolオブジェクトの配列を反復処理できます。

48
Aaron Rasmussen

これは、「何をしたのかwantどうしたの?」の良い例です。そして、Swiftが本当にファーストクラスのタイプを持っていた場合に爆発する複雑さを実際に示しています。

protocol MyProtocol {
    var value: Self { get }
}

すばらしいです。 MyProtocol.valueは、実行時ではなくコンパイル時に決定する必要があることを思い出して、それを実装する型を返します。

var containers: [Container<MyProtocol>] = []

それで、コンパイル時に決定され、これはどのタイプですか?コンパイラを忘れて、紙の上でそれをしてください。ええ、どのタイプになるのかわかりません。つまり、コンクリートタイプです。メタタイプはありません。

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]

AnyObjectが署名に侵入した場合、間違った道を進んでいることがわかります。これについては何も機能しません。 AnyObjectは荒布だけです。

それとも私が見落としていた何かがありますか?

はい。タイプが必要ですが、タイプを指定していません。タイプを制約するためのルールを提供しましたが、実際のタイプは提供していません。あなたの本当の問題に戻って、それについてもっと深く考えてください。 (メタタイプ分析は、CS PhDで作業している場合を除き、「本当の」問題となることはほとんどありません。その場合、SwiftではなくIdrisでこれを行うことになります。)実際に解決している問題は何ですか?

8
Rob Napier

これはEquatableのようなプロトコルでよりよく説明できます。配列[Equatable]を宣言することはできません。2つのIntインスタンスは相互に比較でき、Doubleの2つのインスタンスは相互に比較できますが、Intこれらは両方ともDoubleを実装しますが、Equatableへ。

MyProtocolはプロトコルです。つまり、汎用インターフェースを提供します。残念ながら、定義ではSelfも使用しています。つまり、MyProtocolに準拠するすべてのタイプは、異なる方法で実装されます。

自分で作成しました-Intにはvalueとしてvar value: Intが、MyObjectにはvalueとしてvar value: MyObjectが含まれます。

つまり、MyProtocolに準拠する構造体/クラスを、MyProtocolに準拠する別の構造体/クラスの代わりに使用することはできません。また、具体的なタイプを指定しないと、MyProtocolをこの方法で使用できないことも意味します。

そのSelfを具象型に置き換えた場合、たとえばAnyObject、動作します。ただし、現在(Xcode 6.3.1)は、コンパイル時にセグメンテーションエラーをトリガーします)。

3
Sulthan

この変更された例をプレイグラウンドで試すと、体系的にクラッシュします。

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Int { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
//extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }
}


var containers: [Container<MyProtocol>] = []

おそらく彼らはまだこれに取り組んでおり、物事は将来変わるかもしれません。とにかく今のところ、これについての私の説明は、プロトコルが具象型ではないということです。したがって、プロトコルに準拠したRAMのスペースがどれだけ必要になるかはわかりません(たとえば、IntDoubleと同じ量のRAMを占有しない場合があります)。したがって、ramでの配列の割り当ては非常に難しい問題になる可能性があります。 NSArrayを使用すると、ポインターの配列(NSObjectsへのポインター)が割り当てられ、それらはすべて同じ量のRAMを占有します。 NSArray具象型 "NSObjectへのポインター"の配列と考えることができます。したがって、ラム割り当ての計算に問題はありません。

ArrayDictionary in SwiftはGeneric Structではなく、オブジェクトへのポインタを含むオブジェクト Obj-Cと同様。

お役に立てれば。

1
Matteo Piombo