プロトコルに準拠したオブジェクトを返す関数を作成したいのですが、プロトコルはtypealias
を使用します。次のおもちゃの例を考えてみましょう:
_protocol HasAwesomeness {
typealias ReturnType
func hasAwesomeness() -> ReturnType
}
extension String: HasAwesomeness {
func hasAwesomeness() -> String {
return "Sure Does!"
}
}
extension Int: HasAwesomeness {
func hasAwesomeness() -> Bool {
return false
}
}
_
String
とInt
はHasAwesomeness
に準拠するように拡張されており、それぞれがhasAwesomeness()
メソッドを実装して異なるタイプを返します。
ここで、HasAwesomeness
プロトコルに準拠するオブジェクトを返すクラスを作成します。クラスが何であるかは気にせず、メッセージhasAwesomenss()
を送信できます。以下を実行すると、コンパイルエラーが発生します。
_class AmazingClass: NSObject {
func returnsSomethingWithAwesomeness(key: String) -> HasAwesomeness {
...
}
}
_
エラー:プロトコル 'HasAwesomeness'は、Selfまたは関連する型の要件があるため、一般的な制約としてのみ使用できます
ご想像のとおり、returnsSomethingWithAwesomeness
の目的は、String
パラメータに基づいてInt
またはkey
を返すことです。コンパイラーがスローするエラーは、なぜそれが許可されないのか理解できますが、構文を修正するための洞察を提供します。
_func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
...
}
_
よし、私の読みはメソッドreturnsSomethingWithAwesomeness
は、サブタイプT
を持つすべての型HasAwesomness
を返す汎用メソッドです。ただし、次の実装では、より多くのコンパイル時の型エラーがスローされます。
_func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
if key == "foo" {
return "Amazing Foo"
}
else {
return 42
}
}
_
エラー:タイプ「T」はプロトコル「StringLiteralConvertible」に準拠していません
エラー:タイプ「T」はプロトコル「IntegerLiteralConvertible」に準拠していません
よし、今は行き詰まっている。誰かが型とジェネリックについての私の理解のギャップを埋めるのを手伝ってくれませんか?
ここで何が起こっているのかを理解するための鍵は、実行時に動的に決定されるものとコンパイル時に静的に決定されるものを区別することだと思います。 Javaのようなほとんどの言語では、プロトコル(またはインターフェイス)はすべて実行時でポリモーフィックな動作を取得することを目的としていますが、Swiftでは、関連する型を持つプロトコルを使用してポリモーフィックな動作を取得します。 at コンパイル時。
例のT
のような一般的なプレースホルダーが表示される場合は常に、このT
に入力される型はコンパイル時に決定されます。したがって、あなたの例では:
func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
returnsSomethingWithAwesomeness
は、T
がT
に準拠している限り、あらゆるタイプのHasAwesomeness
を操作できる関数です。
ただし、T
に入力する内容は、returnsSomethingWithAwesomeness
が呼び出された時点で決定されます– Swiftは、呼び出しサイトのすべての情報を調べて、タイプを決定しますT
is is、and replace all T
placeholders that that type。*
したがって、呼び出しサイトでの選択がT
がString
であると仮定すると、returnsSomethingWithAwesomeness
は、プレースホルダーT
のすべての出現で書き換えられると考えることができます。 String
に置き換えられました:
// giving the type of s here fixes T as a String
let s: String = returnsSomethingWithAwesomeness("bar")
func returnsSomethingWithAwesomeness(key: String) -> String {
if key == "foo" {
return "Amazing Foo"
}
else {
return 42
}
}
T
はString
に置き換えられ、notはHasAwesomeness
のタイプに置き換えられます。 HasAwesomeness
は、制約としてのみ使用されています。つまり、T
が取り得る型を制限しています。
このように見ると、return 42
のelse
は意味がありません。文字列を返す関数から42を返すにはどうすればよいでしょうか。
returnsSomethingWithAwesomeness
がT
が最終的に何であっても機能することを確認するには、Swiftは、指定された制約から利用できることが保証されている関数のみを使用するように制限します。この場合、T
について知っているのは、それがHasAwesomeness
に準拠しているということだけです。これは、任意のreturnsSomethingWithAwesomeness
でT
メソッドを呼び出すことができること、または型をHasAwesomeness
に制約する別の関数で使用するか、型T
の変数を別の変数に割り当てます(すべての型が割り当てをサポートします)、そしてつまりそれ 。
他のTと比較することはできません(サポートされる保証はありません==
)。あなたは新しいものを構築することはできません(T
が適切な初期化メソッドを持っているかどうか誰が知っていますか?)。また、文字列または整数リテラルから作成することはできません(T
がStringLiteralConvertible
またはIntegerLiteralConvertible
に準拠する必要があるため、必ずしもそうではありません–したがってこれらの種類のリテラルのいずれかを使用して型を作成しようとすると、これらの2つのエラーが発生します)。
すべてプロトコルに準拠するジェネリック型を返すジェネリック関数を作成することができます。ただし、返されるのはプロトコルではなく特定のタイプなので、どのタイプが動的に決定されることはありません。例えば:
func returnCollectionContainingOne<C: ExtensibleCollectionType where C.Generator.Element == Int>() -> C {
// this is allowed because the ExtensibleCollectionType procol
// requires the type implement an init() that takes no parameters
var result = C()
// and it also defines an `append` function that allows you to do this:
result.append(1)
// note, the reason it was possible to give a "1" as the argument to
// append was because of the "where C.Generator.Element == Int" part
// of the generic placeholder constraint
return result
}
// now you can use returnCollectionContainingOne with arrays:
let a: [Int] = returnCollectionContainingOne()
// or with ContiguousArrays:
let b: ContiguousArray = returnCollectionContainingOne()
このコードのreturnCollectionContainingOne
は、実際には2つの関数であり、1つはContiguousArray
用に実装され、もう1つはArray
用であり、呼び出したときにコンパイラによって自動的に書き込まれます(そしてしたがって、C
を特定のタイプに修正できます)。プロトコルを返す1つの関数ではなく、2つの異なる型を返す2つの関数。したがって、returnsSomethingWithAwesomeness
が実行時に動的引数に基づいてString
またはInt
を返すことができないのと同じように、returnCollectionContainingOne
は、配列または連続した配列を返しました。それが返すことができるのはT
だけであり、コンパイル時にT
が実際に何であれ、コンパイラーによって埋めることができます。
*これは、コンパイラが実際に行うことを少し単純化しすぎていますが、この説明のために行います。