Cara meneruskan tipe kelas sebagai parameter fungsi

146

Saya memiliki fungsi generik yang memanggil layanan web dan membuat serialisasi respons JSON kembali ke objek.

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 */
}

Apa yang saya coba capai adalah setara dengan kode Java ini

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Apakah tanda tangan metode saya untuk apa yang saya coba selesaikan dengan benar?
  • Lebih khusus lagi, apakah menetapkan AnyClasssebagai parameter adalah hal yang benar untuk dilakukan?
  • Saat memanggil metode, saya meneruskan MyObject.selfsebagai nilai returnsClass, tapi saya mendapatkan kesalahan kompilasi "Tidak dapat mengonversi tipe ekspresi '()' untuk mengetikkan 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Edit:

Saya mencoba menggunakan object_getClass, seperti yang disebutkan oleh holex, tetapi sekarang saya mendapatkan:

galat: "Ketik 'CityInfo.Type' tidak sesuai dengan protokol 'AnyObject'"

Apa yang perlu dilakukan agar sesuai dengan protokol?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Jean-Francois Gagnon
sumber
Saya tidak berpikir bahwa obat generik Swifts bekerja seperti javas. dengan demikian sang penyerang tidak bisa sepintar itu. id hilangkan Kelas <T> Hal dan tentukan Tipe Generik secara jelasCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Jawaban:

144

Anda mendekatinya dengan cara yang salah: di Swift, tidak seperti Objective-C, kelas memiliki tipe tertentu dan bahkan memiliki hierarki warisan (yaitu, jika kelas Bmewarisi dari A, maka B.Typejuga mewarisi dari A.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

Itu sebabnya Anda tidak boleh menggunakan AnyClass, kecuali Anda benar-benar ingin mengizinkan kelas apa pun . Dalam hal ini tipe yang tepat adalah T.Type, karena itu menyatakan hubungan antara returningClassparameter dan parameter penutupan.

Bahkan, menggunakannya bukan AnyClassmemungkinkan kompiler untuk menyimpulkan dengan benar jenis-jenis dalam pemanggilan metode:

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())
}

Sekarang ada masalah membangun instance Tuntuk dilewatkan handler: jika Anda mencoba dan menjalankan kode sekarang kompiler akan mengeluh bahwa Titu tidak dapat dibangun dengan (). Dan memang seharusnya begitu: Tharus dibatasi secara eksplisit untuk mengharuskannya mengimplementasikan inisialisasi tertentu.

Ini dapat dilakukan dengan protokol seperti berikut:

protocol Initable {
    init()
}

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

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

Maka Anda hanya perlu mengubah batasan umum dari invokeServicedari <T>menjadi <T: Initable>.

Tip

Jika Anda mendapatkan kesalahan aneh seperti "Tidak dapat mengonversi tipe ekspresi '()' menjadi tipe 'String'", sering kali berguna untuk memindahkan setiap argumen pemanggilan metode ke variabelnya sendiri. Ini membantu mempersempit kode yang menyebabkan kesalahan dan mengungkap masalah jenis inferensi:

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

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

}

Sekarang ada dua kemungkinan: kesalahan berpindah ke salah satu variabel (yang berarti ada bagian yang salah di sana) atau Anda mendapatkan pesan samar seperti "Tidak dapat mengonversi jenis ekspresi ()ke jenis ($T6) -> ($T6) -> $T5".

Penyebab kesalahan terakhir adalah bahwa kompiler tidak dapat menyimpulkan jenis dari apa yang Anda tulis. Dalam hal ini masalahnya adalah yang Thanya digunakan dalam parameter penutupan dan penutupan yang Anda lewati tidak menunjukkan tipe tertentu sehingga kompiler tidak tahu tipe apa yang harus disimpulkan. Dengan mengubah tipe returningClassuntuk menyertakan TAnda memberi kompiler cara untuk menentukan parameter generik.

EliaCereda
sumber
Terima kasih atas petunjuk T.Type - hanya apa yang saya butuhkan untuk melewatkan tipe kelas sebagai argumen.
Echelon
"Tip" pada akhirnya menyelamatkan saya
Alex
Alih-alih menggunakan fungsi templated <T: Initiable>, juga dimungkinkan untuk melewatkan ReturnClass sebagai Initiable.Type
Devous
Dalam kode asli yang akan menghasilkan hasil yang berbeda. Parameter generik Tdigunakan untuk menyatakan hubungan antara objek returningClassdan yang diteruskan ke completionHandler. Jika Initiable.Typedigunakan hubungan ini hilang.
EliaCereda
DAN? Generik tidak diperbolehkan menulisfunc somefunc<U>()
Gargo
34

Anda bisa mendapatkan kelas AnyObjectmelalui cara ini:

Cepat 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

dan Anda dapat meneruskannya sebagai paramater nanti, jika Anda mau.

holex
sumber
1
Anda juga dapat melakukanself.dynamicType
berita baru
1
Setiap kali saya mencoba menggunakan myClass, hal itu menyebabkan kesalahan "penggunaan tipe yang tidak dideklarasikan" myClass ". Swift 3, versi Xcode dan iOS terbaru
Bingung
@Bingung, saya tidak melihat masalah dengan itu, Anda mungkin perlu memberi saya lebih banyak info tentang konteks.
holex
Saya sedang membuat tombol. Dalam kode tombol itu (itu SpriteKit, jadi di dalam touchesBegan) Saya ingin tombol memanggil fungsi Kelas, jadi perlu referensi ke kelas itu. Jadi di Kelas Tombol saya telah membuat variabel untuk menyimpan referensi ke Kelas. Tapi saya tidak bisa mendapatkannya untuk mengadakan Kelas, atau Jenis itu adalah Kelas itu, apa pun kata / terminologi yang tepat. Saya hanya bisa mendapatkannya untuk menyimpan referensi ke instance. Lebih disukai, saya ingin meneruskan referensi ke Tipe Kelas ke dalam tombol. Tapi saya baru saja mulai dengan membuat referensi internal, dan bahkan tidak bisa bekerja.
Bingung
Itu adalah kelanjutan dari metodologi dan pemikiran yang saya gunakan di sini: stackoverflow.com/questions/41092440/… . Sejak itu saya mendapatkan beberapa logika yang bekerja dengan Protokol, tetapi dengan cepat melawan fakta saya juga perlu mencari tahu jenis dan generik terkait untuk mendapatkan apa yang saya pikir bisa saya lakukan dengan mekanisme yang lebih sederhana. Atau cukup salin dan tempel banyak kode di sekitar. Itulah yang biasanya saya lakukan;)
Bingung
8

Saya memiliki kasus penggunaan serupa di 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
    }

}
johney
sumber
Saya mencoba untuk melakukan sesuatu yang mirip, tapi saya mendapatkan error di callsite yang: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Maukah Anda memperbarui jawaban Anda untuk memberikan contoh panggilan loadItem?
Barry Jones
Ternyata ini adalah titik di mana saya harus mempelajari perbedaan antara .Typedan .self(tipe dan metatype).
Barry Jones
0

Gunakan obj-getclass:

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

}

Anggap diri adalah objek info kota.

Batalkan
sumber
0

Cepat 5

Situasi yang tidak persis sama, tetapi saya mengalami masalah yang sama. Yang akhirnya membantu saya adalah ini:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

Kemudian Anda bisa menyebutnya di dalam instance dari kelas kata seperti ini:

myFunction(type(of: self))

Semoga ini bisa membantu seseorang dalam situasi yang sama dengan saya.

Merricat
sumber