Szybka introspekcja i typy ogólne

121

Próbuję dynamicznie tworzyć classtypy oparte na instancjach przy użyciu typów ogólnych, jednak napotykam trudności z introspekcją klas.

Oto pytania:

  • Czy istnieje odpowiednik języka Swift dla Obj-C self.class?
  • Czy istnieje sposób na utworzenie wystąpienia klasy przy użyciu AnyClasswyniku z NSClassFromString?
  • Czy istnieje sposób, aby uzyskać AnyClasslub w inny sposób wpisać informacje wyłącznie z parametru ogólnego T? (Podobna do typeof(T)składni C # )
Erik
źródło
2
stackoverflow.com/a/24069875/292145 zawiera kilka wskazówek dotyczących interfejsu API Swift reflection.
Klaas,
5
Cele-C self.classstałyby się self.dynamicType.selfw Swift I
Filip Hermans
1
W przypadku metody instancji, oto jak wywołać metodę klasową:self.dynamicType.foo()

Odpowiedzi:

109

Cóż, po pierwsze, Swift odpowiednikiem [NSString class]jest .self(zobacz dokumentację Metatype , chociaż są dość cienkie).

W rzeczywistości NSString.classnawet nie działa! Musisz użyć NSString.self.

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

Podobnie z szybką klasą próbowałem ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… błąd mówi:

Wykonanie placu zabaw nie powiodło się: błąd:: 16: 1: błąd: skonstruowanie obiektu klasy typu „X” z wartością metatypu wymaga inicjatora „@required”

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

Zajęło mi trochę czasu, zanim zrozumiałem, co to znaczy… okazuje się, że chce, aby klasa miała @required init()

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

    required init () {

    }
}

let Y = X.self

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

Niektóre dokumenty odnoszą się do tego jako .Type, ale wyświetla MyClass.Typemi błąd na placu zabaw.

Jiaaro
źródło
1
Dziękuję za link do dokumentów Metatype! Całkowicie przeoczyłem ten aspekt typów, doh!
Erik,
14
Możesz użyć .Typelub .Protocolw deklaracji zmiennej, np.let myObject: MyObject.Type = MyObject.self
Sulthan
1
Sulthan: tak więc MyObject.Type jest deklaracją, ale MyObject.self jest metodą fabryczną (można ją wywołać), a myObject jest zmienną zawierającą odniesienie do metody fabrycznej. Wywołanie myObject () spowodowałoby powstanie instancji klasy MyObject. Byłby lepszy przykład, gdyby nazwa zmiennej myObject brzmiała myObjectFactory?
bootchk
2
@wcześniej requirednależy usunąć
fujianjin6471
49

Oto jak używać NSClassFromString. Musisz znać superklasę tego, z czym skończysz. Oto para nadklasa-podklasa, która wie, jak się opisać dla 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"}
}

Zwróć uwagę na użycie specjalnej @objskładni, która dyktuje nazwę tych klas zmienioną przez Objective-C; jest to kluczowe, ponieważ w przeciwnym razie nie znamy połączonego łańcucha, który wyznacza każdą klasę.

Teraz możemy użyć NSClassFromStringdo stworzenia klasy Zork lub Zilk, ponieważ wiemy, że możemy wpisać go jako obiekt NSO i nie ulegnie awarii później:

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

I to jest odwracalne; println(NSStringFromClass(anObject.dynamicType))również działa.


Nowoczesna wersja:

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }
matowe
źródło
10
Głosuj za @objc(ClassName)trochę. Wiedziałem o @objcatrybucie, ale nie wiedziałem, że możesz też podać wskazówkę dotyczącą nazwy klasy.
Erik
1
Doskonałe rozwiązanie, które nadal działa mniej więcej tak, jak napisano 6 lat dalej. Tylko kilka drobnych poprawek na placu zabaw: as! NSObject.Typew pierwszej linii iw aClass.init()drugiej
Kaji
13

Jeśli dobrze czytam dokumentację, jeśli masz do czynienia z instancjami i np. Chcesz zwrócić nową instancję tego samego typu niż obiekt, który otrzymałeś, a Type można skonstruować za pomocą init (), możesz zrobić:

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

Szybko przetestowałem to ze Stringiem:

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

który działał dobrze.

małpa
źródło
1
Tak, dynamicTypedziała tak , jak się spodziewałem. Jednak nie udało mi się porównać typów. Naprawdę duże zastosowanie ma generyczne, więc mógłbym mieć coś podobnego Generic<T>i wewnątrz if T is Double {...}. Wydaje się, że nie jest to możliwe niestety.
Erik,
1
@SiLo Czy kiedykolwiek znalazłeś sposób, aby ogólnie zapytać, czy dwa obiekty są tej samej klasy?
mat.
1
@matt Nie elegancko, nie, nie. Udało mi się jednak stworzyć Defaultableprotokół, który działa podobnie do defaultsłowa kluczowego C # i odpowiednie rozszerzenia dla typów takich jak Stringi Int. Dodając ogólne ograniczenie T:Defaultable, mogłem sprawdzić, czy argument został przekazany is T.default().
Erik
1
@SiLo Clever; Chciałbym zobaczyć ten kod! Rozumiem, że pozwala to obejść dziwne ograniczenia dotyczące używania słowa „jest”. Zgłosiłem błąd dotyczący tych ograniczeń, a także ogólnego braku introspekcji klasowej. Skończyło się na porównaniu ciągów za pomocą NSStringFromClass, ale oczywiście działa to tylko dla potomków NSObject.
mat.
1
@matt Niestety brzmi to sprytniej niż w rzeczywistości, ponieważ nadal musisz zrobić value is String.default()... itd., co po prostu skończyłbyś value is Stringzamiast tego.
Erik,
13

W swift 3

object.dynamicType

jest przestarzałe.

Zamiast tego użyj:

type(of:object)
J.beenie
źródło
7

Szybka implementacja porównywania typów

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

UWAGA: Zauważ, że działa również z protokołami, których obiekt może, ale nie musi, rozszerzać się

eonista
źródło
1

Wreszcie mam coś do roboty. Jest trochę leniwy, ale nawet trasa NSClassFromString () nie działała dla mnie ...

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

i bingo, „makeFoo” zawiera instancję Foo.

Wadą jest to, że twoje klasy muszą pochodzić z FactoryObject i MUSZĄ mieć metodę inicjalizacji Obj-C +, aby Twoja klasa została automatycznie wstawiona do mapy klas przez globalną funkcję "mapClass".

Martin-Gilles Lavoie
źródło
1

Oto kolejny przykład pokazujący implementację hierarchii klas, podobną do zaakceptowanej odpowiedzi, zaktualizowaną dla pierwszej wersji 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()

Drukuje ten wynik:

base
file
display
base
folder
display
possen
źródło
2
Ta technika nie działa poprawnie, jeśli zmienna jest Typem nadklasy. Na przykład, var x: NamedItem.Typejeśli przypiszę x = Folder.Type, to x()zwraca nowy NamedItem, a nie Folder. To sprawia, że ​​technika jest bezużyteczna w wielu zastosowaniach. Uważam to za błąd .
phatmann
1
Właściwie możesz robić, co myślę, że chcesz, używając tej techniki stackoverflow.com/questions/26290469/ ...
możliwe