Propriété en lecture seule calculée vs fonction dans Swift

98

Dans la session Introduction à Swift WWDC, une propriété en lecture seule descriptionest démontrée:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description)

Y a-t-il des implications à choisir l'approche ci-dessus plutôt que d'utiliser une méthode à la place:

class Vehicle {
    var numberOfWheels = 0
    func description() -> String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description())

Il me semble que les raisons les plus évidentes pour lesquelles vous choisiriez une propriété calculée en lecture seule sont:

  • Sémantique - dans cet exemple, il est logique descriptiond'être une propriété de la classe plutôt qu'une action qu'elle effectue.
  • Brevity / Clarté - évite d'avoir à utiliser des parenthèses vides lors de l'obtention de la valeur.

Il est clair que l'exemple ci-dessus est trop simple, mais y a-t-il d'autres bonnes raisons de choisir l'un plutôt que l'autre? Par exemple, y a-t-il des caractéristiques de fonctions ou de propriétés qui vous guideraient dans votre décision d'utiliser?


NB À première vue, cela semble être une question OOP assez courante, mais je suis impatient de connaître toutes les fonctionnalités spécifiques à Swift qui guideraient les meilleures pratiques lors de l'utilisation de ce langage.

Stuart
la source
1
Regardez la session 204 - "Quand ne pas utiliser @property" Il a quelques conseils
Kostiantyn Koval
4
attendez, vous pouvez créer une propriété en lecture seule et ignorer le get {}? Je ne savais pas ça, merci!
Dan Rosenstark
WWDC14 Session 204 peut être trouvée ici (vidéo et diapositives), developer.apple.com/videos/play/wwdc2014/204
user3207158

Réponses:

53

Il me semble que c'est surtout une question de style: je préfère fortement utiliser des propriétés pour cela: propriétés; ce qui signifie des valeurs simples que vous pouvez obtenir et / ou définir. J'utilise des fonctions (ou des méthodes) lorsque le travail est en cours. Peut-être que quelque chose doit être calculé ou lu à partir du disque ou d'une base de données: dans ce cas, j'utilise une fonction, même si seule une valeur simple est renvoyée. De cette façon, je peux facilement voir si un appel est bon marché (propriétés) ou éventuellement cher (fonctions).

Nous obtiendrons probablement plus de clarté lorsque Apple publiera certaines conventions de codage Swift.

Johannes Fahrenkrug
la source
12

Eh bien, vous pouvez appliquer les conseils de Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

Dans certains cas, les fonctions sans arguments peuvent être interchangeables avec des propriétés en lecture seule. Bien que la sémantique soit similaire, il existe certaines conventions stylistiques sur le moment de préférer l'un à l'autre.

Préférez une propriété à une fonction lorsque l'algorithme sous-jacent:

  • ne jette pas
  • la complexité est peu coûteuse à calculer (ou corrigée lors de la première exécution)
  • renvoie le même résultat sur les appels
onmyway133
la source
1
La suggestion «a un O (1)» n'est plus incluse dans cet avis.
David Pettigrew
Édité pour refléter les changements de Kotlin.
Carsten Hagemann
11

Bien qu'une question de propriétés calculées par rapport aux méthodes en général soit difficile et subjective, il existe actuellement un argument important dans le cas de Swift pour préférer les méthodes aux propriétés. Vous pouvez utiliser des méthodes dans Swift comme des fonctions pures, ce qui n'est pas vrai pour les propriétés (à partir de Swift 2.0 beta). Cela rend les méthodes beaucoup plus puissantes et utiles puisqu'elles peuvent participer à la composition fonctionnelle.

func fflat<A, R>(f: (A) -> () -> (R)) -> (A) -> (R) {
    return { f($0)() }
}

func fnot<A>(f: (A) -> Bool) -> (A) -> (Bool) {
    return { !f($0) }
}

extension String {
    func isEmptyAsFunc() -> Bool {
        return isEmpty
    }
}

let strings = ["Hello", "", "world"]

strings.filter(fnot(fflat(String.isEmptyAsFunc)))
Max O
la source
1
strings.filter {! $ (0) .isEmpty} - renvoie le même résultat. Il s'agit d'un exemple modifié de la documentation Apple sur Array.filter (). Et c'est beaucoup plus facile à comprendre.
poGUIst
7

Le runtime étant le même, cette question s'applique également à Objective-C. Je dirais, avec les propriétés que vous obtenez

  • une possibilité d'ajouter un setter dans une sous-classe, rendant la propriété readwrite
  • une capacité à utiliser KVO / didSetpour les notifications de changement
  • plus généralement, vous pouvez passer la propriété aux méthodes qui attendent des chemins de clé, par exemple le tri des requêtes de récupération

Quant à quelque chose de spécifique à Swift, le seul exemple que j'ai est que vous pouvez l'utiliser @lazypour une propriété.

ilya n.
la source
7

Il y a une différence: si vous utilisez une propriété, vous pouvez éventuellement la remplacer et la faire lire / écrire dans une sous-classe.

Fichier analogique
la source
9
Vous pouvez également remplacer des fonctions. Ou ajoutez un setter pour fournir une capacité d'écriture.
Johannes Fahrenkrug
Vous pouvez ajouter un setter ou définir une propriété stockée lorsque la classe de base a défini le nom en tant que fonction? Vous pouvez sûrement le faire s'il définit une propriété (c'est exactement ce que je veux dire), mais je ne pense pas que vous puissiez le faire s'il définissait une fonction.
Fichier analogique
Une fois que Swift a des propriétés privées (voir ici stackoverflow.com/a/24012515/171933 ), vous pouvez simplement ajouter une fonction setter à votre sous-classe pour définir cette propriété privée. Lorsque votre fonction getter est appelée "name", votre setter s'appellera "setName", donc pas de conflit de nom.
Johannes Fahrenkrug
Vous pouvez déjà le faire (la différence est que la propriété stockée que vous utilisez pour le support sera publique). Mais l'OP a demandé s'il y avait une différence entre déclarer une propriété en lecture seule ou une fonction dans la base. Si vous déclarez une propriété en lecture seule, vous pouvez la rendre en lecture-écriture dans une classe dérivée. Une extension qui ajoute willSetet didSetà la classe de base , sans rien savoir des futures classes dérivées, peut détecter les changements dans la propriété substituée. Mais vous ne pouvez rien faire de tel avec des fonctions, je pense.
Fichier analogique
Comment pouvez-vous remplacer une propriété en lecture seule pour ajouter un setter? Merci. Je vois cela dans la documentation, "Vous pouvez présenter une propriété en lecture seule héritée en tant que propriété en lecture-écriture en fournissant à la fois un getter et un setter dans votre substitution de propriété de sous-classe" mais ... dans quelle variable le setter écrit-il?
Dan Rosenstark
5

Dans le cas en lecture seule, une propriété calculée ne doit pas être considérée comme sémantiquement équivalente à une méthode, même lorsqu'elle se comporte de manière identique, car l'abandon de la funcdéclaration brouille la distinction entre les quantités qui composent l' état d'une instance et les quantités qui ne sont que des fonctions du Etat. Vous enregistrez la saisie() sur le site d'appel, mais vous risquez de perdre la clarté de votre code.

À titre d'exemple trivial, considérons le type de vecteur suivant:

struct Vector {
    let x, y: Double
    func length() -> Double {
        return sqrt(x*x + y*y)
    }
}

En déclarant la longueur comme une méthode, il est clair que c'est une fonction de l'état, qui ne dépend que de xet y.

D'autre part, si vous deviez exprimer lengthcomme une propriété calculée

struct VectorWithLengthAsProperty {
    let x, y: Double
    var length: Double {
        return sqrt(x*x + y*y)
    }
}

puis quand vous remplissez-tab-point dans votre IDE sur une instance de VectorWithLengthAsProperty, il ressemblerait comme si x, y, lengthétaient des propriétés sur un pied d' égalité, ce qui est incorrect sur le plan conceptuel.

Egnha
la source
5
Ceci est intéressant, mais pouvez-vous donner un exemple où une propriété en lecture seule calculée serait utilisée en suivant ce principe? Peut-être que je me trompe, mais votre argument semble suggérer qu'ils ne devraient jamais être utilisés, car par définition, une propriété en lecture seule calculée ne comprend jamais state.
Stuart
2

Il existe des situations où vous préféreriez la propriété calculée aux fonctions normales. Tels que: renvoyer le nom complet d'une personne. Vous connaissez déjà le prénom et le nom. Donc vraiment la fullNamepropriété est une propriété pas une fonction. Dans ce cas, il s'agit d'une propriété calculée (car vous ne pouvez pas définir le nom complet, vous pouvez simplement l'extraire en utilisant le prénom et le nom)

class Person{
    let firstName: String
    let lastName: String
    init(firstName: String, lastName: String){
        self.firstName = firstName
        self.lastName = lastName
    }
    var fullName :String{
        return firstName+" "+lastName
    }
}
let william = Person(firstName: "William", lastName: "Kinaan")
william.fullName //William Kinaan
William Kinaan
la source
1

Du point de vue des performances, il ne semble pas y avoir de différence. Comme vous pouvez le voir dans le résultat de référence.

essentiel

main.swift extrait de code:

import Foundation

class MyClass {
    var prop: Int {
        return 88
    }

    func foo() -> Int {
        return 88
    }
}

func test(times: u_long) {
    func testProp(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }


    func testFunc(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }

    print("prop: \(testProp(times: times))")
    print("func: \(testFunc(times: times))")
}

test(times: 100000)
test(times: 1000000)
test(times: 10000000)
test(times: 100000000)

Production:

prop: 0.0380070209503174 func: 0.0350250005722046 prop: 0.371925950050354 func: 0.363085985183716 prop: 3.4023300409317 func: 3.38373708724976 prop: 33.5842199325562 func: 34.8433820009232 Program ended with exit code: 0

Dans le graphique:

référence

Benjamin Wen
la source
2
Date()ne convient pas aux benchmarks car il utilise l'horloge de l'ordinateur, qui est soumise à des mises à jour automatiques par le système d'exploitation. mach_absolute_timeobtiendrait des résultats plus fiables.
Cristik
1

Sémantiquement parlant, les propriétés calculées doivent être étroitement associées à l'état intrinsèque de l'objet - si les autres propriétés ne changent pas, l'interrogation de la propriété calculée à des moments différents devrait donner le même résultat (comparable via == ou ===) - similaire pour appeler une fonction pure sur cet objet.

D'autre part, les méthodes sortent de la boîte avec l'hypothèse que nous n'obtenons pas toujours les mêmes résultats, car Swift n'a pas de moyen de marquer les fonctions comme pures. De plus, les méthodes de la POO sont considérées comme des actions, ce qui signifie que leur exécution peut entraîner des effets secondaires. Si la méthode n'a pas d'effets secondaires, elle peut être convertie en toute sécurité en propriété calculée.

Notez que les deux déclarations ci-dessus sont purement d'un point de vue sémantique, car il pourrait bien arriver que les propriétés calculées aient des effets secondaires auxquels nous ne nous attendons pas et que les méthodes soient pures.

Cristik
la source
0

Historiquement, la description est une propriété sur NSObject et beaucoup s'attendent à ce qu'elle continue de la même manière dans Swift. Ajouter des parens après cela ne fera qu'ajouter de la confusion.

EDIT: Après un vote négatif furieux, je dois clarifier quelque chose - s'il est accédé via la syntaxe dot, cela peut être considéré comme une propriété. Peu importe ce qu'il y a sous le capot. Vous ne pouvez pas accéder aux méthodes habituelles avec la syntaxe dot.

De plus, appeler cette propriété ne nécessitait pas de parens supplémentaires, comme dans le cas de Swift, ce qui peut prêter à confusion.

Dvole
la source
1
En fait, c'est incorrect - descriptionest une méthode requise sur le NSObjectprotocole, et donc dans objective-C est retourné en utilisant [myObject description]. Quoi qu'il en soit, la propriété descriptionétait simplement un exemple artificiel - je recherche une réponse plus générique qui s'applique à toute propriété / fonction personnalisée.
Stuart le
1
Merci pour quelques éclaircissements. Je ne suis toujours pas sûr d'être entièrement d'accord avec votre déclaration selon laquelle toute méthode obj-c sans paramètre qui renvoie une valeur peut être considérée comme une propriété, même si je comprends votre raisonnement. Je vais retirer mon vote pour l'instant, mais je pense que cette réponse décrit la raison «sémantique» déjà mentionnée dans la question, et la cohérence entre les langues n'est pas vraiment le problème ici non plus.
Stuart le