Swift编程语言学习9—— 存储属性和计算属性

时间:2024-08-14 17:06:56

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

存储属性和计算属性通经常使用于特定类型的实例,可是,属性也能够直接用于类型本身,这样的属性称为类型属性。

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

存储属性

简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量,存储属性能够是变量存储属性(用keywordvar定义),也能够是常量存储属性(用keywordlet定义)。

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

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

struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems =FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数0,1,2
rangeOfThreeItems.firstValue = 6
// 该区间如今表示整数6,7,8

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

常量和存储属性

假设创建了一个结构体的实例并赋值给一个常量,则无法改动实例的不论什么属性,即使定义了变量存储属性:

let rangeOfFourItems =FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = 6
// 虽然firstValue诗歌变量属性,这里还是会报错

由于rangeOfFourItems声明成了常量(用letkeyword),即使firstValue是一个变量属性,也无法再改动它了。

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

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

延迟存储属性

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

注意:

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

延迟属性非常实用,当属性的值依赖于在实例的构造过程结束前无法知道详细值的外部因素时,或者当属性的值须要复杂或大量计算时,能够仅仅在须要的时候来计算它。

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

class DataImporter {
/*
DataImporter 是一个将外部文件里的数据导入的类。
这个类的初始化会消耗不少时间。
*/
var fileName = "data.txt"
// 这是提供数据导入功能
} class DataManager {
@lazy var importer = DataImporter()
var data = String[]()
// 这是提供数据管理功能
} let manager = DataManager()
manager.data += "Some data"
manager.data += "Some more data"
// DataImporter 实例的 importer 属性还没有被创建

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 的计算属性就是仅仅读计算属性。仅仅读计算属性总是返回一个值,能够通过点运算符訪问,但不能设置新的值。

注意:

必须使用varkeyword定义计算属性,包含仅仅读计算属性,由于他们的值不是固定的。letkeyword仅仅用来声明常量属性,表示初始化后再也无法改动的值。

仅仅读计算属性的声明能够去掉getkeyword和花括号:

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提供一个仅仅读计算属性来让外部用户直接获取体积是非常实用的。