Berechnete schreibgeschützte Eigenschaft vs. Funktion in Swift

97

In der Einführung in die Swift WWDC-Sitzung wird eine schreibgeschützte Eigenschaft descriptiondemonstriert:

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

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

Gibt es irgendwelche Auswirkungen, wenn Sie stattdessen den oben genannten Ansatz anstelle einer Methode wählen:

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

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

Es scheint mir, dass die offensichtlichsten Gründe, warum Sie sich für eine schreibgeschützte berechnete Eigenschaft entscheiden würden, folgende sind:

  • Semantik - In diesem Beispiel ist es sinnvoll description, eine Eigenschaft der Klasse zu sein und keine Aktion, die sie ausführt.
  • Kürze / Klarheit - verhindert, dass beim Abrufen des Werts leere Klammern verwendet werden müssen.

Das obige Beispiel ist natürlich zu einfach, aber gibt es andere gute Gründe, sich für eines zu entscheiden? Gibt es beispielsweise einige Merkmale von Funktionen oder Eigenschaften, die Sie bei Ihrer Entscheidung für eine Verwendung unterstützen?


NB Auf den ersten Blick scheint dies eine häufig gestellte OOP-Frage zu sein, aber ich bin gespannt auf alle Swift-spezifischen Funktionen, die als Best Practice bei der Verwendung dieser Sprache dienen.

Stuart
quelle
1
Watch 204 Sitzung - "Wann nicht @property verwenden" Es hat einige Tipps
Kostiantyn Koval
4
Warten Sie, Sie können eine schreibgeschützte Eigenschaft erstellen und die get {}? Das wusste ich nicht, danke!
Dan Rosenstark
WWDC14 Session 204 finden Sie hier (Video und Folien), developer.apple.com/videos/play/wwdc2014/204
user3207158

Antworten:

52

Es scheint mir, dass es hauptsächlich eine Frage des Stils ist: Ich bevorzuge es stark, Eigenschaften dafür zu verwenden: Eigenschaften; Dies bedeutet einfache Werte, die Sie erhalten und / oder festlegen können. Ich benutze Funktionen (oder Methoden), wenn die eigentliche Arbeit erledigt wird. Möglicherweise muss etwas von der Festplatte oder aus einer Datenbank berechnet oder gelesen werden: In diesem Fall verwende ich eine Funktion, auch wenn nur ein einfacher Wert zurückgegeben wird. Auf diese Weise kann ich leicht erkennen, ob ein Anruf billig (Eigenschaften) oder möglicherweise teuer (Funktionen) ist.

Wir werden wahrscheinlich mehr Klarheit bekommen, wenn Apple einige Swift-Codierungskonventionen veröffentlicht.

Johannes Fahrenkrug
quelle
12

Nun, Sie können die Ratschläge von Kotlin unter https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties anwenden .

In einigen Fällen können Funktionen ohne Argumente durch schreibgeschützte Eigenschaften ausgetauscht werden. Obwohl die Semantik ähnlich ist, gibt es einige stilistische Konventionen, wann man einander vorziehen soll.

Ziehen Sie eine Eigenschaft einer Funktion vor, wenn der zugrunde liegende Algorithmus:

  • wirft nicht
  • Komplexität ist billig zu berechnen (oder beim ersten Durchlauf zu berechnen)
  • Gibt das gleiche Ergebnis über Aufrufe zurück
onmyway133
quelle
1
Der Vorschlag "hat ein O (1)" ist in diesem Rat nicht mehr enthalten.
David Pettigrew
Bearbeitet, um Kotlins Änderungen widerzuspiegeln.
Carsten Hagemann
11

Während eine Frage der berechneten Eigenschaften gegenüber Methoden im Allgemeinen schwierig und subjektiv ist, gibt es derzeit ein wichtiges Argument im Fall von Swift, um Methoden gegenüber Eigenschaften vorzuziehen. Sie können Methoden in Swift als reine Funktionen verwenden, was für Eigenschaften nicht gilt (ab Swift 2.0 Beta). Dies macht Methoden viel leistungsfähiger und nützlicher, da sie an der funktionellen Zusammensetzung teilnehmen können.

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.
quelle
1
string.filter {! $ (0) .isEmpty} - gibt das gleiche Ergebnis zurück. Es handelt sich um ein modifiziertes Beispiel aus der Apple-Dokumentation zu Array.filter (). Und es ist viel einfacher zu verstehen.
PoGUIst
7

Da die Laufzeit gleich ist, gilt diese Frage auch für Objective-C. Ich würde sagen, mit Eigenschaften, die Sie bekommen

  • eine Möglichkeit, einen Setter in eine Unterklasse einzufügen, wodurch die Eigenschaft erstellt wird readwrite
  • eine Fähigkeit, KVO / zu verwendendidSet für Änderungsbenachrichtigungen zu verwenden
  • Im Allgemeinen können Sie Eigenschaften an Methoden übergeben, die Schlüsselpfade erwarten, z. B. das Sortieren von Abrufanforderungen

Was Swift betrifft, ist das einzige Beispiel, das ich habe, dass Sie es @lazyfür eine Eigenschaft verwenden können.

ilya n.
quelle
7

Es gibt einen Unterschied: Wenn Sie eine Eigenschaft verwenden, können Sie sie eventuell überschreiben und in einer Unterklasse lesen / schreiben.

Analoge Datei
quelle
9
Sie können auch Funktionen überschreiben. Oder fügen Sie einen Setter hinzu, um die Schreibfähigkeit zu gewährleisten.
Johannes Fahrenkrug
Sie können einen Setter hinzufügen oder eine gespeicherte Eigenschaft definieren, wenn die Basisklasse den Namen als Funktion definiert hat. Sicher können Sie es tun, wenn es eine Eigenschaft definiert (das ist genau mein Punkt), aber ich glaube nicht, dass Sie es tun können, wenn es eine Funktion definiert.
Analoge Datei
Sobald Swift über private Eigenschaften verfügt (siehe hier stackoverflow.com/a/24012515/171933 ), können Sie Ihrer Unterklasse einfach eine Setter-Funktion hinzufügen, um diese private Eigenschaft festzulegen . Wenn Ihre Getter-Funktion "Name" heißt, würde Ihr Setter "SetName" heißen, also kein Namenskonflikt.
Johannes Fahrenkrug
Sie können dies bereits tun (der Unterschied besteht darin, dass die gespeicherte Eigenschaft, die Sie für den Support verwenden, öffentlich ist). Das OP fragte jedoch, ob es einen Unterschied zwischen der Deklaration einer schreibgeschützten Eigenschaft oder einer Funktion in der Basis gibt. Wenn Sie eine schreibgeschützte Eigenschaft deklarieren, können Sie sie in einer abgeleiteten Klasse schreibgeschützt machen. Eine Erweiterung, die willSetund didSetzur Basisklasse hinzufügt , ohne etwas über zukünftige abgeleitete Klassen zu wissen, kann Änderungen in der überschriebenen Eigenschaft erkennen. Aber so etwas kann man mit Funktionen nicht machen, denke ich.
Analoge Datei
Wie können Sie eine schreibgeschützte Eigenschaft überschreiben, um einen Setter hinzuzufügen? Vielen Dank. Ich sehe dies in den Dokumenten: "Sie können eine geerbte schreibgeschützte Eigenschaft als schreibgeschützte Eigenschaft darstellen, indem Sie sowohl einen Getter als auch einen Setter in Ihrer Unterklasseneigenschaftsüberschreibung angeben", aber ... in welche Variable schreibt der Setter?
Dan Rosenstark
5

Im schreibgeschützten Fall sollte eine berechnete Eigenschaft nicht als semantisch äquivalent zu einer Methode betrachtet werden, selbst wenn sie sich identisch verhält, da durch das Löschen der funcDeklaration die Unterscheidung zwischen Mengen, die den Status einer Instanz umfassen, und Mengen, die lediglich Funktionen der sind, verwischt wird Zustand. Sie sparen die Eingabe ()an der Anrufstelle, riskieren jedoch, die Klarheit Ihres Codes zu verlieren.

Betrachten Sie als triviales Beispiel den folgenden Vektortyp:

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

Durch die Deklaration der Länge als Methode wird deutlich, dass es sich um eine Funktion des Zustands handelt, die nur von xund abhängt y.

Auf der anderen Seite, wenn Sie lengthals berechnete Eigenschaft ausdrücken würden

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

Wenn Sie dann in Ihrer IDE auf einer Instanz von einen Punkt-Tab-Vervollständigen ausführen VectorWithLengthAsProperty, sieht es so aus, als obx , y, lengthEigenschaften auf gleiche Augenhöhe waren, die konzeptionell falsch ist.

egnha
quelle
5
Das ist interessant, aber können Sie geben ein Beispiel , wo eine berechnete schreibgeschützte Eigenschaft würde verwendet werden , wenn dieses Prinzip zu folgen? Vielleicht irre ich mich, aber Ihr Argument scheint darauf hinzudeuten, dass sie niemals verwendet werden sollten, da eine berechnete schreibgeschützte Eigenschaft per Definition niemals einen Zustand umfasst.
Stuart
2

Es gibt Situationen, in denen Sie berechnete Eigenschaften normalen Funktionen vorziehen würden. Zum Beispiel: Rückgabe des vollständigen Namens einer Person. Sie kennen bereits den Vor- und Nachnamen. Also wirklich diefullName Eigenschaft ist eine Eigenschaft, keine Funktion. In diesem Fall handelt es sich um eine berechnete Eigenschaft (da Sie den vollständigen Namen nicht festlegen können, können Sie ihn einfach mit dem Vor- und Nachnamen extrahieren).

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
quelle
1

Aus Sicht der Leistung scheint es keinen Unterschied zu geben. Wie Sie im Benchmark-Ergebnis sehen können.

Kern

main.swift Code-Auszug:

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)

Ausgabe:

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 der Tabelle:

Benchmark

Benjamin Wen
quelle
2
Date()ist nicht für Benchmarks geeignet, da die Computeruhr verwendet wird, die vom Betriebssystem automatisch aktualisiert wird. mach_absolute_timewürde zuverlässigere Ergebnisse erhalten.
Cristik
1

Semantisch gesehen sollten berechnete Eigenschaften eng mit dem inneren Zustand des Objekts gekoppelt sein. Wenn sich andere Eigenschaften nicht ändern, sollte die Abfrage der berechneten Eigenschaft zu unterschiedlichen Zeiten dieselbe Ausgabe ergeben (vergleichbar über == oder ===) - ähnlich eine reine Funktion für dieses Objekt aufzurufen.

Methoden hingegen werden mit der Annahme ausgeliefert, dass wir möglicherweise nicht immer die gleichen Ergebnisse erzielen, da Swift keine Möglichkeit hat, Funktionen als rein zu kennzeichnen. Außerdem werden Methoden in OOP als Aktionen betrachtet, was bedeutet, dass ihre Ausführung zu Nebenwirkungen führen kann. Wenn die Methode keine Nebenwirkungen hat, kann sie sicher in eine berechnete Eigenschaft konvertiert werden.

Beachten Sie, dass beide obigen Aussagen rein semantisch sind, da es durchaus vorkommen kann, dass berechnete Eigenschaften Nebenwirkungen haben, die wir nicht erwarten, und Methoden rein sind.

Cristik
quelle
0

Historisch gesehen ist die Beschreibung eine Eigenschaft von NSObject und viele würden erwarten, dass sie in Swift dieselbe bleibt. Das Hinzufügen von Parens nach dem Hinzufügen führt nur zu Verwirrung.

EDIT: Nach heftigem Downvoting muss ich etwas klarstellen - wenn über die Punktsyntax darauf zugegriffen wird, kann es als Eigenschaft betrachtet werden. Es ist egal, was sich unter der Haube befindet. Mit der Punktsyntax können Sie nicht auf übliche Methoden zugreifen.

Außerdem erforderte das Aufrufen dieser Eigenschaft keine zusätzlichen Parens, wie im Fall von Swift, was zu Verwirrung führen kann.

Dvole
quelle
1
Tatsächlich ist dies falsch - descriptionist eine erforderliche Methode für das NSObjectProtokoll und wird daher in Ziel-C mit zurückgegeben [myObject description]. Wie auch immer, die Eigenschaft descriptionwar nur ein erfundenes Beispiel - ich suche nach einer allgemeineren Antwort, die für jede benutzerdefinierte Eigenschaft / Funktion gilt.
Stuart
1
Vielen Dank für einige Klarstellungen. Ich bin mir immer noch nicht sicher, ob ich Ihrer Aussage vollständig zustimme, dass jede parameterlose obj-c-Methode, die einen Wert zurückgibt, als Eigenschaft betrachtet werden kann, obwohl ich Ihre Argumentation verstehe. Ich werde meine Ablehnung vorerst zurückziehen, aber ich denke, diese Antwort beschreibt den bereits in der Frage erwähnten Grund für die Semantik, und die sprachübergreifende Konsistenz ist auch hier nicht wirklich das Problem.
Stuart