Swift中的计算只读属性与函数

时间:2014-06-04 10:39:55

标签: methods properties semantics swift

在Swift WWDC会话简介中,演示了只读属性description

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

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

选择上述方法对使用方法有什么影响:

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

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

在我看来,选择只读计算属性的最明显原因是:

  • 语义 - 在此示例中,description是属性类的属性,而不是它执行的操作。
  • 简洁/清晰 - 在获取值时无需使用空括号。

显然,上面的例子过于简单,但还有其他充分理由选择其中一个吗?例如,是否有一些功能或属性的功能可以指导您决定使用哪种功能?


N.B。乍一看,这似乎是一个非常常见的OOP问题,但我很想知道任何特定于Swift的功能,这些功能可以指导使用这种语言时的最佳实践。

10 个答案:

答案 0 :(得分:51)

在我看来,这主要是风格问题:我非常喜欢将属性用于:属性;意味着您可以获得和/或设置的简单值。我在实际工作时使用函数(或方法)。也许必须从磁盘或数据库中计算或读取某些东西:在这种情况下,我使用一个函数,即使只返回一个简单的值。这样我就可以很容易地看出一个电话是便宜的(属性)还是可能是昂贵的(功能)。

当Apple发布一些Swift编码约定时,我们可能会更加清晰。

答案 1 :(得分:12)

好吧,你可以申请Kotlin的建议https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties

  

在某些情况下,没有参数的函数可以互换   具有只读属性。虽然语义相似,但有   关于什么时候喜欢彼此的一些风格约定。

     

在基础算法时首选属性而不是函数:

     
      
  • 不会抛出
  •   
  • 复杂度很便宜(或计算)   在第一次运行)
  •   
  • 通过调用返回相同的结果
  •   

答案 2 :(得分:11)

虽然计算属性与方法的问题一般是困难和主观的,但目前在Swift的案例中有一个重要的论点,即偏好属性的方法。你可以使用Swift中的方法作为纯函数,这对于属性来说是不正确的(从Swift 2.0 beta开始)。这使得方法更加强大和有用,因为它们可以参与功能组合。

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

答案 3 :(得分:7)

由于运行时相同,因此该问题也适用于Objective-C。我说,你得到的属性

  • 可以在子类中添加setter,使属性readwrite
  • 使用KVO / didSet进行更改通知的能力
  • 更一般地说,您可以将属性传递给期望关键路径的方法,例如获取请求排序

至于特定于Swift的东西,我唯一的例子是你可以使用@lazy作为属性。

答案 4 :(得分:7)

有区别: 如果使用属性,则最终可以覆盖它并使其在子类中进行读/写。

答案 5 :(得分:4)

在只读的情况下,计算属性应被视为在语义上等同于方法,即使它们的行为相同,因为删除func声明会模糊数量之间的区别包含实例的 state 和仅仅是函数的数量的数量。您可以在通话网站上保存输入(),但可能会在代码中失去清晰度。

作为一个简单的例子,请考虑以下矢量类型:

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

通过将长度声明为方法,很明显它是状态的函数,仅取决于xy

另一方面,如果您要将length表示为计算属性

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

然后当您在IDE中对VectorWithLengthAsProperty的实例进行dot-tab-complete时,看起来好像xylength是平等的基础,这在概念上是不正确的。

答案 6 :(得分:2)

在某些情况下,您更喜欢计算属性而不是普通函数。如:返回一个人的全名。您已经知道名字和姓氏。所以fullName属性实际上是属性而不是函数。在这种情况下,它是计算属性(因为你不能设置全名,你可以使用firstname和lastname提取它)

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

答案 7 :(得分:1)

从语义上讲,计算属性应该与对象的内在状态紧密耦合 - 如果其他属性没有改变,那么在不同时间查询计算属性应该给出相同的输出(通过==或===进行比较) ) - 类似于在该对象上调用纯函数。

另一方面,方法开箱即用,假设我们可能不会总是得到相同的结果,因为Swift没有办法将函数标记为纯。此外,OOP中的方法被视为操作,这意味着执行它们可能会导致副作用。如果该方法没有副作用,则可以安全地将其转换为计算属性。

请注意,上述两个语句纯粹来自语义角度,因为计算属性可能会产生我们不期望的副作用,而且方法也是纯粹的。

答案 8 :(得分:1)

从性能角度看,似乎没有什么区别。如您在基准测试结果中所见。

gist

main.swift代码段:

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)

输出:

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

在图表中

benchmark

答案 9 :(得分:0)

历史上描述是NSObject上的一个属性,很多人都希望它在Swift中继续相同。在它之后添加parens只会增加混乱。

编辑: 在愤怒的downvoting之后我必须澄清一些东西 - 如果它是通过点语法访问的,它可以被认为是属性。引擎盖下的内容并不重要。您无法使用点语法访问常用方法。

此外,调用此属性不需要额外的parens,如Swift的情况,这可能会导致混淆。