Property属性将值与特定类型的类、结构体、枚举关联。
分类一(根据是否存储):
-
Stored properties 存储属性:
将常量和变量值存储为实例的一部分,适用于 类、结构、 枚举。 -
Computed properties 计算属性 :
计算值而不是存储值,适用于类、结构。
分类二 (根据调用者,是实例,还是类型)
- Type properties 可以是 类,结构体
存储和计算的属性通常与特定类型的实例关联。然而,属性也可以与类型本身相关联。这些属性称为类型属性。
此外,您还可以定义属性观察者来监视属性值中的更改,您可以使用自定义操作来响应这些更改。属性观察者可以添加到您自己定义的存储属性中,也可以添加到子类从超类继承的属性中。
Stored Properties 存储属性
在其最简单的形式中,存储属性是作为特定类或结构的实例的一部分存储的常量或变量。存储属性可以是变量存储属性(由var关键字引入),也可以是常量存储属性(由let关键字引入)。
您可以为存储的属性提供默认值作为其定义的一部分。还可以在初始化期间设置和修改存储属性的初始值。即使对于常量存储的属性也是如此。
下面的例子定义了一个名为FixedLengthRange的结构,它描述了一个整数的范围,其范围长度在创建后不能改变:
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
print(rangeOfThreeItems.firstValue)
print(rangeOfThreeItems.length)
rangeOfThreeItems.firstValue = 1
print(rangeOfThreeItems.firstValue)
print(rangeOfThreeItems.length)
Int 类型的length 使用let 声明为常量,所以无法修改。
int 类型的firstValue 使用 var 声明为变量,所以可以修改
Lazy Stored Properties 懒惰存储属性
懒惰存储属性是直到第一次使用它时才计算其初始值的属性,该过程又被称为懒加载。通过在声明前编写 lazy修饰符,可以指示延迟存储属性。
注意:必须始终将惰性属性声明为变量(使用var关键字),因为在实例初始化完成之前,可能无法检索它的初始值。常量属性在初始化完成之前必须始终有一个值,因此不能声明为惰性属性。
当属性的初始值依赖于外部因素时,延迟属性非常有用,这些外部因素的值在实例初始化完成后才知道。当属性的初始值需要复杂的或计算开销较大的设置时,惰性属性也很有用,除非或者直到需要时才应该执行这些设置。
下面的示例使用惰性存储属性来避免对复杂类进行不必要的初始化。这个例子定义了两个类DataImporter和DataManager,这两个类都没有完整显示:
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的存储属性,它是用一个新的空字符串值数组初始化的。虽然没有显示它的其他功能,但是这个DataManager类的目的是管理和提供对这个字符串数据数组的访问。
DataManager类的部分功能是能够从文件导入数据。这个功能是由DataImporter类提供的,它的初始化花费了大量的时间。这可能是因为DataImporter实例需要打开一个文件,并在初始化DataImporter实例时将其内容读入内存。
DataManager实例可以在不从文件导入数据的情况下管理其数据,因此在创建DataManager本身时不需要创建新的DataImporter实例。相反,在第一次使用DataImporter实例时创建它更有意义。
因为它是用惰性修饰符标记的,所以只有在第一次访问导入器属性时,例如查询其filename属性时,才会创建导入器属性的DataImporter实例:
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
注意: 如果标记为惰性修饰符的属性被多个线程同时访问,且该属性尚未初始化,则不能保证该属性只初始化一次。
Stored Properties and Instance Variables 存储属性和实例变量
如果您有使用Objective-C的经验,您可能知道它提供了两种方法来将值和引用存储为类实例的一部分。除了属性之外,还可以使用实例变量作为存储在属性中的值的后备存储。
Swift将这些概念统一为一个属性声明。Swift属性没有对应的实例变量,并且不能直接访问属性的备份存储。这种方法避免了在不同上下文中如何访问值的混淆,并将属性的声明简化为一个确定的语句。关于属性的所有信息(包括它的名称、类型和内存管理特性)都在一个位置中定义为类型定义的一部分。
Computed Properties 计算属性
除了存储属性之外,类、结构和枚举还可以定义计算属性,而这些计算属性实际上并不存储值。相反,它们提供一个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)
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的计算属性。Rect的当前中心位置始终可以从它的原点和大小确定,因此不需要将中心点存储为显式的点值。相反,Rect为计算变量center定义了自定义getter和setter,使您能够像处理实际存储的属性一样处理矩形的center。
上面的示例创建了一个名为square的新Rect变量。square变量初始化的原点为(0,0),宽度和高度为10。这个正方形由下图中的蓝色正方形表示。
然后通过点语法(square.center)访问square变量的center属性,从而调用center的getter来检索当前属性值。getter实际计算并返回一个表示正方形中心的新点,而不是返回一个现有值。如上所示,getter正确地返回一个中心点(5,5)。
然后将center属性设置为一个新值(15,15),该值将正方形向右上方移动,移动到下图中橙色正方形所示的新位置。设置center属性将调用center的setter,该setter修改存储的origin属性的x和y值,并将正方形移动到它的新位置。
computedProperties_2x.png
Shorthand Setter Declaration 简写setter方法声明
如果计算属性的setter没有为要设置的新值定义名称,则使用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)
}
}
}
Read-Only Computed Properties 只读的计算属性
带有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)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
上面volume是只读的计算属性
Property Observers 属性观察者
属性观察者观察并响应属性值的变化。
每次设置属性值时都会调用属性观察者,即使新值与属性的当前值相同。
可以将属性观察者添加到定义的任何存储属性中,lazy存储属性除外。还可以通过覆盖子类中的属性将属性观察者添加到任何继承的属性(无论是存储的还是计算的)。您不需要为未覆盖的计算属性定义属性观察员,因为您可以在计算属性的setter中观察并响应对其值的更改。属性重写在重写中描述。
你可以选择在一个属性上定义其中一个或两个观察者:
- willSet 将在存储值之前调用willSet。
- didSet 在存储新值之后立即调用didSet。
如果您实现一个willSet观察者,它会将新的属性值作为常量参数传递。您可以为该参数指定一个名称,作为willSet实现的一部分。如果没有在实现中编写参数名和括号,则可以使用默认参数名newValue来提供参数。
类似地,如果实现didSet观察者,它会传递一个包含旧属性值的常量参数。可以为参数命名,也可以使用oldValue的默认参数名。如果在属性自身的didSet观察者中为属性赋值,则赋值的新值将替换刚刚设置的值。
注意:调用超类初始化器后,在子类初始化器中设置属性时调用超类属性的willSet和didSet观察者。在调用超类初始化器之前,类在设置自己的属性时不会调用它们。
有关初始化器委托的更多信息,请参考 Initializer Delegation for Value Types 和 Initializer Delegation for Class Types。
这里有一个willSet和didSet的例子。下面的示例定义了一个名为StepCounter的新类,它跟踪一个人走路时的总步数。这个类可以与计步器或其他计步器的输入数据一起使用,以跟踪一个人在日常活动中的锻炼情况。
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
stepCounter.totalSteps = 360
stepCounter.totalSteps = 896
StepCounter类声明一个Int类型的totalSteps属性。这是一个带有willSet和didSet观察者的存储属性。
每当为属性赋值时,都会调用totalSteps的willSet和didSet观察者。即使新值与当前值相同,也是如此。
本例的willSet观察者为即将到来的新值使用了newTotalSteps的自定义参数名。在本例中,它只是打印将要设置的值。
更新totalSteps的值之后,将调用didSet观察者。它将totalSteps的新值与旧值进行比较。如果步骤总数增加了,就会打印一条消息来指示已经采取了多少新步骤。didSet观察者不为旧值提供自定义参数名,而是使用默认的oldValue名称。
注意:如果将具有观察者的属性作为in-out参数传递给函数,则始终调用willSet和didSet观察者。这是因为in-out参数的copy-in - copy-out内存模型:值总是在函数结束时写回属性。有关in-out参数行为的详细讨论,请参见 In-Out Parameters。
Global and Local Variables 全局变量和局部变量
上面描述的计算和观察属性的功能也适用于全局变量和局部变量。全局变量是定义在任何函数、方法、闭包或类型上下文之外的变量。局部变量是在函数、方法或闭包上下文中定义的变量。
您在前几章中遇到的全局变量和局部变量都已存储为变量。与已存储的属性一样,已存储的变量为特定类型的值提供存储,并允许设置和检索该值。
不过,还可以在全局或局部范围内定义计算变量和存储变量的观察者。计算变量计算它们的值,而不是存储它,并且它们与计算属性以相同的方式编写。
注意:全局常量和变量的计算总是延迟的,这与lazy存储属性类似。与惰性存储属性不同,全局常量和变量不需要用lazy 修饰符标记。 局部常量和变量的计算从不是延迟的。
Type Properties 类型属性
实例属性是属于特定类型实例的属性。每次创建该类型的新实例时,它都有自己的一组属性值,与任何其他实例分开。
您还可以定义属于类型本身的属性,而不是属于该类型的任何一个实例。无论您创建了多少这种类型的实例,这些属性都只有一个副本。这些类型的属性称为类型属性。
属性用于定义值类型是普遍的某一特定类型的所有实例,如一个常数所有实例的属性可以使用(如静态常数C),或一个变量属性存储一个值,该类型的所有实例都是全局的(像一个静态变量C)。
存储类型属性可以是变量或常量。
计算类型属性始终声明为变量属性,方法与实例计算属性相同。
与实例存储属性不同,必须始终为类型存储属性提供默认值。这是因为类型本身没有可以在初始化时为存储的类型属性分配值的初始化器。
存储的类型属性在第一次访问时惰性地初始化。它们保证只初始化一次,即使被多个线程同时访问,也不需要使用lazy修饰符来标记它们。
Type Property Syntax 类型属性的语法
在C和Objective-C中,将与类型关联的静态常量和变量定义为全局静态变量。然而,在Swift中,类型属性是作为类型定义的一部分编写的,在类型的外部花括号中,每个类型属性都显式地限定为其支持的类型的范围。
使用static关键字定义类型属性。对于类类型的计算类型属性,可以使用class关键字来允许子类覆盖超类的实现。下面的示例显示了存储和计算类型属性的语法:
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
}
}
注意:上面的类型计算属性示例用于只读计算类型属性,但是您也可以使用与实例计算属性相同的语法定义读写类型计算属性。
Querying and Setting Type Properties 查询和设置类型属性
类型属性使用点语法查询和设置,就像实例属性一样。然而,类型属性是在类型上查询和设置的,而不是在该类型的实例上。例如:
print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"
下面的示例使用两个类型存储属性作为结构的一部分,该结构为多个音频通道建模音频电平表。每个通道都有一个整数音频级别,范围在0到10之间。
下图说明了如何将这两个音频通道组合在一起来为立体声音频电平表建模。当一个通道的音频水平= 0时,该通道的灯光都不亮。当音频是10级,所有通道的灯光都亮。在这个图中,左边通道的当前水平9日和右通道的当前水平7:
staticPropertiesVUMeter_2x.png
上述音频通道由音频通道结构的实例表示:
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
}
}
}
}
AudioChannel结构定义了两个存储类型属性来支持其功能。第一个阈值level定义了音频级别可以接受的最大阈值。对于所有AudioChannel实例,这是一个常量值10。如果音频信号的值高于10,它将被限制为这个阈值(如下所述)。
第二个类型属性是一个名为maxInputLevelForAllChannels的变量存储属性。这将跟踪任何AudioChannel实例接收到的最大输入值。它的初始值为0。
AudioChannel结构还定义了一个名为currentLevel的存储实例属性,它表示通道当前的音频级别,范围从0到10。
currentLevel属性有一个didSet属性观察者,每当设置currentLevel的值时,这个观察者都会检查它的值。
- 如果currentLevel的新值大于允许的阈值级别,则属性观察者将currentLevel的上限设置为阈值级别。
- 如果currentLevel的新值(在任何capping之后)高于任何AudioChannel实例之前接收到的任何值,则属性观察者将新currentLevel值存储在maxInputLevelForAllChannels类型属性中。
在这两个检查中的第一个检查中,didSet观察者将currentLevel设置为一个不同的值。但是,这不会导致再次调用观察者。
您可以使用AudioChannel结构创建两个名为leftChannel和rightChannel的新音频通道,以表示立体声系统的音频级别:
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
如果将左侧通道的currentLevel设置为7,可以看到maxInputLevelForAllChannels类型属性被更新为等于7:
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
如果您尝试将正确通道的currentLevel设置为11,您可以看到正确通道的currentLevel属性被限制为最大值10,并且maxInputLevelForAllChannels类型属性被更新为等于10:
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"












网友评论