首页 文章

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

提问于
浏览
94

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

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

  • Semantics - 在这个例子中, description 是一个类的属性,而不是它执行的动作 .

  • Brevity/Clarity - 在获取值时无需使用空括号 .

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


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

10 回答

  • 7

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

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

  • 2

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

    在某些情况下,没有参数的函数可以与只读属性互换 . 尽管语义相似,但是对于何时优先选择彼此,存在一些风格约定 . 当底层算法:不抛出复杂性时,优先考虑(或在第一次运行时得到)可以在调用时返回相同的结果

  • 48

    虽然计算属性与方法的问题一般是困难和主观的,但目前在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)))
    
  • 1

    有一点不同:如果使用属性,则最终可以覆盖它并使其在子类中进行读/写 .

  • 12

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

    • 在子类中添加setter的可能性,使属性 readwrite

    • 能够使用KVO / didSet 进行更改通知

    • 更一般地说,您可以将属性传递给期望关键路径的方法,例如获取请求排序

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

  • 0

    在只读的情况下,计算属性应该被认为在语义上等同于方法,即使它们的行为相同,因为删除 func 声明会模糊构成实例状态的数量与仅仅是函数的数量之间的区别 . 国家 . 您可以在呼叫站点保存输入 () ,但可能会在代码中失去清晰度 .

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

    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 属于平等的属性,这在概念上是不正确的 .

  • 1

    在某些情况下,您更喜欢计算属性而不是普通函数 . 如:返回一个人的全名 . 您已经知道名字和姓氏 . 所以 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
    
  • 8

    从性能的角度来看,似乎没有什么区别 . 正如你在基准测试中看到的那样结果 .

    gist

    main.swift code snippet:

    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

  • 3

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

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

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

  • 11

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

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

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

相关问题