Propriedade somente leitura computada versus função em Swift

98

Na sessão de introdução ao Swift WWDC, uma propriedade somente leitura descriptioné demonstrada:

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

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

Existem implicações em escolher a abordagem acima em vez de usar um método:

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

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

Parece-me que os motivos mais óbvios para você escolher uma propriedade computada somente leitura são:

  • Semântica - neste exemplo, faz sentido descriptionser uma propriedade da classe, ao invés de uma ação que ela executa.
  • Brevidade / Clareza - evita a necessidade de usar parênteses vazios ao obter o valor.

Obviamente, o exemplo acima é muito simples, mas existem outras boas razões para escolher um em vez do outro? Por exemplo, existem alguns recursos de funções ou propriedades que orientariam sua decisão de qual usar?


NB À primeira vista, esta parece ser uma pergunta OOP bastante comum, mas estou ansioso para saber de quaisquer recursos específicos do Swift que orientariam as melhores práticas ao usar esta linguagem.

Stuart
fonte
1
Assista à sessão 204 - "Quando não usar @property" Tem algumas dicas
Kostiantyn Koval
4
espere, você pode fazer uma propriedade somente leitura e pular o get {}? Eu não sabia disso, obrigado!
Dan Rosenstark
WWDC14 Sessão 204 pode ser encontrada aqui (vídeo e slides), developer.apple.com/videos/play/wwdc2014/204
user3207158

Respostas:

53

Parece-me que é principalmente uma questão de estilo: prefiro fortemente usar propriedades apenas para isso: propriedades; significando valores simples que você pode obter e / ou definir. Eu uso funções (ou métodos) quando o trabalho real está sendo feito. Talvez algo precise ser calculado ou lido do disco ou de um banco de dados: neste caso, eu uso uma função, mesmo quando apenas um valor simples é retornado. Assim posso ver facilmente se uma chamada é barata (propriedades) ou possivelmente cara (funções).

Provavelmente obteremos mais clareza quando a Apple publicar algumas convenções de codificação Swift.

Johannes Fahrenkrug
fonte
12

Bem, você pode aplicar os conselhos de Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

Em alguns casos, funções sem argumentos podem ser intercambiáveis ​​com propriedades somente leitura. Embora a semântica seja semelhante, existem algumas convenções estilísticas sobre quando preferir um ao outro.

Prefira uma propriedade em vez de uma função quando o algoritmo subjacente:

  • não joga
  • complexidade é barata de calcular (ou caída na primeira execução)
  • retorna o mesmo resultado nas invocações
onmyway133
fonte
1
A sugestão "tem um O (1)" não está mais incluída nesse conselho.
David Pettigrew
Editado para refletir as alterações de Kotlin.
Carsten Hagemann
11

Embora uma questão de propriedades computadas vs métodos em geral seja difícil e subjetiva, atualmente há um argumento importante no caso de Swift para preferir métodos em vez de propriedades. Você pode usar métodos em Swift como funções puras, o que não é verdade para propriedades (a partir do Swift 2.0 beta). Isso torna os métodos muito mais poderosos e úteis, pois podem participar da composição 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)))
Max O
fonte
1
strings.filter {! $ (0) .isEmpty} - retorna o mesmo resultado. É uma amostra modificada da documentação da apple em Array.filter (). E é muito mais fácil de entender.
poGUIst
7

Já que o tempo de execução é o mesmo, esta questão se aplica ao Objective-C também. Eu diria que com propriedades que você obtém

  • uma possibilidade de adicionar um setter em uma subclasse, tornando a propriedade readwrite
  • capacidade de usar KVO /didSet para notificações de mudança
  • de maneira mais geral, você pode passar propriedade para métodos que esperam caminhos de chave, por exemplo, classificação de solicitação de busca

Quanto a algo específico para Swift, o único exemplo que tenho é que você pode usar @lazypara uma propriedade.

ilya n.
fonte
7

Há uma diferença: se você usar uma propriedade, poderá substituí-la e torná-la leitura / gravação em uma subclasse.

Arquivo Analógico
fonte
9
Você também pode substituir funções. Ou adicione um setter para fornecer habilidade de escrita.
Johannes Fahrenkrug
Você pode adicionar um setter ou definir uma propriedade armazenada quando a classe base definiu o nome como uma função? Certamente você pode fazer isso se definir uma propriedade (esse é exatamente o meu ponto), mas eu não acho que você pode fazer isso se definir uma função.
Arquivo analógico de
Assim que o Swift tiver propriedades privadas (veja aqui stackoverflow.com/a/24012515/171933 ), você pode simplesmente adicionar uma função setter à sua subclasse para definir essa propriedade privada. Quando sua função getter é chamada de "nome", seu setter seria chamado de "setName", portanto, não há conflito de nomenclatura.
Johannes Fahrenkrug
Você já pode fazer isso (a diferença é que a propriedade armazenada que você usa para suporte será pública). Mas o OP perguntou se há uma diferença entre declarar uma propriedade somente leitura ou uma função na base. Se você declarar uma propriedade somente leitura, poderá torná-la leitura-gravação em uma classe derivada. Uma extensão que adiciona willSete didSetà classe base , sem saber nada sobre as classes derivadas futuras, pode detectar alterações na propriedade substituída. Mas você não pode fazer nada parecido com funções, eu acho.
Arquivo analógico de
Como você pode substituir uma propriedade somente leitura para adicionar um configurador? Obrigado. Eu vejo isso nos documentos, "Você pode apresentar uma propriedade somente leitura herdada como uma propriedade de leitura e gravação fornecendo um getter e um setter na substituição de propriedade de sua subclasse", mas ... em qual variável o setter grava?
Dan Rosenstark
5

No caso somente leitura, uma propriedade computada não deve ser considerada semanticamente equivalente a um método, mesmo quando se comportam de forma idêntica, porque a eliminação da funcdeclaração confunde a distinção entre as quantidades que compõem o estado de uma instância e as quantidades que são meramente funções do Estado. Você economiza digitação() no local da chamada, mas corre o risco de perder a clareza do seu código.

Como um exemplo trivial, considere o seguinte tipo de vetor:

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

Ao declarar o comprimento como um método, fica claro que é uma função do estado, que depende apenas de xey .

Por outro lado, se você expressasse lengthcomo uma propriedade computada

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

então quando você dot-guia completo em seu IDE em uma instância de VectorWithLengthAsProperty, ele ficaria como se x, y, lengtheram propriedades em pé de igualdade, que é conceitualmente incorreto.

egnha
fonte
5
Isso é interessante, mas você pode dar um exemplo de onde uma propriedade computada somente leitura seria usada ao seguir este princípio? Talvez eu esteja errado, mas seu argumento parece sugerir que eles nunca deveriam ser usados, uma vez que, por definição, uma propriedade computada somente leitura nunca compreende o estado.
Stuart
2

Existem situações em que você prefere propriedades computadas a funções normais. Por exemplo: retornar o nome completo de uma pessoa. Você já sabe o nome e o sobrenome. Portanto, realmente a fullNamepropriedade é uma propriedade, não uma função. Neste caso, é uma propriedade computada (porque você não pode definir o nome completo, você pode apenas extraí-lo usando o nome e o sobrenome)

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

Do ponto de vista do desempenho, parece não haver diferença. Como você pode ver no resultado do benchmark.

essência

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)

Resultado:

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

No gráfico:

benchmark

Benjamin Wen
fonte
2
Date()não é adequado para benchmarks, pois usa o relógio do computador, que está sujeito a atualizações automáticas pelo sistema operacional. mach_absolute_timeobteria resultados mais confiáveis.
Cristik
1

Semanticamente falando, as propriedades computadas devem ser fortemente acopladas ao estado intrínseco do objeto - se outras propriedades não mudarem, então consultar a propriedade computada em momentos diferentes deve dar a mesma saída (comparável via == ou ===) - semelhante para chamar uma função pura nesse objeto.

Os métodos, por outro lado, saem da caixa com a suposição de que nem sempre podemos obter os mesmos resultados, porque o Swift não tem uma maneira de marcar funções como puras. Além disso, os métodos em OOP são considerados ações, o que significa que executá-los pode resultar em efeitos colaterais. Se o método não tiver efeitos colaterais, ele pode ser convertido com segurança em uma propriedade computada.

Observe que as duas instruções acima são puramente de uma perspectiva semântica, pois pode muito bem acontecer que as propriedades computadas tenham efeitos colaterais que não esperamos e os métodos sejam puros.

Cristik
fonte
0

Historicamente, a descrição é uma propriedade em NSObject e muitos esperariam que continuasse a mesma em Swift. Adicionar parênteses depois disso apenas aumentará a confusão.

EDIT: Depois de um downvoting furioso, tenho que esclarecer algo - se for acessado por meio da sintaxe de ponto, pode ser considerado uma propriedade. Não importa o que está sob o capô. Você não pode acessar métodos usuais com sintaxe de ponto.

Além disso, chamar essa propriedade não requer parênteses extras, como no caso do Swift, o que pode causar confusão.

Dvole
fonte
1
Na verdade, isso está incorreto - descriptioné um método obrigatório no NSObjectprotocolo e, portanto, em objetivo-C é retornado usando [myObject description]. De qualquer forma, a propriedade descriptionera simplesmente um exemplo artificial - estou procurando uma resposta mais genérica que se aplique a qualquer propriedade / função personalizada.
Stuart
1
Obrigado por alguns esclarecimentos. Ainda não tenho certeza se concordo totalmente com sua declaração de que qualquer método obj-c sem parâmetros que retorna um valor pode ser considerado uma propriedade, embora eu entenda seu raciocínio. Vou retirar meu voto negativo por enquanto, mas acho que essa resposta está descrevendo o motivo da 'semântica' já mencionado na pergunta, e a consistência entre idiomas não é realmente o problema aqui.
Stuart