Schnelle Selbstbeobachtung und Generika


121

Ich versuche, classmithilfe von Generika dynamisch einen instanzbasierten Typ zu erstellen , habe jedoch Schwierigkeiten mit der Klassenintrospektion.

Hier sind die Fragen:

  • Gibt es ein Swift-Äquivalent zu Obj-C self.class?
  • Gibt es eine Möglichkeit, eine Klasse anhand des AnyClassErgebnisses von zu instanziieren NSClassFromString?
  • Gibt es eine Möglichkeit, AnyClassInformationen ausschließlich aus einem generischen Parameter abzurufen oder auf andere Weise einzugeben T? (Ähnlich der C # typeof(T)-Syntax)

2
stackoverflow.com/a/24069875/292145 gibt einige Hinweise zur Swift Reflection API.
Klaas

5
Objective-C's self.classwürden self.dynamicType.selfin Swift I Glauben werden
Filip Hermans

1
In einer Instanzmethode ist hier, wie man eine Klassenmethode self.dynamicType.foo()

Antworten:


109

Nun, zum einen, die Swift Äquivalent [NSString class]heißt .self(siehe Metatyp docs , obwohl sie ziemlich dünn sind).

In der Tat NSString.classfunktioniert nicht einmal! Du musst benutzen NSString.self.

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

Ebenso habe ich mit einer schnellen Klasse versucht ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm ... der Fehler sagt:

Die Ausführung des Spielplatzes ist fehlgeschlagen: Fehler :: 16: 1: Fehler: Für die Erstellung eines Objekts vom Klassentyp 'X' mit einem Metatypwert ist ein Initialisierer '@required' erforderlich

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

Ich habe eine Weile gebraucht, um herauszufinden, was das bedeutet. Es stellt sich heraus, dass die Klasse eine haben soll @required init()

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

    required init () {

    }
}

let Y = X.self

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

Einige der Dokumente beziehen sich darauf .Type, MyClass.Typegeben mir aber einen Fehler auf dem Spielplatz.


1
Vielen Dank für Ihren Link zu den Metatype-Dokumenten! Ich habe diesen Aspekt der Typen völlig übersehen, doh!
Erik

14
Sie können .Typeoder .Protocolin Variablendeklaration verwenden, zBlet myObject: MyObject.Type = MyObject.self
Sulthan

1
Sulthan: MyObject.Type ist also eine Deklaration, aber MyObject.self ist eine Factory-Methode (kann aufgerufen werden) und myObject ist eine Variable, die einen Verweis auf eine Factory-Methode enthält. Der Aufruf myObject () würde eine Instanz der Klasse MyObject erzeugen. Es wäre ein besseres Beispiel, wenn der Name der Variablen myObject myObjectFactory wäre?
Bootchk

2
@vorher requiredsollte gelöscht werden
fujianjin6471

49

Hier erfahren Sie, wie Sie es verwenden NSClassFromString. Sie müssen die Oberklasse dessen kennen, womit Sie enden werden. Hier ist ein Paar aus Oberklasse und Unterklasse, das sich selbst beschreiben kann 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"}
}

Beachten Sie die Verwendung der speziellen @objSyntax, um den Objective-C-Munged-Namen dieser Klassen zu bestimmen. Das ist entscheidend, weil wir sonst die Munged-Zeichenfolge, die jede Klasse kennzeichnet, nicht kennen.

Jetzt können wir NSClassFromStringdie Zork-Klasse oder die Zilk-Klasse erstellen, da wir wissen, dass wir sie als NSObject eingeben und später nicht abstürzen können:

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

Und es ist reversibel; println(NSStringFromClass(anObject.dynamicType))funktioniert auch.


Moderne Version:

    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 für das @objc(ClassName)bisschen. Ich wusste über das @objcAttribut Bescheid , aber nicht, dass Sie auch einen Hinweis auf den Klassennamen geben könnten.
Erik

1
Hervorragende Lösung, die nach 6 Jahren noch mehr oder weniger funktioniert. Nur ein paar kleine Änderungen, nach denen der Spielplatz gefragt hat: as! NSObject.Typein der ersten Zeile und aClass.init()in der zweiten
Kaji

13

Wenn ich die Dokumentation richtig lese, wenn Sie sich mit Instanzen befassen und z. B. eine neue Instanz desselben Typs als das Objekt zurückgeben möchten, das Sie erhalten haben, und der Typ mit einem init () erstellt werden kann, können Sie Folgendes tun:

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

Ich habe es schnell mit String getestet:

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

das hat gut funktioniert.


1
Ja, dynamicTypefunktioniert wie ich dort erwartet hatte. Ich konnte jedoch keine Typen vergleichen. Der wirklich große Nutzen liegt bei Generika, also könnte ich so etwas wie Generic<T>und drinnen haben if T is Double {...}. Es scheint, dass dies nicht unglücklicherweise möglich ist.
Erik

1
@SiLo Haben Sie jemals eine Möglichkeit gefunden, allgemein zu fragen, ob zwei Objekte derselben Klasse angehören?
Matt

1
@matt Nicht elegant, nein habe ich nicht. Ich konnte jedoch ein DefaultableProtokoll erstellen , das ähnlich wie das defaultSchlüsselwort von C # funktioniert , und geeignete Erweiterungen für Typen wie Stringund Int. Durch Hinzufügen der generischen Einschränkung von T:Defaultablekonnte ich überprüfen, ob das Argument übergeben wurde is T.default().
Erik

1
@ SiLo Clever; Ich würde diesen Code gerne sehen! Ich gehe davon aus, dass dies die seltsamen Einschränkungen bei der Verwendung von "is" umgeht. Ich habe einen Fehler in Bezug auf diese Einschränkungen und auch in Bezug auf den allgemeinen Mangel an Klassenintrospektion gemeldet. Am Ende habe ich Zeichenfolgen mit NSStringFromClass verglichen, aber das funktioniert natürlich nur für NSObject-Nachkommen.
Matt

1
@matt Leider klingt es klüger als es wirklich ist, weil du immer noch value is String.default()... etc tun musst, was du value is Stringstattdessen einfach tun würdest .
Erik

13

In schneller 3

object.dynamicType

ist veraltet.

Verwenden Sie stattdessen:

type(of:object)

7

Schnelle Implementierung von Vergleichstypen

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

HINWEIS: Beachten Sie, dass es auch mit Protokollen funktioniert, die das Objekt möglicherweise erweitert oder nicht


1

Endlich etwas zum Arbeiten. Es ist ein bisschen faul, aber selbst die NSClassFromString () -Route hat bei mir nicht funktioniert ...

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

und Bingo, "makeFoo" enthält eine Foo-Instanz.

Der Nachteil ist, dass Ihre Klassen von FactoryObject abgeleitet sein müssen und die Obj-C + -Initialisierungsmethode haben MÜSSEN, damit Ihre Klasse von der globalen Funktion "mapClass" automatisch in die Klassenzuordnung eingefügt wird.


1

Hier ist ein weiteres Beispiel, das die Implementierung der Klassenhierarchie zeigt, ähnlich der akzeptierten Antwort, die für die erste Version von Swift aktualisiert wurde.

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

Druckt dieses Ergebnis:

base
file
display
base
folder
display

2
Diese Technik funktioniert nicht richtig, wenn die Variable der Typ der Oberklasse ist. var x: NamedItem.TypeWenn ich es beispielsweise zuweise x = Folder.Type, wird x()ein neues zurückgegeben NamedItem, kein a Folder. Dies macht die Technik für viele Anwendungen unbrauchbar. Ich halte das für einen Fehler .
Phatmann

1
Tatsächlich können Sie mit dieser Technik tun, was ich denke, dass Sie wollen. Stackoverflow.com/questions/26290469/…
possen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.