O Swift suporta reflexão?

113

O Swift suporta reflexão? por exemplo, existe algo como valueForKeyPath:e setValue:forKeyPath:para objetos Swift?

Na verdade, ele ainda tem um sistema de tipo dinâmico, algo como obj.classem Objective-C?

Khanh Nguyen
fonte
1
Criei uma classe auxiliar para reflexão em Swift. Você pode encontrá-lo em: github.com/evermeer/EVReflection
Edwin Vermeer
2
Eles removeram o reflexo no Swift 2.0. Isto é como eu estou enumerando atributos e valores Fazer a ligação
Mohacs

Respostas:

85

Parece que há o início de algum suporte de reflexão:

class Fruit {
    var name="Apple"
}

reflect(Fruit()).count         // 1
reflect(Fruit())[0].0          // "name"
reflect(Fruit())[0].1.summary  // "Apple"

Do mchambers gist, aqui: https://gist.github.com/mchambers/fb9da554898dae3e54f2

Stevex
fonte
5
Bem, eu não consideraria isso um reflexo real. Por um lado, é somente leitura. Parece-me que é apenas um hack para habilitar a depuração no Xcode. MirrorNa verdade, o protocolo cita a palavra IDEvárias vezes.
Sulthan
7
E funciona apenas para propriedades. Sem reflexão de método.
Sulthan
11
Autor da essência, verificando. Escrevi isso no laboratório Swift da WWDC e decidi compartilhar o resto. Como todos descobriram, os engenheiros com quem falei confirmaram que a função reflect () existe para dar suporte ao Playground. Mas você ainda pode se divertir com ele :) aqui eu criei um pequeno serializador de modelo usando-o. Cole-o no Playground e divirta-se: gist.github.com/mchambers/67640d9c3e2bcffbb1e2
Marc Chambers
1
Dê uma olhada na resposta stackoverflow.com/a/25345461/292145 para descobrir como _stdlib_getTypeNamepode ajudar.
Klaas de
1
Aqui está uma classe que fará reflexão de classes base e opcionais (não tipos) e tem suporte para NSCoding e análise de e para um dicionário: github.com/evermeer/EVCloudKitDao/blob/master/AppMessage/…
Edwin Vermeer
44

Se uma classe se estende NSObject, então toda a introspecção e dinamismo de Objective-C funcionam. Isso inclui:

  • A capacidade de perguntar a uma classe sobre seus métodos e propriedades e de invocar métodos ou definir propriedades.
  • A capacidade de trocar implementações de métodos. (adicionar funcionalidade a todas as instâncias).
  • A capacidade de gerar e atribuir uma nova subclasse rapidamente. (adicionar funcionalidade a uma determinada instância)

Uma lacuna dessa funcionalidade é o suporte para tipos de valor opcionais Swift. Por exemplo, as propriedades Int podem ser enumeradas e modificadas, mas Int? propriedades não podem. Tipos opcionais podem ser enumerados parcialmente usando reflect / MirrorType, mas ainda não modificados.

Se uma classe não se estende NSObject, então apenas a nova reflexão muito limitada (e em andamento?) Funciona (consulte reflect / MirrorType), o que adiciona capacidade limitada de perguntar a uma instância sobre sua classe e propriedades, mas nenhum dos recursos adicionais acima .

Quando não está estendendo NSObject, ou usando a diretiva '@objc', o padrão do Swift é o envio estático e baseado em vtable. Isso é mais rápido, no entanto, na ausência de uma máquina virtual, não permite a interceptação do método de tempo de execução. Essa interceptação é uma parte fundamental do Cocoa e é necessária para os seguintes tipos de recursos:

  • Os elegantes observadores de propriedades do Cocoa. (Os observadores de propriedade são integrados à linguagem Swift).
  • Aplicação não invasiva de questões transversais como registro, gerenciamento de transações (ou seja, Programação Orientada a Aspectos).
  • Proxies, encaminhamento de mensagens, etc.

Portanto, é recomendado que clases em aplicativos Cocoa / CocoaTouch implementados com Swift:

  • Estenda de NSObject. A nova caixa de diálogo de classe no Xcode orienta nessa direção.
  • Onde a sobrecarga de um despacho dinâmico leva a problemas de desempenho, o despacho estático pode ser usado - em loops estreitos com chamadas a métodos com corpos muito pequenos, por exemplo.

Resumo:

  • O Swift pode se comportar como C ++, com despacho estático / vtable rápido e reflexão limitada. Isso o torna adequado para aplicativos de nível inferior ou de alto desempenho, mas sem a complexidade, a curva de aprendizado ou o risco de erro associado ao C ++
  • Embora Swift seja uma linguagem compilada, o estilo de mensagem de invocação de método adiciona a introspecção e o dinamismo encontrados em linguagens modernas como Ruby e Python, assim como Objective-C, mas sem a sintaxe legada de Objective-C.

Dados de referência: sobrecarga de execução para invocações de método:

  • estático: <1,1ns
  • vtable: ~ 1.1ns
  • dinâmico: ~ 4,9 ns

(o desempenho real depende do hardware, mas as relações permanecerão semelhantes).

Além disso, o atributo dinâmico nos permite instruir explicitamente ao Swift que um método deve usar despacho dinâmico e, portanto, oferecerá suporte à interceptação.

public dynamic func foobar() -> AnyObject {
}
Jasper Blues
fonte
2
Mesmo usando técnicas Objective-C, parece não funcionar para tipos Swift opcionais. Eu sugeriria observar essa limitação na resposta, a menos que esteja faltando um truque.
Whitneyland
8

A documentação fala sobre um sistema de tipo dinâmico, principalmente sobre

Type e dynamicType

Consulte Tipo de Metatipo (na Referência da Linguagem)

Exemplo:

var clazz = TestObject.self
var instance: TestObject = clazz()

var type = instance.dynamicType

println("Type: \(type)") //Unfortunately this prints only "Type: Metatype"

Agora, supondo que TestObjectestendeNSObject

var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!") //prints "yes!"
}

Atualmente, não há reflexão implementada.

EDIT: Eu estava aparentemente errado, veja a resposta de stevex. Há uma reflexão simples somente leitura para propriedades incorporadas, provavelmente para permitir que os IDEs inspecionem o conteúdo do objeto.

Sulthan
fonte
6

Parece que uma API de reflexão Swift não é uma alta prioridade para a Apple no momento. Mas além da resposta de @stevex, há outra função na biblioteca padrão que ajuda.

A partir do beta 6 _stdlib_getTypeNameobtém o nome de tipo mutilado de uma variável. Cole isso em um playground vazio:

import Foundation

class PureSwiftClass {
}

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")

O resultado é:

TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

A entrada do blog de Ewan Swick ajuda a decifrar essas strings:

por exemplo, _TtSisignifica o Inttipo interno do Swift .

Mike Ash tem um ótimo post no blog que cobre o mesmo tópico .

Klaas
fonte
@aleclarson Sim, isso também é bastante útil.
Klaas
1
não são API privadas? A Apple aprovará o aplicativo se ele for usado?
Eduardo Costa
@EduardoCosta sim, com certeza. Eles são privados. Eu só os uso para compilações de depuração.
Klaas
Aqui está um link atualizado para o artigo do blog de Ewan Swick: eswick.com/2014/06/08/Inside-Swift
RenniePet
5

Você pode querer considerar o uso de toString () em vez disso. É público e funciona da mesma forma que _stdlib_getTypeName () com a diferença de que também funciona em AnyClass , por exemplo, em um Playground.

class MyClass {}

toString(MyClass.self) // evaluates to "__lldb_expr_49.MyClass"
entrada de seda
fonte
1

Nenhuma reflectpalavra-chave no Swift 5, agora você pode usar

struct Person {
    var name="name"
    var age = 15
}

var me = Person()
var mirror = Mirror(reflecting: me)

for case let (label?, value) in mirror.children {
    print (label, value)
}

Jacky
fonte
Por que isso não foi votado? Isso é muito útil. Vou aplicá-lo para jsondesserialização
javadba