Introspezione e generici di classe rapida


121

Sto cercando di creare dinamicamente un classtipo basato su istanze utilizzando generici, tuttavia riscontro difficoltà con l'introspezione della classe.

Ecco le domande:

  • Esiste un equivalente Swift di Obj-C self.class?
  • C'è un modo per istanziare una classe usando il AnyClassrisultato di NSClassFromString?
  • C'è un modo per ottenere AnyClasso altrimenti digitare le informazioni rigorosamente da un parametro generico T? (Simile alla typeof(T)sintassi di C # )

2
stackoverflow.com/a/24069875/292145 fornisce alcuni suggerimenti sull'API di riflessione Swift.
Klaas

5
L'obiettivo-C self.classsarebbe diventato, self.dynamicType.selfsecondo Swift I
Filip Hermans il

1
In un metodo di istanza, ecco come chiamare un metodo di classe:self.dynamicType.foo()

Risposte:


109

Bene, per uno, l'equivalente Swift di [NSString class]è .self(vedi i documenti Metatype , anche se sono piuttosto sottili).

In effetti, NSString.classnon funziona nemmeno! Devi usare NSString.self.

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

Allo stesso modo, con una lezione rapida ho provato ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm ... l'errore dice:

Esecuzione del parco giochi non riuscita: errore:: 16: 1: errore: la costruzione di un oggetto di tipo classe "X" con un valore di metatipo richiede un inizializzatore "@required"

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

Mi ci è voluto un po 'per capire cosa significa ... si scopre che vuole che la classe abbia un @required init()

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

    required init () {

    }
}

let Y = X.self

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

Alcuni documenti si riferiscono a questo come .Type, ma MyClass.Typemi danno un errore nel playground.


1
Grazie per il tuo collegamento ai documenti Metatype! Ho completamente trascurato quell'aspetto dei tipi, doh!
Erik

14
È possibile utilizzare .Typeo .Protocolin una dichiarazione di variabile, ad esempiolet myObject: MyObject.Type = MyObject.self
Sulthan

1
Sulthan: quindi MyObject.Type è una dichiarazione ma MyObject.self è un metodo factory (può essere chiamato) e myObject è una variabile contenente un riferimento a un metodo factory. La chiamata myObject () produrrebbe un'istanza della classe MyObject. Sarebbe un esempio migliore se il nome della variabile myObject fosse myObjectFactory?
bootchk

2
@prima requireddovrebbe essere cancellato
fujianjin6471

49

Ecco come si usa NSClassFromString. Devi conoscere la superclasse con cui finirai. Ecco una coppia superclasse-sottoclasse che sa come descrivere se stessa per 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"}
}

Notare l'uso della @objsintassi speciale per dettare il nome munged Objective-C di queste classi; questo è cruciale, perché altrimenti non conosciamo la stringa mungata che designa ogni classe.

Ora possiamo usare NSClassFromStringper creare la classe Zork o la classe Zilk, perché sappiamo che possiamo digitarla come NSObject e non andare in crash in seguito:

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

Ed è reversibile; println(NSStringFromClass(anObject.dynamicType))funziona anche.


Versione moderna:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }

10
Upvote per il @objc(ClassName)bit. Sapevo @objcdell'attributo ma non che tu potessi dare anche un suggerimento sul nome della classe.
Erik

1
Ottima soluzione che funziona ancora più o meno come scritto dopo 6 anni. Solo un paio di piccoli ritocchi richiesti dal playground: as! NSObject.Typenella prima riga e aClass.init()nella seconda
Kaji

13

Se sto leggendo correttamente la documentazione, se ti occupi di istanze e ad esempio vuoi restituire una nuova istanza dello stesso tipo dell'oggetto che ti è stato dato e il tipo può essere costruito con un init () puoi fare:

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

L'ho testato rapidamente con String:

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

che ha funzionato bene.


1
Sì, dynamicTypefunziona come mi aspettavo lì. Tuttavia, non sono stato in grado di confrontare i tipi. Il vero grande uso è con i generici, quindi potrei avere qualcosa di simile Generic<T>e dentro if T is Double {...}. Sembra che non sia possibile sfortunatamente.
Erik

1
@SiLo Hai mai trovato un modo per chiedere in generale se due oggetti sono della stessa classe?
matt

1
@matt Non elegantemente, no non l'ho fatto. Tuttavia, sono stato in grado di creare un Defaultableprotocollo che funziona in modo simile alla defaultparola chiave di C # e estensioni appropriate per tipi come Stringe Int. Aggiungendo il vincolo generico di T:Defaultable, potrei controllare se l'argomento è passato is T.default().
Erik

1
@SiLo Clever; Mi piacerebbe vedere quel codice! Presumo che questo aggiri le strane limitazioni sull'uso di "è". Ho segnalato un bug su queste limitazioni e anche sulla generale mancanza di introspezione di classe. Ho finito per confrontare le stringhe usando NSStringFromClass, ma ovviamente funziona solo per i discendenti di NSObject.
matt

1
@matt Sfortunatamente suona più intelligente di quanto non sia in realtà perché devi ancora fare value is String.default()... ecc., Che value is Stringinvece finiresti per fare .
Erik

13

In swift 3

object.dynamicType

è deprecato.

Usa invece:

type(of:object)

7

Rapida implementazione del confronto dei tipi

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

NOTA: si noti che funziona anche con i protocolli che l'oggetto può estendere o meno


1

Finalmente ho qualcosa su cui lavorare. È un po 'pigro ma anche il percorso NSClassFromString () non ha funzionato per me ...

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")

e bingo, "makeFoo" contiene un'istanza Foo.

Lo svantaggio è che le tue classi devono allontanarsi da FactoryObject e DEVONO avere il metodo di inizializzazione Obj-C + in modo che la tua classe venga automaticamente inserita nella mappa delle classi dalla funzione globale "mapClass".


1

Ecco un altro esempio che mostra l'implementazione della gerarchia di classi, simile alla risposta accettata, aggiornata per la prima versione di 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()

Stampa questo risultato:

base
file
display
base
folder
display

2
Questa tecnica non funziona correttamente se la variabile è il Tipo della superclasse. Ad esempio, dato var x: NamedItem.Type, se lo assegno x = Folder.Type, x()restituisce un nuovo NamedItem, non un Folder. Ciò rende la tecnica inutile per molte applicazioni. Lo considero un bug .
phatmann

1
In realtà si può fare quello che pensa che si desidera utilizzare questa tecnica stackoverflow.com/questions/26290469/...
Possen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.