さまざまな型のジェネリッククラスの配列で遊んでいます。問題をサンプルコードで説明するのが最も簡単です。
// 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
を取得するより良い方法はありますか?または、私が見落とした何かがありますか?
あなたが望むことをする方法-ある種の-がある。プロトコルを使用して、型の制限を解消し、必要な結果を得る方法がありますが、それは必ずしもきれいではありません。あなたの状況でプロトコルとして私が思いついたものは次のとおりです。
_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
はもはやジェネリックではないので、Array
sの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
に適合するさまざまなタイプを混在させることさえできます。したがって、次のようにString
にMyProtocol
拡張子を付けると:
_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
オブジェクトの配列を反復処理できます。
これは、「何をしたのか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でこれを行うことになります。)実際に解決している問題は何ですか?
これは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)は、コンパイル時にセグメンテーションエラーをトリガーします)。
この変更された例をプレイグラウンドで試すと、体系的にクラッシュします。
// 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のスペースがどれだけ必要になるかはわかりません(たとえば、Int
はDouble
と同じ量のRAMを占有しない場合があります)。したがって、ramでの配列の割り当ては非常に難しい問題になる可能性があります。 NSArray
を使用すると、ポインターの配列(NSObjects
へのポインター)が割り当てられ、それらはすべて同じ量のRAMを占有します。 NSArray
は具象型 "NSObject
へのポインター"の配列と考えることができます。したがって、ラム割り当ての計算に問題はありません。
Array
とDictionary
in SwiftはGeneric Structではなく、オブジェクトへのポインタを含むオブジェクト Obj-Cと同様。
お役に立てれば。