Swift5.4 语言指南(十二) 属性
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9729365.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
属性将值与特定的类,结构或枚举关联。存储的属性将常量和变量值存储为实例的一部分,而计算的属性将计算(而不是存储)值。计算的属性由类,结构和枚举提供。存储的属性仅由类和结构提供。
存储和计算的属性通常与特定类型的实例相关联。但是,属性也可以与类型本身关联。这样的属性称为类型属性。
此外,您可以定义属性观察器以监视属性值的更改,您可以使用自定义操作对其进行响应。可以将属性观察器添加到您自己定义的存储属性中,也可以添加到子类从其超类继承的属性中。
您还可以使用属性包装器在多个属性的getter和setter中重用代码。
存储的属性
最简单的形式是,存储属性是作为特定类或结构实例的一部分存储的常量或变量。存储的属性可以是变量存储的属性(由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 though firstValue is a variable property
因为rangeOfFourItems
声明为常量(使用let
关键字),所以firstValue
即使firstValue
是可变属性,也无法更改其属性。
此行为是由于结构是值类型。当值类型的实例被标记为常量时,其所有属性也被标记为常量。
对于引用类型为class的类而言,情况并非如此。如果您将引用类型的实例分配给常量,则仍然可以更改该实例的变量属性。
懒惰的存储属性
甲懒惰存储的属性是一个属性,其初始值是不计算它的使用直到第一次。您可以通过在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 hasn't yet been created
的DataManager
类有一个存储属性调用data
,这是与一个新的,空数组初始化String
的值。尽管未显示其其余功能,但此类的目的DataManager
是管理和提供对此String
数据数组的访问。
DataManager
该类功能的一部分是从文件导入数据的能力。DataImporter
该类提供了此功能,假定需要花费很短的时间来初始化。这可能是因为实例初始化后DataImporter
需要打开文件并将其内容读入内存DataImporter
。
因为DataManager
实例可以在不从文件导入数据的情况下管理其数据,DataManager
所以在创建DataImporter
实例DataManager
本身时不会创建新实例。相反,DataImporter
如果首次使用实例,则在创建实例时更有意义。
因为标记了lazy
修饰符,所以DataImporter
该importer
属性的实例仅在importer
首次访问该属性时才创建,例如在filename
查询其属性时:
- print(manager.importer.filename)
- // the DataImporter instance for the importer property has now been created
- // Prints "data.txt"
笔记
如果lazy
多个线程同时访问带有修饰符的属性,并且该属性尚未初始化,则不能保证该属性仅被初始化一次。
存储的属性和实例变量
如果您有使用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)
- 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
封装awidth
和aheight
。Rect
通过原点和大小定义一个矩形。
该Rect
结构还提供了称为的计算属性center
。a的当前中心位置Rect
始终可以通过origin
和来确定size
,因此您无需将中心点存储为显式Point
值。而是Rect
为名为的计算变量定义一个自定义的getter和setter方法center
,以使您能够center
像处理真正的存储属性一样处理矩形。
上面的示例创建了一个Rect
名为的新变量square
。的square
变量被初始化的原点,和的宽度和高度。下图中的蓝色正方形表示该正方形。(0, 0)
10
该square
变量的center
属性,然后通过点语法(访问square.center
),这会导致需要gettercenter
被调用,获取当前的属性值。getter实际上不是返回现有值,而是计算并返回一个新值Point
以表示正方形的中心。如上所示,吸气剂正确返回的中心点。(5, 5)
center
然后将该属性设置为的新值,该值将正方形向上和向右移动,移至下图中橙色正方形所示的新位置。设置属性会调用的setter ,它会修改存储的属性的和值,并将正方形移至新位置。(15, 15)
center
center
x
y
origin
速记员声明
如果计算属性的设置器未为要设置的新值定义名称,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)
- }
- }
- }
速记吸气剂声明
如果getter的整个主体是单个表达式,则getter隐式返回该表达式。这是该Rect
结构的另一个版本,该版本利用此速记符号和setter的速记符号:
- struct CompactRect {
- var origin = Point()
- var size = Size()
- var center: Point {
- get {
- Point(x: origin.x + (size.width / 2),
- y: origin.y + (size.height / 2))
- }
- set {
- origin.x = newValue.x - (size.width / 2)
- origin.y = newValue.y - (size.height / 2)
- }
- }
- }
return
从getter省略the遵循与return
从函数省略相同的规则,如使用隐式返回的函数中所述。
只读计算属性
具有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)")
- // Prints "the volume of fourByFiveByTwo is 40.0"
这个例子定义了一个新的结构叫做Cuboid
,其表示与3D矩形框width
,height
和depth
特性。此结构还具有称为的只读计算属性volume
,该属性计算并返回长方体的当前体积。它没有任何意义的volume
是可调节的,因为这将是模棱两可至于哪些值width
,height
以及depth
应该用于特定的volume
值。尽管如此,Cuboid
提供一个只读的计算属性以使外部用户能够发现其当前的计算量对于a还是有用的。
物业观察员
财产观察员观察并响应财产价值的变化。每次设置属性值时都会调用属性观察器,即使新值与属性的当前值相同也是如此。
您可以在以下位置添加属性观察器:
- 您定义的存储属性
- 您继承的存储属性
- 您继承的计算属性
对于继承的属性,可以通过在子类中覆盖该属性来添加属性观察器。对于您定义的计算属性,请使用属性的setter观察并响应值更改,而不是尝试创建观察者。覆盖属性在Overriding中进行了描述。
您可以选择在属性上定义这些观察者中的一个或两个:
willSet
在值存储之前被调用。didSet
新值存储后立即调用。
如果实现willSet
观察者,则它将新的属性值作为常量参数传递。您可以在实现中为此参数指定一个名称willSet
。如果您未在实现中编写参数名称和括号,则该参数的默认参数名称为newValue
。
同样,如果实现didSet
观察者,则将传递一个包含旧属性值的常量参数。您可以命名参数,也可以使用默认参数名称oldValue
。如果您在其自己的didSet
观察器中为属性分配值,则分配的新值将替换刚刚设置的值。
笔记
在调用超类初始化器之后,如果在子类初始化器中设置了属性,则将调用超类属性的willSet
和didSet
观察者。在调用超类初始化程序之前,类在设置其自己的属性时不会调用它们。
有关初始化委派的详细信息,请参阅初始化函数代表团值类型和初始值设定代表团类的类型。
下面是一个例子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
- // 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
类声明了一个totalSteps
类型的属性Int
。这是willSet
和didSet
观察者的存储属性。
在willSet
和didSet
观察员totalSteps
每当属性分配一个新的值被调用。即使新值与当前值相同,也是如此。
本示例的willSet
观察者newTotalSteps
为即将到来的新值使用自定义参数名称。在此示例中,它只是打印出将要设置的值。
该didSet
观测器的值后调用totalSteps
被更新。它将的新值totalSteps
与旧值进行比较。如果步骤总数增加,则会显示一条消息,指示已执行了多少个新步骤。该didSet
观察者不提供旧值自定义参数名称,默认的名称oldValue
来代替。
笔记
如果将具有观察者的属性作为输入输出参数传递给函数,则始终会调用willSet
和didSet
观察者。这是因为in-out参数的in-in copy-out内存模型:该值始终在函数末尾写回到该属性。有关输入输出参数行为的详细讨论,请参见输入输出参数。
物业包装
属性包装器在管理属性存储方式的代码与定义属性的代码之间增加了一层隔离。例如,如果您具有提供线程安全检查或将其基础数据存储在数据库中的属性,则必须在每个属性上编写该代码。使用属性包装器时,定义包装器时,只需编写一次管理代码,然后通过将其应用于多个属性来重用该管理代码。
要定义属性包装器,您需要创建定义属性的结构,枚举或类wrappedValue
。在下面的代码中,该TwelveOrLess
结构确保包装的值始终包含小于或等于12的数字。如果您要求存储更大的数字,则改为存储12。
- @propertyWrapper
- struct TwelveOrLess {
- private var number: Int
- init() { self.number = 0 }
- var wrappedValue: Int {
- get { return number }
- set { number = min(newValue, 12) }
- }
- }
设置器确保新值小于12,并且getter返回存储的值。
笔记
上例中的声明number
将变量标记为private
,以确保number
仅在的实现中使用TwelveOrLess
。在其他地方编写的代码使用的getter和setter访问值wrappedValue
,并且不能number
直接使用。有关的信息private
,请参阅访问控制。
通过在属性之前写包装器的名称作为属性,将包装器应用于属性。这是一个存储一个小矩形的结构,它使用由TwelveOrLess
属性包装器实现的“ small”相同(相当随意)的定义:
- struct SmallRectangle {
- @TwelveOrLess var height: Int
- @TwelveOrLess var width: Int
- }
- var rectangle = SmallRectangle()
- print(rectangle.height)
- // Prints "0"
- rectangle.height = 10
- print(rectangle.height)
- // Prints "10"
- rectangle.height = 24
- print(rectangle.height)
- // Prints "12"
在height
和width
来自定义性能得到它们的初始值TwelveOrLess
,它设置TwelveOrLess.number
为零。rectangle.height
因为数字很小,所以将数字10存储到成功。尝试存储24实际上存储的是12的值,因为24对于属性设置程序的规则而言太大。
将包装器应用于属性时,编译器将合成为包装器提供存储的代码和提供通过包装器访问属性的代码。(属性包装器负责存储包装的值,因此没有用于此的合成代码。)您可以编写使用属性包装器的行为的代码,而无需利用特殊的属性语法。例如,这是SmallRectangle
先前代码清单的的一个版本,该版本将其属性TwelveOrLess
显式地包装在结构中,而不是@TwelveOrLess
作为属性来编写:
- struct SmallRectangle {
- private var _height = TwelveOrLess()
- private var _width = TwelveOrLess()
- var height: Int {
- get { return _height.wrappedValue }
- set { _height.wrappedValue = newValue }
- }
- var width: Int {
- get { return _width.wrappedValue }
- set { _width.wrappedValue = newValue }
- }
- }
在_height
和_width
属性存储属性包装的一个实例,TwelveOrLess
。获取height
和width
包装对wrappedValue
属性的访问权的setter和setter 。
设置包装属性的初始值
上面示例中的代码通过number
在的定义中提供初始值来设置wrapd属性的初始值TwelveOrLess
。使用此属性包装器的代码不能为被包装的属性指定其他初始值,TwelveOrLess
例如,SmallRectangle
不能给出height
或width
初始值的定义。为了支持设置初始值或其他自定义,属性包装器需要添加一个初始化程序。下面是一个扩大版TwelveOrLess
叫SmallNumber
那一套包裹和最大值定义初始化:
- @propertyWrapper
- struct SmallNumber {
- private var maximum: Int
- private var number: Int
- var wrappedValue: Int {
- get { return number }
- set { number = min(newValue, maximum) }
- }
- init() {
- maximum = 12
- number = 0
- }
- init(wrappedValue: Int) {
- maximum = 12
- number = min(wrappedValue, maximum)
- }
- init(wrappedValue: Int, maximum: Int) {
- self.maximum = maximum
- number = min(wrappedValue, maximum)
- }
- }
的定义SmallNumber
包括三个初始化器init()
,init(wrappedValue:)
和init(wrappedValue:maximum:)
,下面的示例使用这些初始化器来设置包装后的值和最大值。有关初始化和初始化程序语法的信息,请参见Initialization。
当您将包装器应用于属性并且未指定初始值时,Swift使用init()
初始化程序来设置包装器。例如:
- struct ZeroRectangle {
- @SmallNumber var height: Int
- @SmallNumber var width: Int
- }
- var zeroRectangle = ZeroRectangle()
- print(zeroRectangle.height, zeroRectangle.width)
- // Prints "0 0"
的情况下,SmallNumber
该包裹height
并width
通过调用创建SmallNumber()
。初始化程序中的代码使用默认值0和12设置初始包装值和初始最大值。属性包装器仍提供所有初始值,如之前的示例中所使用TwelveOrLess
的SmallRectangle
。与该示例不同,SmallNumber
它还支持编写那些初始值作为声明属性的一部分。
当您为属性指定初始值时,Swift使用init(wrappedValue:)
初始化程序来设置包装器。例如:
- struct UnitRectangle {
- @SmallNumber var height: Int = 1
- @SmallNumber var width: Int = 1
- }
- var unitRectangle = UnitRectangle()
- print(unitRectangle.height, unitRectangle.width)
- // Prints "1 1"
当您使用包装器写属性时,该属性将转换为对初始化器的调用。的情况下,该包裹并通过调用创建。初始化程序使用此处指定的包装值,并且使用默认最大值12。= 1
init(wrappedValue:)
SmallNumber
height
width
SmallNumber(wrappedValue: 1)
当您在自定义属性后的括号中写入参数时,Swift将使用接受这些参数的初始化程序来设置包装器。例如,如果您提供一个初始值和一个最大值,Swift将使用init(wrappedValue:maximum:)
初始化程序:
- struct NarrowRectangle {
- @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
- @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
- }
- var narrowRectangle = NarrowRectangle()
- print(narrowRectangle.height, narrowRectangle.width)
- // Prints "2 3"
- narrowRectangle.height = 100
- narrowRectangle.width = 100
- print(narrowRectangle.height, narrowRectangle.width)
- // Prints "5 4"
的实例SmallNumber
,它包装height
是通过调用创建,以及包装的实例是通过调用创建。SmallNumber(wrappedValue: 2, maximum: 5)
width
SmallNumber(wrappedValue: 3, maximum: 4)
通过包含属性包装器的参数,可以在包装器中设置初始状态,或者在创建包装器时将其他选项传递给包装器。此语法是使用属性包装器的最通用方法。您可以为属性提供所需的任何参数,然后将它们传递给初始化程序。
当包含属性包装器参数时,还可以使用赋值指定初始值。Swift将分配视为一个wrappedValue
参数,并使用接受您所包含的参数的初始化程序。例如:
- struct MixedRectangle {
- @SmallNumber var height: Int = 1
- @SmallNumber(maximum: 9) var width: Int = 2
- }
- var mixedRectangle = MixedRectangle()
- print(mixedRectangle.height)
- // Prints "1"
- mixedRectangle.height = 20
- print(mixedRectangle.height)
- // Prints "12"
SmallNumber
包装的实例height
是通过调用来创建的,该实例使用默认的最大值12。包装的实例是通过调用来创建的。SmallNumber(wrappedValue: 1)
width
SmallNumber(wrappedValue: 2, maximum: 9)
从属性包装器投影值
除了包装的值之外,属性包装器还可以通过定义投影值来公开其他功能,例如,管理对数据库的访问的属性包装器可以flushDatabaseConnection()
在其投影值上公开方法。预计值的名称与包装值相同,但以美元符号($
)开头。因为您的代码无法定义以$
投影值开头的属性,所以不会干扰您定义的属性。
在SmallNumber
上面的示例中,如果尝试将属性设置为太大的数字,则属性包装器将在存储数字之前对其进行调整。下面的代码projectedValue
在SmallNumber
结构中添加了一个属性,以在存储该新值之前跟踪该属性包装器是否调整了该属性的新值。
- @propertyWrapper
- struct SmallNumber {
- private var number: Int
- var projectedValue: Bool
- init() {
- self.number = 0
- self.projectedValue = false
- }
- var wrappedValue: Int {
- get { return number }
- set {
- if newValue > 12 {
- number = 12
- projectedValue = true
- } else {
- number = newValue
- projectedValue = false
- }
- }
- }
- }
- struct SomeStructure {
- @SmallNumber var someNumber: Int
- }
- var someStructure = SomeStructure()
- someStructure.someNumber = 4
- print(someStructure.$someNumber)
- // Prints "false"
- someStructure.someNumber = 55
- print(someStructure.$someNumber)
- // Prints "true"
写入someStructure.$someNumber
访问包装器的预计值。存储样四少数后,的值someStructure.$someNumber
是false
。但是,预计值是true
在尝试存储太大的数字(如55 )之后得出的。
属性包装器可以返回任何类型的值作为其投影值。在此示例中,属性包装器仅公开一条信息(无论数字是否已调整),因此它公开该布尔值作为其投影值。需要公开更多信息的包装器可以返回其他某种数据类型的实例,也可以返回self
以公开其包装器的实例作为其投影值。
当您从属于类型一部分的代码中访问投影值时(例如,属性获取器或实例方法),可以self.
像访问其他属性一样在属性名称之前省略。在以下示例中的代码是指围绕包装件的投影值height
与width
作为$height
和$width
:
- enum Size {
- case small, large
- }
- struct SizedRectangle {
- @SmallNumber var height: Int
- @SmallNumber var width: Int
- mutating func resize(to size: Size) -> Bool {
- switch size {
- case .small:
- height = 10
- width = 20
- case .large:
- height = 100
- width = 100
- }
- return $height || $width
- }
- }
因为属性包装器语法只是具有getter和setter的属性的语法糖,所以访问height
和width
行为与访问任何其他属性相同。例如,resize(to:)
访问中的代码height
及其width
使用的属性包装器。如果调用,开关盒将矩形的高度和宽度设置为100。包装程序将防止这些属性的值大于12,并且将投影值设置为,以记录其调整其值的事实。最后,return语句检查并确定属性包装器是否已调整或。resize(to: .large)
.large
true
resize(to:)
$height
$width
height
width
全局和局部变量
上面描述的用于计算和观察属性的功能也可用于全局变量和局部变量。全局变量是在任何函数,方法,闭包或类型上下文之外定义的变量。局部变量是在函数,方法或闭包上下文中定义的变量。
您在上一章中遇到的全局变量和局部变量都已存储为变量。与存储的属性一样,存储的变量为某种类型的值提供存储,并允许设置和检索该值。
但是,您还可以在全局或局部范围内定义计算变量并为存储的变量定义观察者。计算变量将计算其值,而不是存储它的值,并且它们的写法与计算属性的方法相同。
类型属性
实例属性是属于特定类型的实例的属性。每次创建该类型的新实例时,它都有自己的一组属性值,与其他任何实例分开。
您还可以定义属于类型本身的属性,而不是属于该类型的任何一个实例的属性。无论您创建了多少个该类型的实例,这些属性将永远只有一个副本。这些类型的属性称为类型属性。
类型属性对于定义特定类型的所有实例通用的值很有用,例如,所有实例都可以使用的常量属性(例如C中的静态常量),或存储所有实例的全局值的变量属性这种类型的(类似于C中的静态变量)。
存储的类型属性可以是变量或常量。计算类型属性始终以与计算实例属性相同的方式声明为变量属性。
笔记
与存储实例属性不同,您必须始终为存储类型属性赋予默认值。这是因为类型本身没有初始化程序,该初始化程序可以在初始化时将值分配给存储的type属性。
存储的类型属性在其第一次访问时被延迟初始化。保证它们只能被初始化一次,即使同时被多个线程访问也不需要初始化lazy
。
类型属性语法
在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
- }
- }
笔记
上面的计算类型属性示例仅适用于只读计算类型属性,但是您也可以使用与计算实例属性相同的语法来定义读写计算类型属性。
查询和设置类型属性
与实例属性一样,查询类型属性并使用点语法对其进行设置。但是,将查询类型属性并将其设置在type上,而不是在该类型的实例上进行设置。例如:
- 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
:
上述音频通道由以下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
- }
- }
- }
- }
该AudioChannel
结构定义了两个存储的类型属性以支持其功能。第一个thresholdLevel
定义音频电平可以采用的最大阈值。10
对于所有AudioChannel
实例,这是一个恒定值。如果音频信号的值大于10
,它将被限制为该阈值(如下所述)。
第二种类型的属性是一个名为的变量存储属性maxInputLevelForAllChannels
。这将跟踪任何 AudioChannel
实例已接收到的最大输入值。它以的初始值开头0
。
该AudioChannel
结构还定义了一个称为的存储实例属性currentLevel
,该属性0
以to的比例表示通道的当前音频级别10
。
该currentLevel
属性有一个didSet
属性观察器,可以在currentLevel
设置属性时检查其值。该观察者执行两项检查:
- 如果的新值
currentLevel
大于允许值thresholdLevel
,则属性观察器的上限currentLevel
为thresholdLevel
。 - 如果
currentLevel
(在任何上限之后)新值大于任何AudioChannel
实例先前接收到的任何值,则属性观察器将新currentLevel
值存储在maxInputLevelForAllChannels
type属性中。
笔记
在这两个检查的第一个中,didSet
观察者将设置currentLevel
为不同的值。但是,这不会导致再次调用观察者。
您可以使用该AudioChannel
结构创建两个新的音频通道,分别称为leftChannel
和rightChannel
,以表示立体声系统的音频电平:
- var leftChannel = AudioChannel()
- var rightChannel = AudioChannel()
如果currentLevel
将左通道的设置为,则7
可以看到maxInputLevelForAllChannels
type属性已更新为equal 7
:
- leftChannel.currentLevel = 7
- print(leftChannel.currentLevel)
- // Prints "7"
- print(AudioChannel.maxInputLevelForAllChannels)
- // Prints "7"
如果您尝试currentLevel
将右侧通道的设置为,则11
可以看到右侧通道的currentLevel
属性被限制为的最大值10
,并且maxInputLevelForAllChannels
type属性被更新为equal 10
:
- rightChannel.currentLevel = 11
- print(rightChannel.currentLevel)
- // Prints "10"
- print(AudioChannel.maxInputLevelForAllChannels)
- // Prints "10"