在Swift中初始化类常量

4
我正在尝试类似这样的操作(这只是为了演示而进行的虚构示例):

class Test {
  let hello = "hello"
  let world = "world"
  let phrase: String {
    return self.hello + self.world
  }
}

但是在Swift中,你不能使用let来定义计算属性。有没有一种方法可以不编写init()方法来实现这个功能呢?谢谢!

2个回答

6
let不能用于只读计算属性的原因是,它用于说明该属性实际上的在被设置后永远不会改变,而不是该属性是只读的。正如Apple文档所说(强调是我的):

必须使用 var 关键字声明计算属性,包括只读计算属性,因为它们的值不固定。 let 关键字仅用于常量属性,以指示它们的值在实例初始化后不能更改。

因此,您需要使用 var 来反映计算属性的值随时可能发生变化的事实,因为您在访问它时会即时创建它。虽然在您的代码中,这不可能发生,因为您的helloworld属性本身就是let常量。但是,Swift无法推断出这一点,因此您仍然需要使用var

例如:

class Test {
    let hello = "hello"
    let world = "world"
    var phrase: String {
        return self.hello + self.world
    }
}

(这不会改变属性的可读性 - 因为您没有为其提供setter,所以它仍然是只读的)

但是在您的情况下,您可能想考虑使用延迟属性,因为您的helloworld属性是常量。延迟属性在首次访问时创建,并保留其值的余生 - 这意味着每次访问时都不必继续将两个常量连接在一起。

例如:

class Test {
    let hello = "hello"
    let world = "world"
    lazy var phrase: String = {
        return self.hello + self.world
    }()
}

另一个 let 属性的特性是,在初始化之前必须知道它们的值。由于懒加载属性的值在此之前可能不确定,您还需要将其定义为 var
如果您仍然坚持想要一个 let 属性,那么我认为你有两个选择。
第一个选择是最简洁的(尽管你已经说过你不想这样做)- 你可以在初始化器中分配你的 phrase 属性。只要在调用 super.init 之前完成这个步骤,就不必处理可选项。例如:
class Test {
    let hello = "hello"
    let world = "world"
    let phrase: String

    init() {
        phrase = hello+world
    }
}

您无法在内联中完成此操作,因为该范围中的self是指静态类,而不是类实例。因此,您无法访问实例成员,必须使用init() 或 lazy/calculated属性。
第二个选项相当hacky——您可以在类级别上镜像helloworld 属性,从而可以在phrase声明中内联访问它们。例如:
class Test {
    static let hello = "hello"
    static let world = "world"

    // for some reason, Swift has trouble inferring the type
    // of the static mirrored versions of these properties
    let hello:String = Test.hello
    let world:String = Test.world

    let phrase = hello+world
}

如果您实际上不需要将helloworld属性作为实例属性,则可以将它们设置为static,这将解决您的问题。

1
很遗憾的是,helloworld(都使用let)的不可变性不能传递地应用于phrase。从概念上讲,从不可变属性派生出来的内容本身也应该是不可变的。 - Alexander
@AMomchilov 确实,编译器能够推断计算属性是否可以被视为不可变的将是很棒的事情 - 但是您应该考虑对编译器设计的影响。您必须搜索整个计算属性的主体,包括任何方法调用,任何其他计算属性,以确定结果是否是不可变的。最终结果可能会非常混乱和不直观 - 并且可能比当前语法具有更多的边缘情况。 - Hamish
我相信编译器会因为其他100个非常小众的优化原因而这样做。无论如何,我会在swift-evolution邮件列表中提出这个问题。 - Alexander
@AMomchilov 好主意,我对他们对此的看法肯定感兴趣 - 尽管我仍然持有初步的怀疑态度。毫无疑问,编译器已经能够为了优化而执行这样的操作,我只是担心结果可能对用户来说不太直观(鉴于 Swift 的设计目标是相当直观的)。 - Hamish
为什么不呢?就接口而言,phrasehelloworld之间不应该有任何特别的区别。phrase恰好是从计算属性返回的String而不是String字面量,这只是一个实现细节,与接口无关。 - Alexander
显示剩余2条评论

2

要将其作为计算属性使用,将let替换为var。例如:

class Test {
  let hello = "hello"
  let world = "world"
  var phrase: String {
     return self.hello + self.world
  }
}

这样你就可以在不初始化的情况下使用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接