Swift soutient-il la réflexion?

113

Swift soutient-il la réflexion? Existe-t-il quelque chose comme valueForKeyPath:et setValue:forKeyPath:pour les objets Swift?

En fait, a-t-il même un système de type dynamique, quelque chose comme obj.classen Objective-C?

Khanh Nguyen
la source
1
J'ai créé une classe d'aide à la réflexion dans Swift. Vous pouvez le trouver sur: github.com/evermeer/EVReflection
Edwin Vermeer
2
Ils ont supprimé le reflet dans Swift 2.0. Voici comment
j'énumère les

Réponses:

85

On dirait qu'il y a le début d'un support de réflexion:

class Fruit {
    var name="Apple"
}

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

De mchambers gist, ici: https://gist.github.com/mchambers/fb9da554898dae3e54f2

stevex
la source
5
Eh bien, je ne considérerais pas cela comme une vraie réflexion. D'une part, c'est en lecture seule. Il me semble que ce n'est qu'un hack pour activer le débogage dans Xcode. Le protocole Mirrorcite le mot IDEplusieurs fois.
Sulthan
7
Et cela ne fonctionne que pour les propriétés. Aucune réflexion sur la méthode.
Sulthan
11
Auteur de l'enregistrement essentiel. J'ai écrit ceci dans le laboratoire Swift de la WWDC, j'ai pensé que je partagerais le reste. Comme tout le monde l'a compris, les ingénieurs avec lesquels j'ai parlé ont confirmé que la fonction refléter () existe pour prendre en charge le Playground. Mais vous pouvez toujours vous amuser avec :) ici, j'ai piraté un petit sérialiseur de modèle en l'utilisant. Collez-le dans le Playground et amusez-vous: gist.github.com/mchambers/67640d9c3e2bcffbb1e2
Marc Chambers
1
Jetez un œil à la réponse stackoverflow.com/a/25345461/292145 pour savoir comment _stdlib_getTypeNamepeut vous aider.
Klaas
1
Voici une classe qui reflètera les classes de base et les options (pas les types) et prend en charge NSCoding et l'analyse depuis et vers un dictionnaire: github.com/evermeer/EVCloudKitDao/blob/master/AppMessage/…
Edwin Vermeer
44

Si une classe s'étend NSObject, alors toute l'introspection et le dynamisme d'Objective-C fonctionnent. Ceci comprend:

  • La possibilité d'interroger une classe sur ses méthodes et propriétés, et d'appeler des méthodes ou de définir des propriétés.
  • La possibilité d'échanger des implémentations de méthodes. (ajouter des fonctionnalités à toutes les instances).
  • La possibilité de générer et d'attribuer une nouvelle sous-classe à la volée. (ajouter des fonctionnalités à une instance donnée)

Un inconvénient de cette fonctionnalité est la prise en charge des types de valeur optionnels Swift. Par exemple, les propriétés Int peuvent être énumérées et modifiées mais Int? les propriétés ne peuvent pas. Les types facultatifs peuvent être énumérés partiellement à l'aide de Reflect / MirrorType, mais toujours pas modifiés.

Si une classe ne s'étend pas NSObject, alors seule la nouvelle réflexion très limitée (et en cours?) Fonctionne (voir refléter / MirrorType), ce qui ajoute une capacité limitée à demander à une instance sa classe et ses propriétés, mais aucune des fonctionnalités supplémentaires ci-dessus .

Lorsqu'il n'étend pas NSObject ou n'utilise pas la directive '@objc', Swift utilise par défaut une distribution basée sur statique et vtable. Ceci est plus rapide, cependant, en l'absence d'une machine virtuelle ne permet pas l'interception de méthode d'exécution. Cette interception est une partie fondamentale de Cocoa et est requise pour les types de fonctionnalités suivants:

  • Les observateurs de la propriété élégante de Cocoa. (Les observateurs de propriété sont directement intégrés à la langue Swift).
  • Application non invasive de préoccupations transversales telles que la journalisation, la gestion des transactions (c'est-à-dire la programmation orientée aspect).
  • Proxys, transfert de messages, etc.

Par conséquent, il est recommandé que les classes dans les applications Cocoa / CocoaTouch implémentées avec Swift:

  • Extension de NSObject. La nouvelle boîte de dialogue de classe dans Xcode va dans cette direction.
  • Lorsque la surcharge d'une répartition dynamique entraîne des problèmes de performances, la répartition statique peut être utilisée - dans des boucles serrées avec des appels à des méthodes avec de très petits corps, par exemple.

Résumé:

  • Swift peut se comporter comme C ++, avec une répartition statique / vtable rapide et une réflexion limitée. Cela le rend adapté aux applications de niveau inférieur ou à haute performance, mais sans la complexité, la courbe d'apprentissage ou le risque d'erreur associé au C ++.
  • Alors que Swift est un langage compilé, le style de messagerie de l'invocation de méthode ajoute l'introspection et le dynamisme des langages modernes comme Ruby et Python, tout comme Objective-C, mais sans la syntaxe héritée d'Objective-C.

Données de référence: surcharge d'exécution pour les appels de méthode:

  • statique: <1,1 ns
  • vtable: ~ 1.1ns
  • dynamique: ~ 4,9 ns

(les performances réelles dépendent du matériel, mais les ratios resteront similaires).

De plus, l'attribut dynamic nous permet d'indiquer explicitement à Swift qu'une méthode doit utiliser la répartition dynamique et prendra donc en charge l'interception.

public dynamic func foobar() -> AnyObject {
}
Jasper Blues
la source
2
Même en utilisant les techniques Objective-C, il semble ne pas fonctionner pour les types Swift optionnels. Je suggérerais de noter cette limitation dans la réponse à moins que je ne rate une astuce.
whitneyland
8

La documentation parle d'un système de type dynamique, principalement

Type et dynamicType

Voir Type de métatype (dans Référence du langage)

Exemple:

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

var type = instance.dynamicType

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

En supposant maintenant TestObjects'étendNSObject

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

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

Actuellement, aucune réflexion n'est mise en œuvre.

EDIT: J'avais apparemment tort, voir la réponse de stevex. Il existe une réflexion simple en lecture seule pour les propriétés intégrées, probablement pour permettre aux IDE d'inspecter le contenu des objets.

Sulthan
la source
6

Il semble qu'une API de réflexion Swift ne soit pas une priorité pour Apple pour le moment. Mais en plus de la réponse @stevex, il existe une autre fonction dans la bibliothèque standard qui aide.

À partir de la version bêta 6, _stdlib_getTypeNameobtient le nom de type mutilé d'une variable. Collez ceci dans un terrain de jeu vide:

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

La sortie est:

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

L'entrée de blog d'Ewan Swick aide à déchiffrer ces chaînes:

par exemple _TtSireprésente le Inttype interne de Swift .

Mike Ash a une excellente entrée de blog sur le même sujet .

Klaas
la source
@aleclarson Oui, c'est aussi très utile.
Klaas le
1
ne sont pas ces API privées? Apple approuvera-t-il l'application si elle est utilisée?
Eduardo Costa
@EduardoCosta oui, bien sûr. Ils sont privés. Je les utilise uniquement pour les versions de débogage.
Klaas
Voici un lien mis à jour vers l'article du blog d'Ewan Swick: eswick.com/2014/06/08/Inside-Swift
RenniePet
5

Vous voudrez peut-être envisager d'utiliser toString () à la place. Il est public et fonctionne de la même manière que _stdlib_getTypeName () à la différence qu'il fonctionne également sur AnyClass , par exemple dans un Playground, entrez

class MyClass {}

toString(MyClass.self) // evaluates to "__lldb_expr_49.MyClass"
soie
la source
1

Aucun reflectmot-clé dans Swift 5, vous pouvez maintenant utiliser

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
la source
Pourquoi ce n'est pas voté? Ceci est très utile. Je vais l'appliquer pour la jsondésérialisation
javadba