swift属性_存储属性_计算属性_类型属性

分享到:

属性关联值具有特定的类、结构或枚举。存储属性将常量和变量值存为实体的一部分,但是计算属性计算(而不是存储)值。由类、结构和枚举规定计算属性。仅由类和结构规定存储属性。

存储和计算属性通常与一个特定类型的实体相关联。然而,属性也可以与其本身的类型相关联。此类属性称为类型属性。

此外,可对属性观察器进行定义以便观察属性值的变化情况,这样可以使用自定义操作进行响应。可以为你自己定义的存储属性添加属性观察器,也可以为继承父类的子类属性添加。

 

存储属性

 

最简单的情形,作为特定类或结构实体的一部分,存储属性是常量或者变量。存储属性可分为变量存储属性(关键字 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 是变量属性,也无法改变其firstValue属性。

这种特性取决于值类型的结构。当一个值类型的实体被标记为常量时,则成为所有数值的共有属性。

类则不同,其仅仅是引用类型。如果你将引用类型的实体赋值给常量,依然能够改变实体的变量属性。

 

延迟存储属性

 

延迟存储属性是第一次使用时才进行初值计算的属性。在声明之前,你可以通过编写@lazy属性表示延迟存储属性。

 

注:你必须始终声明延迟存储属性为变量属性(关键字 var),因其初始值直到实体初始化完成之后才被检索。常量属性在实体初始化完成之前必须始终有一个值,因此常量属性不能声明为延迟存储属性。

当属性初始值取决于外部因素时,延迟存储属性就派上用场了,其值在实体初始化完成之前不能够确定。

当属性初始值需要复杂或计算上高代价的设置(除非或直到需要时才执行)时,延迟属性也有用。

下例使用延迟存储属性来避免复杂类的不必要初始化操作。该例定义了 DataImporter 类和 DataManager 类,两者均未完全显示:

 

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

}

DataManager {

@lazy var importer = DataImporter()

var data = String[]()

the DataManager class would provide data management functionality here

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 类的目的是管理 String 数据组并为其规定访问接口。

DataManager类的部分功能可以从文件中导入数据。

这个功能由 DataImporter 类规定,被假定为需一定时间来初始化。这可能是因为初始化 DataImporter 实体时,需要打开文件并将其内容读到存储器中。

因为 DataManager 实体管理其数据而无需导入文件中的数据是可能的,所以在 DataManager 本身被创建时,并不需要创建一个新的 DataImporter 实体。相反,如果且当首次使用 DataImporter 实体时,创建 DataImporter 实体更有意义。

因被标以 @lazy 属性,所以 importer 属性的 DataImporter 实体仅在其被第一次访问时才被创建,例如其 fileName 属性需要被询问时:

 

println(manager.importer.fileName)

// the DataImporter instance for the importer property has now been created

// prints "data.txt"

 

存储属性和实体变量

 

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

Swift 使用一个单一属性声明来统一这些概念。Swift 属性没有与之相符的实体变量,并且属性的后备存储也不能直接访问。这种方法避免了混淆如何在不同上下文中访问值,并将属性声明简化为一个单一的明确的声明。关于属性的所有信息-包含名称、类型和内存管理特征-定义为单一位置中类型定义的一部分。

计算属性

 

除了存储属性,类、结构和枚举能够定义实际上并不存储值的计算属性。相反,它们规定吸气剂和可选的调节器来间接地检索和设置其他属性和值。

 

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)

square = Rect(origin: Point(x: 0.0, y: 0.0),

size: Size(width: 10.0, height: 10.0))

initialSquareCenter = square.center

square.center = Point(x: 15.0, y: 15.0)

("square.origin is now at (\(square.origin.x), \(square.origin.y))")

prints "square.origin is now at (10.0, 10.0)"

 

本示例定义了制作几何图形的三种结构:

 

Point encapsulates an (x, y) coordinate.

Size encapsulates a width and a height.

Rect defines a rectangle by an origin point and a size.

 

Rect结构还规定了计算属性,即center。Rect 的当前中心位置的坐标通常可通过其origin 和 size 属性得来,所以并不需要将中心点存储为明确的Point值。相反,Rect 自定义称为 center 的计算属性的吸气剂和调节器,以使你能够操作长方形的center,就像它是一个真正的存储属性一样。

前面的示例新建了一个Rect变量,称为square。square 变量的原点初始化为 (0, 0),高度和宽度初始化为 10。由以下图形中的蓝色正方形表示。 然后,通过点语法(square.center)访问 square 变量的 center 属性,这会调用 center 的吸气剂,以检索当前属性值。不同于直接返回一个存在的值,吸气剂要通过计算才能返回代表正方形中心点的新 Point。从上述示例可以看出,吸气剂可准确返回中心点(5,5)。

然后 center 属性被设置成新的值 (15, 15),这样就把这个正方形向右向上移动到了图中橙色部分所表示的新的位置。通过调用center的 调节器来设置 center 属性,这将改变存储 origin 属性的 x 和 y 值,并将正方形移动到新的位置。

computedProperties_2x.png

速计调节器声明

 

如果计算属性的调节器未定义要设置新值的名称,则会默认使用名称 newValue。下面是利用这种速记符号的另一种版本的 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)

 

只读计算属性

 

仅包含吸气剂不包含调节器的计算属性被称为只读计算机属性。只读计算属性通常返回值,并可通过点语法访问,但无法设置为一个不同的值。

注:你必须使用 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 fourByFiveByTwo is \(fourByFiveByTwo.volume)")

// prints "the volume of fourByFiveByTwo is 40.0"

 

该例定义了称为 Cuboid 的新结构,其代表包含width, height和depth属性的三维长方体结构。该结构还有一个只读计算属性 volume,它计算并返回该长方体的当前 volume。因为就应用于特定volume值的width, height和depth的值而言,volume是否可设置这一点并不明确。然而,这可用于 Cubic 规定只读计算属性以使得外部用户能够访问到其当前计算的容积。

属性观察者

 

借助属性观察者观察属性值并对其变化做出应答。每次设定属性值时,需调用属性观察者,即使新值与当前属性值相同。

可将属性观察者加到你定义的存储属性中,懒惰存储属性除外。也可将属性观察者加到继承属性(无论是否是存储型的还是计算型的)中,通过在一个子类之内重写属性而获得该属性。在重写部分对通过重写属性而获得该属性进行了描述。

注:无需对未经过重写的计算型属性定义属性观察者,因为在属性赋值器内,可直接对其值变化做出观察和应答。

可选择这些属性观察者中的其中任何一个或二者皆可:

启用willSet观察者时,新的属性值将作为一个常量参数于该观察者中通过。可指定此参数的名称,作为willSet执行的一部分。启用观察者时,若未输入参数名称和圆括号,该参数则会以默认参数名称newValue来表示。

同样,若启用didSet观察者,包含原属性值的常量参数会于该观察者中通过。如需要,可对该参数进行命名或使用默认参数名称oldValue。

注:当一个属性首次初始化时,无需调用willSet和didSet观察者。当在初始化语境外设置属性值时,可调用上述观察者。

下面给出了关于运行中的willSet和didSet的示例:在下列示例中,定义一个称为StepCounter的新类,通过StepCounter可以记录人们行走时所迈出的总步数。这一类可能要用到来自步数计或其他步进计数器的数据以便记录一个人在日常生活中的步行数。

 

class StepCounter {

var totalSteps: Int = 0 {

willSet(newTotalSteps) {

println("About to set totalSteps to \(newTotalSteps)")

}

didSet {

if totalSteps > oldValue {

println("Added \(totalSteps - oldValue) steps")

}

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 536steps

 

该StepCounter类定义了一个Int类型的totalSteps属性。这是一个带有willSet和didSet观察者的存储属性。

当属性被赋予一个新值时,可调用totalSteps的willSet 和didSet观察者。即使新值与当前值是相同的,也是如此。

例如,wilSet观察者对即将产生的新值使用自定义参数名称newTotalSteps。在该示例中,仅简单地输出了待设定值。

当totalSteps的值更新后,调用didSet观察者。它对totalSteps的新值与旧值做出比较。若总步数增加,则会输出一条信息来表明走了多少步。didSet观察者并没有为旧值提供一个自定义参数,而是用默认名称oldValue来代替。

 

注:若在属性本身具备的didSet观察者内对属性赋予一个值,所赋予的新值则会取代刚才已设定的值。

 

全局变量和局部变量

 

上述计算型属性和观察型属性对全局变量和局部变量均有效。全局变量是指被定义在任何函数、类函数、闭包或上下语境之外的变量。局部变量是指被定义在函数、类函数或闭包上下文的变量。

在上述章节中提及的全局变量和局部变量均是存储变量。存储变量,像存储属性一样,储存特定类型值且允许对该值进行设置和检索。

但是,在全局或局部范围内,也可以定义计算型变量以及定义一个存储变量的观察者。计算型变量是对其进行计算而非存储,计算变量的写入方式与计算型属性的写入方式相同。

 

注:全局常量和变量总是以一种和懒惰存储属性相似的方式被延迟地计算。不同于懒惰存储属性,全局常量和变量不必标有@lazy标识。 局部常量和变量从来没有徐缓地计算过。

类型属性

 

实例属性是指属于特定类型的实例的属性。每当创造该类型新实例时,该实例有自身已设定的属性值,并与其他的实例是不同的。

也可以定义属于该类型自身的属性,而不是针对该类型的任一实例。创建的所有该类型的实例,这里都会有这些属性的副本。该类型的属性被称为类型属性。

类型属性可定义对某个特定类型所有实例通用的值,例如所有实例都能使用的常量属性(像在C中的静态常量)或存储该类型(像在C中的静态变量)所有实例全局值的变量属性。

可以为值类型(即,结构和枚举)定义存储类型和计算类型属性。对类而言,只能够定义计算类型属性。

值类型的存储类型属性可以是变量,也可以是常量。而计算类型属性通常声明成变量属性,类似于计算实例属性。

注:不同于存储实例属性,需要为存储类型属性设定一个默认值。这是因为类型本身没有能在初始化阶段的存储类型属性设定一个值的初始化器。

 

类型属性句法

 

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

你用static关键字对值类型的类型属性进行定义且用class关键字对类类型的类型属性进行定义。下面的示例展示了存储类型属性和计算类型属性的用法:

 

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

SomeClass {

class var computedTypeProperty: Int {

return an Int value here

 

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

查询和设置类型属性

 

和实例属性一样,类型属性通过点语法进行查询和设置。但是类型属性的查询与设置是针对类型而言的,并不是针对类型的实例。例如:

 

println(SomeClass.computedTypeProperty)

// prints "42"

println(SomeStructure.storedTypeProperty)

// prints "Some value."

SomeStructure.storedTypeProperty = "Another value."

println(SomeStructure.storedTypeProperty)

// prints "Another value."

 

下列示例在一个结构中使用两个存储类型属性来展示一组声音通道的音频等级表。每个通道使用0到10来表示声音的等级。

从下面的图表中可以看出,使用了两组音频通道来表示一个立体声音频等级表。当一个通道的音频等级为0时,该通道的所有灯都不会亮。 当音频等级为10时,该通道的所有灯都会亮。在该图中,左通道表示等级为9,右通道表示等级为7:

staticPropertiesVUMeter_2x.png

上述的音频通道是由AudioChannel结构实例表示的。

结构:

 

struct AudioChannel {

static let thresholdLevel = 10

static var maxInputLevelForAllChannels = 0

var currentLevel: Int = 0 {

didSet {

if currentLevel > AudioChannel.thresholdLevel {

// cap the new audio level to the threshold level

currentLevel = AudioChannel.thresholdLevel

}

if currentLevel > AudioChannel.maxInputLevelForAllChannels {

// store this as the new overall maximum input level

AudioChannel.maxInputLevelForAllChannels = currentLevel

}

 

AudoChannel结构定义两种存储类型属性去支持其函数性。第一个类型属性中,thresholdLevel定义了音频所能达到的最高等级。对所有的AudoChannel实例而言,是个值为10的常量。当一个音频信号的值超过10时,这个信号将被限制在这个阈值(如下所述)。

第二个类型属性是一种被称为maxInputLevelForAllChannels的变化存储属性。该属性会记录任一AudoChannel实例接收的音频的最高等级。它被初始化为0。

AudoChannel结构也定义一种被称为currentLevel存储实例属性,该属性表示该通道的当前音频等级的范围在0至10之间。

currentLevel属性使用didSet属性观察者来检查currentLevel值的改变。该观察者进行两项检查:

注:第一道检查中,didSet为currentLevel设置了新值。这并不会造成观察者再次被调用。

可以使用Audiochannel结构创建两个新的leftchannel和rightchannel的音频通道来表现立体声音响系统的音频等级:

 

var leftChannel = AudioChannel()

var rightChannel = AudioChannel()

 

若设置左通道currentLevel至7,maxInputLevelForAllChannels类型属性则被更新为7:

 

leftChannel.currentLevel = 7

println(leftChannel.currentLevel)

// prints "7"

println(AudioChannel.maxInputLevelForAllChannels)

// prints "7"

 

若设置右边通道的currentLevel至11,右边通道的currentLevel属性被限制到最大值10,且maxInputLevelForAllChanneb类型属性被更新为10:

 

rightChannel.currentLevel = 11

println(rightChannel.currentLevel)

// prints "10"

println(AudioChannel.maxInputLevelForAllChannels)

// prints "10"

 

昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC