首页 > 代码库 > Swift 的存储属性和计算属性

Swift 的存储属性和计算属性

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

 

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

 

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

 

 

存储属性

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

 

可以在定义存储属性的时候指定默认值,请参考构造过程一章的默认属性值一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考构造过程一章的在初始化阶段修改常量存储属性一节。

 

下面的例子定义了一个名为FixedLengthRange的结构体,他描述了一个在创建后无法修改值域宽度的区间:

 

struct FixedLengthRange {
   var firstValue: Int
   let length: Int
}
var rangeOfThreeItems =FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数0,1,2
rangeOfThreeItems.firstValue = http://www.mamicode.com/6>

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

 

 

常量和存储属性

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

 

let rangeOfFourItems =FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = http://www.mamicode.com/6>

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

 

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

 

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

 

 

延迟存储属性

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

 

注意:

 

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

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

 

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

 

class DataImporter {
   /*
   DataImporter 是一个将外部文件中的数据导入的类。
    这个类的初始化会消耗不少时间。
   */
   var fileName = "data.txt"
   // 这是提供数据导入功能
}
 
class DataManager {
   @lazy var importer = DataImporter()
   var data = http://www.mamicode.com/String[]()>

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

 

DataManager的一个功能是从文件导入数据,该功能由DataImporter类提供,DataImporter需要消耗不少时间完成初始化:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。

 

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

 

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

 

println(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”


 

存储属性和实例变量

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

 

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))")
// 输出"square.origin is now at (10.0, 10.0)”


这个例子定义了 3 个几何形状的结构体:

 

Point封装了一个(x, y)的坐标

Size封装了一个width和height

Rect表示一个有原点和尺寸的矩形

Rect也提供了一个名为center的计算属性。一个矩形的中心点可以从原点和尺寸来算出,所以不需要将它以显式声明的Point来保存。Rect的计算属性center提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。

 

例子中接下来创建了一个名为square的Rect实例,初始值原点是(0, 0),宽度高度都是10。如图所示蓝色正方形。

 

square的center属性可以通过点运算符(square.center)来访问,这会调用getter 来获取属性的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的Point来表示square的中心点。如代码所示,它正确返回了中心点(5, 5)。

 

center属性之后被设置了一个新的值(15, 15),表示向右上方移动正方形到如图所示橙色正方形的位置。设置属性center的值会调用 setter 来修改属性origin的x和y的值,从而实现移动正方形到新的位置。

 

 

 

 

便捷 setter 声明

如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称newValue。下面是使用了便捷 setter 声明的Rect结构体代码:

 

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 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。

 

注意:

 

必须使用var关键字定义计算属性,包括只读计算属性,因为他们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。

只读计算属性的声明可以去掉get关键字和花括号:

 

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)
println("the volume of fourByFiveByTwois \(fourByFiveByTwo.volume)")
// 输出 "the volumeof fourByFiveByTwo is 40.0"


这个例子定义了一个名为Cuboid的结构体,表示三维空间的立方体,包含width、height和depth属性,还有一个名为volume的只读计算属性用来返回立方体的体积。设置volume的值毫无意义,因为通过width、height和depth就能算出volume。然而,Cuboid提供一个只读计算属性来让外部用户直接获取体积是很有用的。

Swift 的存储属性和计算属性