web-dev-qa-db-ja.com

クラスタイプを関数パラメーターとして渡す方法

Webサービスを呼び出し、JSON応答をシリアル化してオブジェクトに戻す汎用関数があります。

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

私が達成しようとしているのは、このJavaコードと同等です

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • 私が達成しようとしているもののメソッドシグネチャは正しいですか?
  • より具体的には、AnyClassをパラメータータイプとして指定するのが正しいことですか?
  • メソッドを呼び出すとき、returningClassの値としてMyObject.selfを渡しますが、コンパイルを取得しますエラー「式の型 '()'を型 'String'に変換できません」
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

編集:

Holexで述べたようにobject_getClassを使用してみましたが、次のようになりました:

エラー:「タイプ「CityInfo.Type」はプロトコル「AnyObject」に準拠していません」

プロトコルに準拠するには何をする必要がありますか?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
131

あなたは間違った方法でそれに近づいています:Swiftでは、Objective-Cとは異なり、クラスには特定のタイプがあり、継承階層さえあります(つまり、クラスBAから継承する場合、B.TypeA.Typeも継承します):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

anyクラスを本当に許可しない限り、AnyClassを使用しないでください。この場合、returningClassパラメーターとクロージャーのパラメーター間のリンクを表すため、正しい型はT.Typeになります。

実際、AnyClassの代わりにこれを使用すると、コンパイラーはメソッド呼び出しで型を正しく推測できます。

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

現在、Tのインスタンスを作成してhandlerに渡す問題があります。今すぐコードを実行しようとすると、コンパイラはT()で作成できないと文句を言います。そして当然のことながら、Tは、特定の初期化子を実装することを要求するために明示的に制約されなければなりません。

これは、次のようなプロトコルで実行できます。

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

次に、invokeServiceの汎用制約を<T>から<T: Initable>に変更するだけです。

先端

「式の型 '()'を型 'String'に変換できません」などの奇妙なエラーが発生した場合、メソッド呼び出しのすべての引数を独自の変数に移動すると便利です。エラーの原因となっているコードを絞り込み、型推論の問題を明らかにするのに役立ちます。

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

現在、2つの可能性があります。エラーが変数の1つに移動する(間違った部分があることを意味する)か、「式のタイプ()をタイプ($T6) -> ($T6) -> $T5に変換できません」のような不可解なメッセージを受け取ります。

後者のエラーの原因は、コンパイラが記述したタイプを推測できないことです。この場合、問題はTがクロージャーのパラメーターでのみ使用され、渡されたクロージャーは特定の型を示していないため、コンパイラはどの型を推論するのかを知らないということです。 returningClassのタイプを変更してTを含めることにより、コンパイラーに汎用パラメーターを決定する方法を提供します。

128
EliaCereda

この方法でAnyObjectのクラスを取得できます。

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

必要に応じて、後でパラメータとして渡すことができます。

32
holex

私はSwift5で同様のユースケースを持っています:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

2
johney

obj-getclass を使用します。

CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}

自分が都市情報オブジェクトであると仮定します。

0
Undo