Files
the-swift-programming-langu…/source/chapter2/10_Properties.md
2014-06-08 08:00:33 +08:00

6.6 KiB
Raw Blame History

属性 (Properties)

属性将值跟特定的类、结构或枚举关联。一种是存储属性,把常量或变量的值作为实例的一部分,一种是计算属性,它计算一个值。计算属性可以用于类、结构和枚举里,存储属性只能用于类和结构。

存储属性和计算属性通常用于特定类型的实例,但是,属性也可以直接用于类型本身,这种属性称为类属性。

另外,还可以定义属性观察者来监控属性值的变化,以此来触发一个自定义的操作。属性观察者可以添加到自己写的存储属性上,也可以添加到从父类继承的属性上。

存储属性

简单来说,一个存储属性就是一个特定类型实例里表示常量或变量的部分,存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。

可以在定义存储属性的时候指定默认值,详见默认属性值一节。也可以在初始化阶段设置或修改存储属性的值,甚至修改常量存储属性的值,详见在初始化阶段修改常量存储属性一节。

下面的例子定义了一个名为FixedLengthRange的结构体,表示一个在创建后无法修改整数范围的类型:

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

FixedLengthRange的实例包含一个名为firstValue的变量存储属性和一个名为length的常量存储属性。在上面的例子中,length在创建实例的时候被赋值,因为它是一个常量存储属性,所以再无法修改它的值。

常量和存储属性

如果创建了一个结构体的实例并赋值给一个常量,则无法修改实例的任何属性,即使定义了变量存储属性:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even thought firstValue is a variable property

因为rangeOfFourItems声明成了常量(用let关键字),即使firstValue是一个变量属性,也无法再修改属性它的值。

这种行为是由于结构体struct属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。

属于引用类型的类class则不一样把一个引用类型的实例赋给一个常量后仍然可以修改实例的变量属性。

延迟存储属性

延迟存储属性是指当第一次被调用的时候才有初始值的属性。在属性声明前使用@lazy特性来表示一个延迟存储属性。

注意

必须将延迟存储属性声明成变量(使用var关键字),因为可能在实例构造完成之前属性的值无法得到。常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

延迟属性很有用,当属性的值依赖于在实例的构造过程结束前无法知道具体值的外部因素时,或者当属性的值需要复杂或大量计算时,可以只在需要的时候来计算它。

下面复合类的例子使用了延迟存储属性来避免不必要的初始化。例子中定义了DataImporterDataManager两个类,下面是部分代码:

class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a non-trivial amount of time to initialize.
    */
    var fileName = "data.txt"
    // the DataImporter class would provide data importing functionality here
}
 
class DataManager {
    @lazy var importer = DataImporter()
    var data = String[]()
    // the DataManager class would provide data management functionality here
}
 
let manager = DataManager()
manager.data += "Some data"
manager.data += "Some more data"
// the DataImporter instance for the importer property has not yet been created

DataManager类包含一个名为data的存储属性,初始值是一个空的字符串(String)数组。虽然没有写出全部代码,DataManager类的目的是管理和提供对这个字符串数组的访问。

DataManager的一个功能是从文件导入数据,该功能由DataImporter类提供,它需要一定的时间来处理。因为它需要在实例化之后打开文件、读取文件内容到内存。

DataManager也可以不从文件中导入数据,所以当DataManager的实例被创建时,就没有必要创建一个DataImporter的实例。同时,更有意义的是当用到DataImporter的时候才去创建它。

由于使用了@lazy特性Attributeimporter属性只有在第一次被访问的时候才被创建。比如访问它的属性fileName时:

println(manager.importer.fileName)
// the DataImporter instance for the importer property has now been created
// prints "data.txt”

存储属性和实例变量

如果您有过Objective-C经验应该知道有2种方式在类实例存储值和引用。对于属性来说也可以使用实例变量作为属性值的后端存储。

Swift编程语言中把这些理论统一用属性来实现。Swift中的属性没有对应的实例变量属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰同时也将属性的定义简化成一个语句。 一个类型中属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方定义。

计算属性

除存储属性外,类、结构体和枚举可以定义计算属性计算属性不直接存储值而是提供一个getter来获取值一个可选的setter来间接设置值。

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set(newCenter) {
        origin.x = newCenter.x - (size.width / 2)
        origin.y = newCenter.y - (size.height / 2)
    }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
println("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// prints "square.origin is now at (10.0, 10.0)”