Swift学习笔记-属性

属性

class 可以拥有属性。属性分为两种,存储属性和计算属性。

存储属性

最简单的情形,作为特定类或结构实例的一部分,存储属性存储着常量或者变量的值。存储属性可分为变量存储属性(关键字var描述)和常量存储属性(关键字let描述)。

当定义存储属性时,你可以提供一个默认值,这些在“默认属性值”描述。在初始化过程中你也可以设置或改变存储属性的初值。这个准则对常量存储属性也同样适用(在“初始化过程中改变常量属性”描述)

下面的例子定义了一个叫FixedLengthRange的结构,它描述了一个一定范围内的整数值,当创建这个结构时,范围长度是不可以被改变的:

1
2
3
4
5
6
7
8
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

因为rangeOfFourItems是一个常量(let),即便firstValue是一个变量属性,它的值也是不可以被改变的

这样的特性是因为结构是值类型。当一个值类型实例作为常量而存在,它的所有属性也作为常量而存在。

而这个特性对类并不适用,因为类是引用类型。如果你将引用类型的实例赋值给常量,依然能够改变实例的变量属性。

Lazy Stored Properties(懒惰存储属性?)
懒惰存储属性是当它第一次被使用时才进行初值计算。通过在属性声明前加上@lazy来标识一个懒惰存储属性。

注意
必须声明懒惰存储属性为变量属性(通过var),因为它的初始值直到实例初始化完成之后才被检索。常量属性在实例初始化完成之前就应该被赋值,因此常量属性不能够被声明为懒惰存储属性。

当属性初始值因为外部原因,在实例初始化完成之前不能够确定时,就要定义成懒惰存储属性。当属性初始值需要复杂或高代价的设置,在它需要时才被赋值时,懒惰存储属性就派上用场了。

下面的例子使用懒惰存储属性来防止类中不必要的初始化操作。它定义了类DataImporter和类DataManager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

DataImporter的实例importer只有在当它在第一次被访问时才被创建。

存储属性与实例变量

如果你使用过Objective-C,你应该知道它提供两种方式来存储作为类实例一部分的值与引用。除了属性,你可以使用实例变量作为属性值的后备存储

Swift使用一个单一属性声明来统一这些概念。一个Swift属性没有与之相符的实例变量,并且属性的后备存储也不能直接访问。这防止了在不通上下文中访问值的混淆,并且简化属性声明成为一个单一的、最终的语句。关于属性的所有信息-包含名称、类型和内存管理等-作为类型定义的一部分而定义。

计算属性

除了存储属性,类、结构和枚举能够定义计算属性。计算属性并不存储值,它提供getter和可选的setter来间接地获取和设置其它的属性和值。

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

setter声明的简略写法

如果计算属性的setter方法没有将被设置的值定义一个名称,将会默认地使用newValue这个名称来代替。下面的例子采用了这样一种特性,定义了Rect结构的新版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct AlternativeRect {
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 {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}

只读计算属性

只读计算属性只带有一个getter方法,通过点操作符,可以放回属性值,但是不能修改它的值。

1
2
3
4
5
6
7
8
9
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

注意

应该使用var关键字将计算属性(包含只读计算属性)定义成变量属性,因为它们的值并不是固定的。let关键字只被常量属性说使用,以表明一旦被设置它们的值就是不可改变的了

类型属性

类型属性句法

在C和Objective-C中,定义静态常量、变量和全局静态变量一样。但是在swift中,类型属性的定义要放在类型定义中进行,在类型定义的大括号中,显示地声明它在类型中的作用域。

对值类型而言,定义类型属性使用static关键字,而定义类类型的类型属性使用class关键字。下面的例子展示了存储和计算类型属性的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
// return an Int value here
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { // return an Int value here
}
}
class SomeClass {
class var computedTypeProperty: Int {
// return an Int value here
}
}

注意:

上面的例子是针对只读计算类型属性而言的,不过你也可以像计算实例属性一样定义可读可写的计算类型属性

查询与设置类型属性

像实例属性一样,类型属性通过点操作符来查询与设置。但是类型属性的查询与设置是针对类型而言的,并不是针对类型的实例。例如:

1
2
3
4
5
6
7
println(SomeClass.computedTypeProperty)
// prints "42"
print(SomeStructure.storedTypeProperty)
// prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// prints "Another value.

属性观察者

属性观察者观察属性值的改变并对此做出响应。当设置属性的值时,属性观察者就被调用,即使当新值同原值相同时也会被调用。

除了懒惰存储属性,你可以为任何存储属性加上属性观察者定义。另外,通过重写子类属性,也可以继承属性(存储或计算)加上属性观察者定义。属性重写在“重写”章节定义。

注意

不必为未重写的计算属性定义属性观察者,因为可以通过它的setter方法直接对值的改变做出响应

定义属性的观察者时,你可以单独或同时使用下面的方法:

willSet:设置值前被调用

didSet:设置值后立刻被调用

当实现willSet观察者时,新的属性值作为常量参数被传递。你可以为这个参数起一个名字,如果不的话,这个参数就默认地被命名成newValue。

在实现didSet观察者时也是一样,只不过传递的产量参数表示的是旧的属性值。

注意

属性初始化时,willset和didSet并不会被调用。只有在初始化上下文之外,当设置属性值时才被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

类StepCounter声明了一个Int类型的、含有willSet和didSet观察者的存储属性totalSteps。当这个属性被赋予新值时,willSet和didSet将会被调用,即使新值和旧值是相同的。

例子中的willSet观察者为参数起了个新的名字newTotalSteps,它简单地打印了即将被设置的值。

当totalSteps值被更新时,didSet观察者被调用,它比较totalSteps的新值和旧值,如果新值比旧值大,就打印所增加的步数。didSet并没有为旧值参数命名,在本例中,将会使用默认的名字oldValue来表示旧的值。

注意

如果通过didSet来设置属性的值,即使属性值刚刚被设置过,起作用的也将会是didSet,即新值是didSet设置的值,在 didSet 中不要设置属性的值,否则程序将陷入死循环。

注意

值类型(结构体,枚举)中既可以定义存储属性也可以定义计算属性;引用类型(类)

参见:http://letsswift.com/2014/06/properties/