web-dev-qa-db-ja.com

Protocol.Typeを汎用T.Typeパラメータに渡せないのはなぜですか?

私はSwinjectを使っていましたが、問題が発生します。私はほぼ一日中これに行き詰まっています。これはSwiftが静的に型付けされた言語であるためと思われますが、完全にはわかりません。

私の問題をこの遊び場でまとめました

protocol Protocol {}

class Class: Protocol {}

let test: Protocol.Type = Class.self

func printType(confromingClassType: Protocol.Type) {
    print(confromingClassType)
}

func printType<Service>(serviceType: Service.Type) {
    print(serviceType)
}

print(Class.self) // "Class"
printType(serviceType: Class.self) // "Class"
print(test) // "Class"
printType(confromingClassType: test) // "Class"

printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'"

Test.selfやtype(of:test)などのさまざまなソリューションを試しましたが、どれも機能しません。

だから、変数として提供されたジェネリックパラメーターで関数を呼び出すことはできないと思いますか?

19
Guillaume L.

_P.Type_対_P.Protocol_

プロトコルのメタタイプには2種類あります。一部のプロトコルPおよび準拠するタイプCの場合:

  • _P.Protocol_は、プロトコル自体のタイプを示します(保持できる値は_P.self_のみです)。
  • _P.Type_は、プロトコルに準拠する具象型を示します。 _C.self_の値を保持できますが、not_P.self_ プロトコルが自分自身に準拠していないため (ただし、1つの例外があります) Anyトップタイプ であるため、このルールはAnyです。したがって、メタタイプ値は_Any.Type_として入力できます。_Any.self_を含む)。

あなたが直面している問題は、特定の汎用プレースホルダーTに対して、TがプロトコルPの場合、_T.Type_はであるということです。 not_P.Type_ – _P.Protocol_です。

したがって、例に戻ると、

_protocol P {}
class C : P {}

func printType<T>(serviceType: T.Type) {
    print(serviceType)
}

let test: P.Type = C.self

// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
_

printType(serviceType:)の引数としてtestを渡すことはできません。どうして? testは_P.Type_なので、また、Tの代わりに、_serviceType:_パラメータに_P.Type_を指定させることはできません。

PTに置き換えた場合、パラメーターは_P.Protocol_を取ります。

_printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
_

Tのように、Cコンクリート型に置き換えた場合、パラメーターは_C.Type_を取ります。

_printType(serviceType: C.self) // C.self is of type C.Type
_

プロトコル拡張機能のハッキング

さて、Tconcrete型に置き換えることができれば、_C.Type_を関数に渡すことができることを学びました。 _P.Type_がラップする動的な型で置き換えることはできますか?残念ながら、これには opening existentials と呼ばれる言語機能が必要です。これは現在、ユーザーが直接使用することはできません。

ただし、Swiftdoesは、プロトコル型のインスタンスまたはメタタイプのメンバーにアクセスするときに、存在を暗黙的に開きます(つまり、ランタイム型を掘り出し、汎用的なプレースホルダーの形でアクセスできます。プロトコル拡張でこの事実を利用できます。

_protocol P {}
class C : P {}

func printType<T>(serviceType: T.Type) {
  print("T.self = \(T.self)")
  print("serviceType = \(serviceType)")
}

extension P {
  static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
    printType(serviceType: self)
  }
}

let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
_

ここではかなりのことが行われているので、少し解凍してみましょう。

  • Pの拡張メンバーcallPrintType()には、Selfに制限された暗黙的な汎用プレースホルダーPがあります。暗黙のselfパラメーターは、このプレースホルダーを使用して入力されます。

  • _P.Type_でcallPrintType()を呼び出すと、Swiftは_P.Type_がラップする動的タイプを暗黙的に掘り出します(これは存在の始まりです) 、それを使用してSelfプレースホルダーを満たします。次に、この動的メタタイプを暗黙のselfパラメータに渡します。

  • したがって、SelfCによって満たされ、printTypeの汎用プレースホルダーTに転送できます。


_T.Type_のときに_P.Type_が_T == P_ではないのはなぜですか?

汎用プレースホルダーPの代わりにTを使用しないため、上記の回避策がどのように機能するかがわかります。しかし、プロトコルタイプPTに置き換えると、_T.Type_でない_P.Type_になるのはなぜですか?

まあ、考慮してください:

_func foo<T>(_: T.Type) {
    let t: T.Type = T.self
    print(t)
}
_

PTに置き換えた場合はどうなりますか? _T.Type_が_P.Type_の場合、次のようになります。

_func foo(_: P.Type) {
    // Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
    let p: P.Type = P.self
    print(p)
}
_

これは違法です。 _P.self_ではなく_P.Type_型であるため、_P.Protocol_を_P.Type_に割り当てることはできません。

したがって、結果として、Pに準拠するany具象型を説明するメタタイプを取る関数パラメーターが必要な場合は、(1つの特定の具象適合型ではなく) )–ジェネリックではなく、_P.Type_パラメータのみが必要です。ジェネリックスは、異種の型をモデル化しません。それがプロトコル型の目的です。

そして、それはまさにあなたがprintType(conformingClassType:)で持っているものです:

_func printType(conformingClassType: P.Type) {
    print(conformingClassType)
}

printType(conformingClassType: test) // okay
_

_P.Type_型のパラメーターがあるため、testを渡すことができます。ただし、これは_P.self_型ではないため、_P.Type_を渡すことができないことを意味します。

_// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self) 
_
30
Hamish

私はあなたのコードを遊び場で実行しました、そしてそれがそれがコンパイルしない理由です

let test: Protocol.Type = Class.self

testの型宣言を削除すると、コードが機能し、Class.Type15行に出力されます。

したがって、次のコードはコンパイルして実行されます。

protocol Protocol {}

class Class: Protocol {}

let test = Class.self

func printType<Service>(serviceType: Service.Type) {
    print(serviceType)
}

print(Class.Type.self) // "Class.Type"
printType(serviceType: Class.Type.self) // "Class.Type"
print(type(of: test)) // "Class.Type"

printType(serviceType: type(of: test)) // "Class.Type"

これが問題の解決に役立つことを願っています。


編集

元のコードでプレイグラウンドで発生しているエラーは次のとおりです。

Playground execution failed: error: Untitled Page 2.xcplaygroundpage:9:1: error: cannot invoke 'printType' with an argument list of type '(serviceType: Protocol.Type.Type)'
printType(serviceType: type(of: test)) // "Class.Type"

これは、Typeを2回呼び出すことを意味します。これが、コードがコンパイルされない理由です。これは、タイプProtocol.Typeの引数でメソッドを既に呼び出しているためです。

メソッドのシグネチャを次のように変更すると、

テストを許可:Protocol.Type = Class.self

func printType<Service>(serviceType: Service) {
    print(serviceType)
}

すべてがコンパイルされて正しく動作し、Class.typeを出力します

test.Typeを一度しか呼び出せないため、これが私の最初のバージョンの回答をコンパイルする理由でもあります。

1
Catalina T.