Propiedad calculada de solo lectura vs función en Swift


98

En la sesión de Introducción a Swift WWDC, descriptionse demuestra una propiedad de solo lectura :

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

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

¿Hay alguna implicación en elegir el enfoque anterior en lugar de usar un método en su lugar?

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

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

Me parece que las razones más obvias por las que elegiría una propiedad calculada de solo lectura son:

  • Semántica : en este ejemplo, tiene sentido descriptionque sea una propiedad de la clase, en lugar de una acción que realiza.
  • Brevedad / Claridad : evita la necesidad de utilizar paréntesis vacíos al obtener el valor.

Claramente, el ejemplo anterior es demasiado simple, pero ¿hay otras buenas razones para elegir uno sobre el otro? Por ejemplo, ¿existen algunas características de las funciones o propiedades que guiarían su decisión de cuál usar?


NB A primera vista, esta parece una pregunta de OOP bastante común, pero estoy ansioso por conocer las características específicas de Swift que guiarían las mejores prácticas al usar este lenguaje.


1
Ver 204 sesión - "Cuándo no usar @property" Tiene algunos consejos
Kostiantyn Koval

4
espere, puede hacer una propiedad de solo lectura y omitir el get {}? ¡No lo sabía, gracias!
Dan Rosenstark

La sesión 204 de WWDC14 se puede encontrar aquí (video y diapositivas), developer.apple.com/videos/play/wwdc2014/204
user3207158

Respuestas:


53

Me parece que es principalmente una cuestión de estilo: prefiero usar propiedades solo para eso: propiedades; es decir, valores simples que puede obtener y / o establecer. Utilizo funciones (o métodos) cuando se realiza el trabajo real. Tal vez algo tenga que ser calculado o leído desde el disco o desde una base de datos: en este caso uso una función, incluso cuando solo se devuelve un valor simple. De esa manera puedo ver fácilmente si una llamada es barata (propiedades) o posiblemente costosa (funciones).

Probablemente obtendremos más claridad cuando Apple publique algunas convenciones de codificación Swift.


12

Bueno, puede aplicar los consejos de Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

En algunos casos, las funciones sin argumentos pueden ser intercambiables con propiedades de solo lectura. Aunque la semántica es similar, existen algunas convenciones estilísticas sobre cuándo preferir uno a otro.

Prefiere una propiedad sobre una función cuando el algoritmo subyacente:

  • no tira
  • la complejidad es barata de calcular (o en caché en la primera ejecución)
  • devuelve el mismo resultado sobre las invocaciones

1
La sugerencia "tiene una O (1)" ya no se incluye en ese consejo.
David Pettigrew

Editado para reflejar los cambios de Kotlin.
Carsten Hagemann

11

Si bien la cuestión de las propiedades calculadas frente a los métodos en general es difícil y subjetiva, actualmente existe un argumento importante en el caso de Swift para preferir los métodos a las propiedades. Puede usar métodos en Swift como funciones puras, lo que no es cierto para las propiedades (a partir de Swift 2.0 beta). Esto hace que los métodos sean mucho más poderosos y útiles, ya que pueden participar en la composición funcional.

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}: devuelve el mismo resultado. Es una muestra modificada de la documentación de Apple en Array.filter (). Y es mucho más fácil de entender.
poGUIst

7

Dado que el tiempo de ejecución es el mismo, esta pregunta también se aplica a Objective-C. Yo diría que con las propiedades que obtienes

  • una posibilidad de agregar un setter en una subclase, haciendo que la propiedad readwrite
  • la capacidad de usar KVO / didSetpara notificaciones de cambios
  • de manera más general, puede pasar propiedades a métodos que esperan rutas clave, por ejemplo, buscar orden de solicitud

En cuanto a algo específico de Swift, el único ejemplo que tengo es que se puede utilizar @lazypara una propiedad.


7

Hay una diferencia: si usa una propiedad, eventualmente puede anularla y hacerla leer / escribir en una subclase.


9
También puede anular funciones. O agregue un colocador para proporcionar capacidad de escritura.
Johannes Fahrenkrug

¿Puede agregar un establecedor o definir una propiedad almacenada cuando la clase base definió el nombre como una función? Seguramente puedes hacerlo si definió una propiedad (ese es exactamente mi punto), pero no creo que puedas hacerlo si definió una función.
Archivo analógico

Una vez que Swift tenga propiedades privadas (consulte aquí stackoverflow.com/a/24012515/171933 ), simplemente puede agregar una función de establecimiento a su subclase para establecer esa propiedad privada. Cuando su función getter se llama "name", su setter se llamará "setName", por lo que no hay conflicto de nombres.
Johannes Fahrenkrug

Ya puede hacerlo (la diferencia es que la propiedad almacenada que usa para soporte será pública). Pero el OP preguntó si hay una diferencia entre declarar una propiedad de solo lectura o una función en la base. Si declara una propiedad de solo lectura, puede convertirla en lectura-escritura en una clase derivada. Una extensión que agrega willSety didSeta la clase base , sin saber nada de futuras clases derivadas, puede detectar cambios en la propiedad invalidada. Pero no se puede hacer nada parecido con las funciones, creo.
Archivo analógico

¿Cómo se puede anular una propiedad de solo lectura para agregar un establecedor? Gracias. Veo esto en los documentos, "Puede presentar una propiedad heredada de solo lectura como una propiedad de lectura-escritura proporcionando tanto un captador como un definidor en la anulación de su propiedad de subclase" pero ... ¿en qué variable escribe el definidor?
Dan Rosenstark

5

En el caso de solo lectura, una propiedad calculada no debe considerarse semánticamente equivalente a un método, incluso cuando se comportan de manera idéntica, porque eliminar la funcdeclaración desdibuja la distinción entre cantidades que comprenden el estado de una instancia y cantidades que son meras funciones de la estado. Ahorra escribir ()en el sitio de la llamada, pero corre el riesgo de perder claridad en su código.

Como ejemplo trivial, considere el siguiente tipo de vector:

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

Al declarar la longitud como método, queda claro que es una función del estado, que depende solo de xy y.

Por otro lado, si tuvieras que expresar lengthcomo una propiedad calculada

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

a continuación, cuando Dot-tab-completo en su IDE en una instancia de VectorWithLengthAsProperty, se vería como si x, y, lengtheran propiedades en pie de igualdad, que es conceptualmente incorrecto.


5
Esto es interesante, pero ¿puede dar un ejemplo de dónde se usaría una propiedad de solo lectura calculada al seguir este principio? Tal vez me equivoque, pero su argumento parece sugerir que nunca deben usarse, ya que, por definición, una propiedad de solo lectura calculada nunca comprende el estado.
Stuart

2

Hay situaciones en las que preferiría la propiedad calculada sobre las funciones normales. Por ejemplo: devolver el nombre completo de una persona. Ya sabes el nombre y el apellido. Entonces, realmente la fullNamepropiedad es una propiedad, no una función. En este caso, se trata de una propiedad calculada (debido a que no puede establecer el nombre completo, simplemente puede extraerlo usando el nombre y el apellido)

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

Desde la perspectiva del desempeño, no parece haber diferencia. Como puede ver en el resultado del benchmark.

esencia

main.swift fragmento de código:

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)

Salida:

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

En la tabla:

punto de referencia


2
Date()no es adecuado para evaluaciones comparativas, ya que utiliza el reloj de la computadora, que está sujeto a actualizaciones automáticas por parte del sistema operativo. mach_absolute_timeobtendría resultados más fiables.
Cristik

1

Hablando semánticamente, las propiedades calculadas deben estar estrechamente acopladas con el estado intrínseco del objeto; si otras propiedades no cambian, la consulta de la propiedad calculada en diferentes momentos debería dar el mismo resultado (comparable a través de == o ===) - similar para llamar a una función pura en ese objeto.

Los métodos, por otro lado, salen de la caja con la suposición de que es posible que no siempre obtengamos los mismos resultados, porque Swift no tiene una forma de marcar funciones como puras. Además, los métodos en OOP se consideran acciones, lo que significa que ejecutarlos puede provocar efectos secundarios. Si el método no tiene efectos secundarios, se puede convertir de forma segura en una propiedad calculada.

Tenga en cuenta que las dos declaraciones anteriores son puramente desde una perspectiva semántica, ya que bien podría suceder que las propiedades calculadas tengan efectos secundarios que no esperamos y que los métodos sean puros.


0

Históricamente, la descripción es una propiedad de NSObject y muchos esperarían que continúe igual en Swift. Agregar paréntesis después solo agregará confusión.

EDITAR: Después de una furiosa votación negativa, tengo que aclarar algo: si se accede a él a través de la sintaxis de puntos, se puede considerar una propiedad. No importa lo que haya debajo del capó. No puede acceder a los métodos habituales con sintaxis de puntos.

Además, llamar a esta propiedad no requería parens adicionales, como en el caso de Swift, lo que puede generar confusión.


1
En realidad, esto es incorrecto: descriptiones un método requerido en el NSObjectprotocolo, por lo que en el objetivo-C se devuelve usando [myObject description]. De todos modos, la propiedad descriptionfue simplemente un ejemplo artificial: estoy buscando una respuesta más genérica que se aplique a cualquier propiedad / función personalizada.
Stuart

1
Gracias por alguna aclaración. Todavía no estoy seguro de estar completamente de acuerdo con su afirmación de que cualquier método obj-c sin parámetros que devuelva un valor puede considerarse una propiedad, aunque entiendo su razonamiento. Retiraré mi voto en contra por ahora, pero creo que esta respuesta describe la razón 'semántica' ya mencionada en la pregunta, y la coherencia entre idiomas tampoco es realmente el problema aquí.
Stuart
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.