Introspecção e genéricos de classe rápida

121

Estou tentando criar dinamicamente um classtipo baseado em instância usando genéricos, mas estou tendo dificuldades com a introspecção de classe.

Aqui estão as perguntas:

  • Existe um equivalente Swift aos Obj-C self.class?
  • Existe uma maneira de instanciar uma classe usando o AnyClassresultado NSClassFromString?
  • Existe uma maneira de obter AnyClassou digitar informações estritamente de um parâmetro genérico T? (Semelhante à typeof(T)sintaxe do C # )
Erik
fonte
2
stackoverflow.com/a/24069875/292145 fornece algumas dicas sobre a API de reflexão Swift.
Klaas
5
De Objective-C self.classse tornaria self.dynamicType.selfem Swift I crença
Filip Hermans
1
Em um método de instância, self.dynamicType.foo()

Respostas:

109

Bem, por um lado, o equivalente Swift de [NSString class]é .self(consulte os documentos do Metatype , embora eles sejam muito finos).

De fato, NSString.classnem funciona! Você tem que usar NSString.self.

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

Da mesma forma, com uma classe rápida, tentei ...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

Hmm… o erro diz:

Falha na execução do playground: erro:: 16: 1: erro: a construção de um objeto do tipo de classe 'X' com um valor de metatipo requer um inicializador '@required'

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

Levei um tempo para entender o que isso significa ... Acontece que ele quer que a classe tenha um @required init()

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

    required init () {

    }
}

let Y = X.self

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

Alguns dos documentos se referem a isso como .Type, mas MyClass.Typeme dão um erro no playground.

Jiaaro
fonte
1
Obrigado pelo seu link para os documentos do Metatype! Eu negligenciei totalmente esse aspecto de Tipos, doh!
Erik
14
Você pode usar .Typeou .Protocolna declaração de variáveis, por exemplo:let myObject: MyObject.Type = MyObject.self
Sulthan
1
Sulthan: então MyObject.Type é uma declaração, mas MyObject.self é um método de fábrica (pode ser chamado) e myObject é uma variável que contém uma referência a um método de fábrica. A chamada myObject () produziria uma instância da classe MyObject. Seria um exemplo melhor se o nome da variável myObject fosse myObjectFactory?
bootchk
2
@antes requireddeve ser excluído
fujianjin6471 22/08
49

Aqui está como usar NSClassFromString. Você precisa conhecer a superclasse do que vai acabar. Aqui estão um par de superclasse-subclasse que sabe como se descrever para 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"}
}

Observe o uso da @objsintaxe especial para ditar o nome munged Objective-C dessas classes; isso é crucial, porque, caso contrário, não conhecemos a string munged que designa cada classe.

Agora podemos usar NSClassFromStringpara criar a classe Zork ou Zilk, porque sabemos que podemos digitá-lo como um NSObject e não travar mais tarde:

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

E é reversível; println(NSStringFromClass(anObject.dynamicType))também funciona.


Versão 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
    }
mate
fonte
10
Voto positivo para um @objc(ClassName)pouco. Eu sabia sobre o @objcatributo, mas não que você também pudesse dar uma dica sobre o nome da classe.
Erik
1
Excelente solução que ainda funciona mais ou menos como está escrito 6 anos depois. Apenas alguns ajustes menores que o playground pediu: as! NSObject.Typena primeira linha e aClass.init()na segunda
Kaji
13

Se estou lendo a documentação corretamente, se você lida com instâncias e, por exemplo, deseja retornar uma nova instância do mesmo tipo que o objeto que você recebeu e o tipo pode ser construído com um init (), você pode:

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

Eu rapidamente testei com String:

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

que funcionou bem.

monkeydom
fonte
1
Sim, dynamicTypefunciona como eu esperava lá. No entanto, não consegui comparar tipos. O grande uso real é com genéricos, para que eu possa ter algo parecido Generic<T>e interno if T is Double {...}. Parece que isso não é possível, infelizmente.
Erik
1
@SiLo Você já encontrou uma maneira de perguntar em geral se dois objetos são da mesma classe?
Matt
1
@matt Não elegantemente, não, eu não fiz. No entanto, consegui criar um Defaultableprotocolo que funcione de maneira semelhante à defaultpalavra-chave do C # e extensões apropriadas para tipos como Stringe Int. Adicionando a restrição genérica de T:Defaultable, eu poderia verificar se o argumento foi aprovado is T.default().
Erik
1
@SiLo Clever; Eu gostaria de ver esse código! Entendo que isso contorna as estranhas limitações no uso de "is". Eu registrei um bug nessas limitações e também na falta geral de introspecção de classe. Acabei comparando seqüências de caracteres usando NSStringFromClass, mas é claro que isso só funciona para descendentes de NSObject.
18118 matt-
1
@matt Infelizmente, parece mais inteligente do que realmente é, porque você ainda precisa fazer value is String.default()... etc, o que você acabaria fazendo value is String.
Erik
13

Em rápido 3

object.dynamicType

está obsoleto.

Em vez disso, use:

type(of:object)
J.beenie
fonte
7

Implementação rápida de tipos de comparação

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: Observe que ele também funciona com protocolos em que o objeto pode ou não estender

eonista
fonte
1

Finalmente tenho algo para trabalhar. É um pouco preguiçoso, mas mesmo a rota NSClassFromString () não funcionou para mim ...

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 o bingo, "makeFoo" contém uma instância do Foo.

A desvantagem é que suas classes devem derivar do FactoryObject e DEVEM ter o método de inicialização do Obj-C + para que sua classe seja inserida automaticamente no mapa da classe pela função global "mapClass".

Martin-Gilles Lavoie
fonte
1

Aqui está outro exemplo que mostra a implementação da hierarquia de classes, semelhante à resposta aceita, atualizada para a primeira versão do 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()

Imprime este resultado:

base
file
display
base
folder
display
possuir
fonte
2
Essa técnica não funciona corretamente se a variável for o tipo da superclasse. Por exemplo, dado que var x: NamedItem.Type, se eu atribuí-lo x = Folder.Type, x()retorna um novo NamedItem, não um Folder. Isso torna a técnica inútil para muitas aplicações. Eu considero isso um bug .
Phatmann
1
Na verdade, você pode fazer o que eu acho que você deseja usando esta técnica stackoverflow.com/questions/26290469/…
possen