Proprietà di sola lettura calcolate rispetto alla funzione in Swift


98

Nella sessione Introduzione a Swift WWDC, descriptionviene dimostrata una proprietà di sola lettura :

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

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

Ci sono delle implicazioni nella scelta dell'approccio di cui sopra rispetto all'utilizzo di un metodo:

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

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

Mi sembra che i motivi più ovvi per cui sceglieresti una proprietà calcolata di sola lettura sono:

  • Semantica : in questo esempio ha senso descriptionessere una proprietà della classe, piuttosto che un'azione che esegue.
  • Brevità / Chiarezza : evita la necessità di utilizzare parentesi vuote quando si ottiene il valore.

Chiaramente l'esempio sopra è eccessivamente semplice, ma ci sono altri buoni motivi per sceglierne uno rispetto all'altro? Ad esempio, ci sono alcune caratteristiche di funzioni o proprietà che potrebbero guidare la tua decisione su quale utilizzare?


NB A prima vista questa sembra una domanda OOP abbastanza comune, ma sono ansioso di conoscere tutte le funzionalità specifiche di Swift che potrebbero guidare le migliori pratiche quando si utilizza questo linguaggio.


1
Guarda la sessione 204 - "Quando non usare @property" Contiene alcuni suggerimenti
Kostiantyn Koval

4
aspetta, puoi creare una proprietà di sola lettura e saltare il get {}? Non lo sapevo, grazie!
Dan Rosenstark

La sessione 204 della WWDC14 può essere trovata qui (video e diapositive), developer.apple.com/videos/play/wwdc2014/204
user3207158

Risposte:


53

Mi sembra che sia soprattutto una questione di stile: preferisco fortemente usare le proprietà proprio per questo: proprietà; significa valori semplici che puoi ottenere e / o impostare. Uso funzioni (o metodi) quando viene svolto il lavoro effettivo. Forse qualcosa deve essere calcolato o letto da disco o da un database: in questo caso utilizzo una funzione, anche quando viene restituito solo un semplice valore. In questo modo posso facilmente vedere se una chiamata è economica (proprietà) o forse costosa (funzioni).

Probabilmente otterremo maggiore chiarezza quando Apple pubblicherà alcune convenzioni di codifica Swift.


12

Bene, puoi applicare i consigli di Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

In alcuni casi le funzioni senza argomenti potrebbero essere intercambiabili con proprietà di sola lettura. Sebbene la semantica sia simile, ci sono alcune convenzioni stilistiche su quando preferire l'una all'altra.

Preferisci una proprietà a una funzione quando l'algoritmo sottostante:

  • non lancia
  • la complessità è economica da calcolare (o da determinare alla prima esecuzione)
  • restituisce lo stesso risultato su invocazioni

1
Il suggerimento "ha un O (1)" non è più incluso in quel consiglio.
David Pettigrew

Modificato per riflettere i cambiamenti di Kotlin.
Carsten Hagemann

11

Mentre una questione di proprietà calcolate rispetto ai metodi in generale è difficile e soggettiva, attualmente c'è un argomento importante nel caso di Swift per preferire i metodi alle proprietà. Puoi utilizzare i metodi in Swift come funzioni pure, il che non è vero per le proprietà (a partire da Swift 2.0 beta). Ciò rende i metodi molto più potenti e utili poiché possono partecipare alla composizione funzionale.

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

1
strings.filter {! $ (0) .isEmpty} - restituisce lo stesso risultato. È un esempio modificato dalla documentazione di Apple su Array.filter (). Ed è molto più facile da capire.
POGUIst

7

Poiché il runtime è lo stesso, questa domanda si applica anche a Objective-C. Direi, con le proprietà che ottieni

  • una possibilità di aggiungere un setter in una sottoclasse, rendendo la proprietà readwrite
  • la capacità di utilizzare KVO / didSetper le notifiche di modifica
  • più in generale, puoi passare proprietà a metodi che si aspettano percorsi chiave, ad esempio fetch request sorting

Per quanto riguarda qualcosa di specifico di Swift, l'unico esempio che ho è che puoi usare @lazyper una proprietà.


7

C'è una differenza: se usi una proprietà puoi eventualmente sovrascriverla e farla leggere / scrivere in una sottoclasse.


9
Puoi anche sovrascrivere le funzioni. Oppure aggiungi un setter per fornire capacità di scrittura.
Johannes Fahrenkrug

Puoi aggiungere un setter o definire una proprietà memorizzata quando la classe base ha definito il nome come una funzione? Sicuramente puoi farlo se ha definito una proprietà (questo è esattamente il mio punto), ma non credo che tu possa farlo se ha definito una funzione.
File analogico

Una volta che Swift ha proprietà private (vedi qui stackoverflow.com/a/24012515/171933 ), potresti semplicemente aggiungere una funzione setter alla tua sottoclasse per impostare quella proprietà privata. Quando la tua funzione getter è chiamata "name", il tuo setter sarà chiamato "setName", quindi nessun conflitto di denominazione.
Johannes Fahrenkrug

Puoi già farlo (la differenza è che la proprietà memorizzata che usi per il supporto sarà pubblica). Ma l'OP ha chiesto se c'è una differenza tra dichiarare una proprietà di sola lettura o una funzione nella base. Se si dichiara una proprietà di sola lettura, è possibile impostarla in lettura-scrittura in una classe derivata. Un'estensione che aggiunge willSete didSetalla classe base , senza sapere nulla di future classi derivate, può rilevare modifiche nella proprietà sostituita. Ma non puoi fare niente del genere con le funzioni, credo.
File analogico

Come puoi sovrascrivere una proprietà di sola lettura per aggiungere un setter? Grazie. Lo vedo nei documenti, "Puoi presentare una proprietà di sola lettura ereditata come proprietà di lettura-scrittura fornendo sia un getter che un setter nella tua sottoclasse sovrascrittura della proprietà" ma ... su quale variabile scrive il setter?
Dan Rosenstark

5

Nel caso di sola lettura, una proprietà calcolata non dovrebbe essere considerata semanticamente equivalente a un metodo, anche quando si comportano in modo identico, perché l'eliminazione della funcdichiarazione offusca la distinzione tra quantità che comprendono lo stato di un'istanza e quantità che sono semplicemente funzioni del stato. Risparmi la digitazione ()nel sito della chiamata, ma rischi di perdere chiarezza nel codice.

Come esempio banale, considera il seguente tipo di vettore:

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

Dichiarando la lunghezza come metodo, è chiaro che è una funzione dello stato, che dipende solo da xe y.

D'altra parte, se dovessi esprimere lengthcome proprietà calcolata

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

poi quando si Dot-tab-completa nel vostro IDE su un'istanza di VectorWithLengthAsProperty, sarebbe sembrare come se x, y, lengtherano immobili in condizioni di parità, che è concettualmente errato.


5
Questo è interessante, ma puoi fornire un esempio di dove verrebbe utilizzata una proprietà di sola lettura calcolata quando si segue questo principio? Forse mi sbaglio, ma il tuo argomento sembra suggerire che non dovrebbero mai essere usati, poiché per definizione una proprietà di sola lettura calcolata non comprende mai lo stato.
Stuart

2

Ci sono situazioni in cui preferiresti la proprietà calcolata rispetto alle normali funzioni. Ad esempio: restituire il nome completo di una persona. Conosci già il nome e il cognome. Quindi la fullNameproprietà è davvero una proprietà, non una funzione. In questo caso, è una proprietà calcolata (poiché non è possibile impostare il nome completo, è sufficiente estrarlo utilizzando il nome e il cognome)

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

1

Dal punto di vista delle prestazioni, non sembra esserci alcuna differenza. Come puoi vedere nel risultato del benchmark.

essenza

main.swift snippet di codice:

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)

Produzione:

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

In grafico:

prova delle prestazioni


2
Date()non è adatto per i benchmark in quanto utilizza l'orologio del computer, soggetto ad aggiornamenti automatici da parte del sistema operativo. mach_absolute_timeotterrebbero risultati più affidabili.
Cristik

1

Semanticamente parlando, le proprietà calcolate dovrebbero essere strettamente accoppiate con lo stato intrinseco dell'oggetto: se altre proprietà non cambiano, l'interrogazione della proprietà calcolata in momenti diversi dovrebbe dare lo stesso output (confrontabile tramite == o ===) - simile per chiamare una funzione pura su quell'oggetto.

I metodi d'altra parte vengono fuori dagli schemi con il presupposto che potremmo non ottenere sempre gli stessi risultati, perché Swift non ha un modo per contrassegnare le funzioni come pure. Inoltre, i metodi in OOP sono considerati azioni, il che significa che eseguirli potrebbe provocare effetti collaterali. Se il metodo non ha effetti collaterali, può essere convertito in sicurezza in una proprietà calcolata.

Si noti che entrambe le affermazioni precedenti sono puramente da una prospettiva semantica, poiché potrebbe accadere che le proprietà calcolate abbiano effetti collaterali che non ci aspettiamo e che i metodi siano puri.


0

Storicamente la descrizione è una proprietà di NSObject e molti si aspetterebbero che continui lo stesso in Swift. L'aggiunta di parentesi dopo aggiungerà solo confusione.

EDIT: Dopo un furioso downvoting devo chiarire qualcosa: se vi si accede tramite la sintassi del punto, può essere considerata una proprietà. Non importa cosa c'è sotto il cofano. Non puoi accedere ai metodi usuali con la sintassi del punto.

Inoltre, chiamare questa proprietà non richiedeva parentesi extra, come nel caso di Swift, il che potrebbe creare confusione.


1
In realtà questo non è corretto: descriptionè un metodo richiesto nel NSObjectprotocollo, quindi nell'obiettivo-C viene restituito utilizzando [myObject description]. Ad ogni modo, la proprietà descriptionera semplicemente un esempio artificioso: sto cercando una risposta più generica che si applichi a qualsiasi proprietà / funzione personalizzata.
Stuart

1
Grazie per qualche chiarimento. Non sono ancora sicuro di essere completamente d'accordo con la tua affermazione secondo cui qualsiasi metodo obj-c senza parametri che restituisce un valore può essere considerato una proprietà, anche se capisco il tuo ragionamento. Ritiro il mio voto negativo per ora, ma penso che questa risposta stia descrivendo il motivo "semantico" già menzionato nella domanda, e la coerenza linguistica non è nemmeno il problema qui.
Stuart
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.