Introspeksi & generik kelas cepat

121

Saya mencoba untuk secara dinamis membuat classtipe berbasis contoh menggunakan obat generik, namun saya mengalami kesulitan dengan introspeksi kelas.

Inilah pertanyaannya:

  • Apakah ada Swift yang setara dengan Obj-C self.class?
  • Apakah ada cara untuk membuat instance kelas menggunakan AnyClasshasil dari NSClassFromString?
  • Apakah ada cara untuk mendapatkan AnyClassatau mengetik informasi secara ketat dari parameter generik T? (Mirip dengan typeof(T)sintaks C # )
Erik
sumber
2
stackoverflow.com/a/24069875/292145 memberikan beberapa petunjuk tentang API refleksi Swift.
Klaas
5
Objective-C self.classakan menjadi self.dynamicType.selfdalam keyakinan Swift I
Filip Hermans
1
Dalam metode instance, berikut cara memanggil metode kelas:self.dynamicType.foo()

Jawaban:

109

Yah, untuk satu, padanan Swift [NSString class]adalah .self(lihat dokumen Metatype , meskipun mereka cukup tipis).

Bahkan, NSString.classtidak berhasil! Anda harus menggunakan NSString.self.

let s = NSString.self
var str = s()
str = "asdf"

Begitu pula dengan kelas yang sigap saya coba ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… kesalahannya mengatakan:

Eksekusi Playground gagal: error:: 16: 1: error: membuat objek dengan tipe kelas 'X' dengan nilai metatype memerlukan penginisialisasi '@required'

 Y().me()
 ^
 <REPL>:3:7: note: selected implicit initializer with type '()'
 class X {
       ^

Butuh beberapa saat bagi saya untuk memikirkan apa artinya ini… ternyata kelas itu ingin memiliki a @required init()

class X {
    func me() {
        println("asdf")
    }

    required init () {

    }
}

let Y = X.self

// prints "asdf"
Y().me()

Beberapa dokumen menyebut ini sebagai .Type, tetapi MyClass.Typememberi saya kesalahan di taman bermain.

Jiaaro
sumber
1
Terima kasih atas tautan Anda ke dokumen Metatype! Saya benar-benar mengabaikan aspek Tipe itu, doh!
Erik
14
Anda dapat menggunakan .Typeatau .Protocoldalam deklarasi variabel, misalnyalet myObject: MyObject.Type = MyObject.self
Sulthan
1
Sulthan: jadi MyObject.Type adalah sebuah deklarasi tetapi MyObject.self adalah metode pabrik (dapat disebut) dan myObject adalah variabel yang berisi referensi ke metode pabrik. Panggilan myObject () akan menghasilkan instance kelas MyObject. Akan lebih baik contoh jika nama variabel myObject adalah myObjectFactory?
bootchk
2
@sebelum requiredharus dihapus
fujianjin6471
49

Berikut cara menggunakannya NSClassFromString. Anda harus mengetahui superclass tentang apa yang akan Anda hasilkan. Berikut adalah pasangan superclass-subclass yang tahu bagaimana mendeskripsikan diri mereka sendiri println:

@objc(Zilk) class Zilk : NSObject {
    override var description : String {return "I am a Zilk"}
}

@objc(Zork) class Zork : Zilk {
    override var description : String {return "I am a Zork"}
}

Perhatikan penggunaan @objsintaks khusus untuk mendikte nama Objective-C munged dari kelas-kelas ini; itu penting, karena jika tidak kita tidak tahu string munged yang menunjukkan setiap kelas.

Sekarang kita bisa gunakan NSClassFromStringuntuk membuat kelas Zork atau kelas Zilk, karena kita tahu kita bisa mengetiknya sebagai NSObject dan tidak crash nanti:

let aClass = NSClassFromString("Zork") as NSObject.Type
let anObject = aClass()
println(anObject) // "I am a Zork"

Dan itu bisa dibalik; println(NSStringFromClass(anObject.dynamicType))juga bekerja.


Versi modern:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }
Matt
sumber
10
Beri suara positif untuk @objc(ClassName)bit tersebut. Saya tahu tentang @objcatributnya tetapi tidak Anda juga bisa memberikan petunjuk tentang nama kelas.
Erik
1
Solusi luar biasa yang masih berfungsi kurang lebih seperti yang ditulis 6 tahun kemudian. Hanya beberapa perubahan kecil yang diminta taman bermain: as! NSObject.Typedi baris pertama dan aClass.init()yang kedua
Kaji
13

Jika saya membaca dokumentasi dengan benar, jika Anda berurusan dengan instance dan misalnya ingin mengembalikan instance baru dari Type yang sama daripada objek yang telah Anda berikan dan Type dapat dibangun dengan init () Anda dapat melakukan:

let typeOfObject = aGivenObject.dynamicType
var freshInstance = typeOfObject()

Saya segera mengujinya dengan String:

let someType = "Fooo".dynamicType
let emptyString = someType()
let threeString = someType("Three")

yang bekerja dengan baik.

monyet
sumber
1
Ya, dynamicTypebekerja seperti yang saya harapkan di sana. Namun, saya tidak dapat membandingkan tipe. Penggunaan yang sangat besar adalah dengan obat generik, jadi saya bisa memiliki sesuatu seperti Generic<T>dan di dalamnya if T is Double {...}. Tampaknya hal itu tidak mungkin terjadi.
Erik
1
@SiLo Apakah Anda pernah menemukan cara untuk menanyakan secara umum apakah dua objek berada dalam kelas yang sama?
matt
1
@matt Tidak elegan, tidak, saya tidak. Namun, saya dapat membuat Defaultableprotokol yang bekerja mirip dengan defaultkata kunci C # , dan ekstensi yang sesuai untuk jenis seperti Stringdan Int. Dengan menambahkan batasan umum dari T:Defaultable, saya dapat memeriksa apakah argumen tersebut berhasil is T.default().
Erik
1
@Pintar_ Saya ingin melihat kode itu! Saya menganggap ini mengatasi batasan aneh pada penggunaan "adalah". Saya telah melaporkan bug pada batasan tersebut, dan juga kurangnya introspeksi kelas secara umum. Saya akhirnya membandingkan string menggunakan NSStringFromClass, tetapi tentu saja itu hanya berfungsi untuk keturunan NSObject.
matt
1
@matt Sayangnya kedengarannya lebih pintar daripada yang sebenarnya karena Anda masih harus melakukan value is String.default()... dll, yang akhirnya akan Anda lakukan value is String.
Erik
13

Dalam 3 cepat

object.dynamicType

sudah ditinggalkan.

Sebagai gantinya gunakan:

type(of:object)
J. beenie
sumber
7

Implementasi cepat untuk membandingkan tipe

protocol Decoratable{}
class A:Decoratable{}
class B:Decoratable{}
let object:AnyObject = A()
object.dynamicType is A.Type//true
object.dynamicType is B.Type//false
object.dynamicType is Decoratable.Type//true

CATATAN: Perhatikan bahwa itu juga bekerja dengan protokol objek mungkin atau mungkin tidak diperluas

eonist
sumber
1

Akhirnya ada sesuatu untuk dikerjakan. Agak malas tetapi bahkan rute NSClassFromString () tidak berfungsi untuk saya ...

import Foundation

var classMap = Dictionary<String, AnyObject>()

func mapClass(name: String, constructor: AnyObject) -> ()
{
    classMap[name] = constructor;
}

class Factory
{
    class func create(className: String) -> AnyObject?
    {
        var something : AnyObject?

        var template : FactoryObject? = classMap[className] as? FactoryObject

        if (template)
        {
            let somethingElse : FactoryObject = template!.dynamicType()

            return somethingElse
        }

        return nil
    }
}


 import ObjectiveC

 class FactoryObject : NSObject
{
    @required init() {}
//...
}

class Foo : FactoryObject
{
    class override func initialize()
    {
        mapClass("LocalData", LocalData())
    }
    init () { super.init() }
}

var makeFoo : AnyObject? = Factory.create("Foo")

dan bingo, "makeFoo" berisi instance Foo.

Kelemahannya adalah kelas Anda harus keluar dari FactoryObject dan mereka HARUS memiliki metode inisialisasi Obj-C + sehingga kelas Anda secara otomatis dimasukkan ke dalam peta kelas dengan fungsi global "mapClass".

Martin-Gilles Lavoie
sumber
1

Berikut adalah contoh lain yang menunjukkan implementasi hierarki kelas, mirip dengan jawaban yang diterima, diperbarui untuk rilis pertama Swift.

class NamedItem : NSObject {
    func display() {
        println("display")
    }

    required override init() {
        super.init()
        println("base")
    }
}

class File : NamedItem {
    required init() {
        super.init()
        println("folder")
    }
}

class Folder : NamedItem {
    required init() {
        super.init()
        println("file")
    }
}

let y = Folder.self
y().display()
let z = File.self
z().display()

Cetak hasil ini:

base
file
display
base
folder
display
possen
sumber
2
Teknik ini tidak bekerja dengan benar jika variabelnya adalah Jenis kelas super. Misalnya, diberikan var x: NamedItem.Type, jika saya menetapkannya x = Folder.Type, maka x()mengembalikan baru NamedItem, bukan Folder. Ini membuat teknik tidak berguna untuk banyak aplikasi. Saya menganggap ini bug .
phatmann
1
Sebenarnya Anda dapat melakukan apa yang saya pikir Anda inginkan menggunakan teknik ini stackoverflow.com/questions/26290469/…
possen