Swift 属性

属性关联特定类、结构体或枚举的值。Swift主要有存储属性和计算属性。存储属性存储实例的常量或者变量值,然而计算属性主要是计算值(而不是存储值),相当于是附加属性,可以用已有的存储属性计算得到。

计算和存储属性同样一般是关联特定类型的实例。然而,属性同样能够和类型本身关联。这样的属性叫做类型属性。

此外,你还能够定义属性观察器来监视属性值的变化,利用它,你可以写一些响应的操作。属性观察器既能够自己定义,也能够从父类当中继承得到。

存储属性

存储属性,最简单的形式,就是存储在一个特定的类或机构体的实例中的,一个常量或变量。存储属性既可以是变量(由关键字var定义),也可以是常量(由关键字let定义)。

下面的例子,定义了一个结构FixedLengthRange,描述了一个整型数据的范围,其中的范围长度创建以后就不能改变。

1
2
3
4
5
6
7
8
9
10

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

常量结构体实例的存储属性

假如你创建了一个结构体实例,并将它赋给一个常量,那么你就不能改变这个实例的属性,包括变量属性,如下:

1
2
3
4
5

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 though firstValue is a variable property

因为rangeOfFourItems被声明为一个常量,所以不可能改变它的firstValue属性,尽管它是一个变量属性。

这个行为是因为结构体属于值类型。值类型的实例如果标记为常量,那么它的所有属性都是常量。

这中情况对类并不适用,类是引用类型。假如你将一个引用类型的实例赋给一个常量,你仍然可以改变实例的变量属性。

懒(加载)存储属性

懒属性就是一种属性,直到它第一次被使用,它的值才会被计算。

注意点
懒属性必须声明为一个变量,因为它的初始值不会被用到,在实例初始化完成后。而常量属性必须在初始化完成以后有值,因此它不能被声明为懒属性

懒属性在以下情况下非常有用:它的值依赖于外在因素,也就是实例初始化完以后,这些外在因素还不知道的情况。还有就是,属性的值计算非常复杂或者计算代价非常高。只有使用它的时候去计算最好。

下面例子,利用懒存储属性,避免一个复杂类的不必要初始化。这个例子定义两个类DataImporterDataManager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial 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.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created

DataManager类用来管理属性data, 一个String数组。

DataManager的部分功能是从文件当中获取data。然后这个功能是由类DataImporter提供的,需要花费相当多的时间进行初始化。这可能是因为DataImporter实例需要打开文件,将文件内容读入到内存等等。

但是使用了懒属性,使用lazy标记,importer属性对应的DataImporter实例,只有当importer属性第一次被使用的时候才会被创建,比方说它的文件名被访问的时候;

1
2
3
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"

计算属性

除了存储属性,类,结构体和枚举也能够定义计算属性,它实际上不存储一个值。相反,它们提供一个获取和可选的设置,用来直接检索或设置属性和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

这个例子定义来地理形状的三个结构:

  • Point封装了一个点的x-和y-坐标
  • Size封装了长度和宽度
  • Rect定义了一个长方形通过一个原点的点和尺寸

Rect结构同样提供了一个计算属性center。这个属性可以依赖orgin和size算出来,设置它,可以通过size,改变origin。也就是计算属性,主要是提供一些不是最原始的属性,但是又可以方便用户使用和设置的一种属性。

只读计算属性

计算属性只有获取(getter)就叫做只读计算属性。只读计算属性经常返回一个值,所以可用的语法获取。

同样,可以用简写的形式,去掉get关键字和它的大括号:

1
2
3
4
5
6
7
8
9
10

struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

属性观察器

属性观察器观察并对属性值的变化作出响应。属性观察器,在每一次属性值设置的时候都会被调用,即使新的属性值和现在的值一样。

你可以给任何存储属性添加属性观察器,除了懒加载存储属性。你也可以给任何继承的属性添加属性观察器,通过在子类中重写属性。

你可以针对一个属性,定义单个或者同时定义两个观察器。

  • willSet在值被存储前被调用
  • disSet在新值存储后立即被调用

如果你实现一个willSet观察器,它会被传递新的属性值作为常量参数,并且你可以对这个人参数指定一个名字。如果你不指定名字,这个参数的默认名字为newValue

类似的,如果你实现disSet观察器,它会被传递一个常量参数,包含的是先前的值。你同样可以为这个参数命名,或者使用默认值oldValue

接下来,是一个willSetdisSet的例子。这个例子,是一个StepCounter的类,用来记步,它用一个变量totalSteps来记录当前的步数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

这个类用一个变量totalSteps来记录当前的步数。无论什么时候,只要属性被赋予新值,其中的willSetdisSet观察器都会被调用。willSet中,每次都会输出新的步数。而在didSet中,当新的步数超过旧的步数的时候,就输出新增多少步。

全局和局部变量

以上所描述的计算和观察属性对全局和局部变量都试用。全局变量定义在任何函数、方法、闭包或者类型上下文的外面。其它情况,都是局部变量。

类型属性

类型属性就是平常说的类属性,和类绑定,而和实例无关。

类属性,同样也可以是变量或者常量。计算类型属性同样也只能是变量,同一般的实例属性一样。

类型属性语法

定义类属性使用static关键字。对于需要继承的可计算类型属性,可以使用class关键字替代。下面是类属性的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}