★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9729365.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
属性将值与特定类,结构或枚举相关联。存储的属性将常量和变量值存储为实例的一部分,而计算属性则计算(而不是存储)值。计算属性由类,结构和枚举提供。存储的属性仅由类和结构提供。
存储和计算属性通常与特定类型的实例相关联。但是,属性也可以与类型本身相关联。这些属性称为类型属性。
此外,您可以定义属性观察器以监视属性值的更改,您可以使用自定义操作进行响应。可以将属性观察器添加到您自己定义的存储属性中,也可以添加到子类从其超类继承的属性中。
存储属性
在其最简单的形式中,存储属性是一个常量或变量,存储为特定类或结构的实例的一部分。存储的属性可以是变量存储属性(由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
是变量属性,也无法更改其属性。
此行为是由于结构是值类型。当值类型的实例标记为常量时,其所有属性也都标记为常量。
对于作为引用类型的类,情况也是如此。如果将引用类型的实例分配给常量,则仍可以更改该实例的变量属性。
懒惰的存储属性
甲懒惰存储的属性是一个属性,其初始值是不计算使用它的第一次直到。通过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
,这是与一个新的,空数组初始化String
的值。虽然未显示其余功能,但此类的目的DataManager
是管理并提供对此String
数据数组的访问。
DataManager
该类的部分功能是从文件导入数据的能力。此功能由DataImporter
类提供,假设需要花费大量时间进行初始化。这可能是因为DataImporter
实例需要在DataImporter
初始化实例时打开文件并将其内容读入内存。
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
。的当前中心位置Rect
总是可以从它的确定origin
和size
,所以你不需要中心点存储为一个明确的Point
价值。而是Rect
为被调用的计算变量定义一个自定义的getter和setter center
,使您能够使用矩形center
,就像它是一个真正的存储属性一样。
上面的例子创建了一个Rect
名为的新变量square
。的square
变量被初始化的原点,和的宽度和高度。该正方形由下图中的蓝色方块表示。(0, 0)
10
该square
变量的center
属性,然后通过点语法(访问square.center
),这会导致需要getter center
被调用,获取当前的属性值。getter实际上不是返回现有值,而是实际计算并返回一个新的Point
来表示方形的中心。如上所示,吸气剂正确返回中心点。(5, 5)
center
然后将该属性设置为新值,该值将方块向上和向右移动到下图中橙色方块所示的新位置。设置属性会调用setter for ,它会修改存储属性的值和值,并将方块移动到新位置。(15, 15)
center
center
x
y
origin
速记二传手声明
如果计算属性的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)
- }
- }
- }
速记吸气剂宣言
如果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遵循与省略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
提供只读计算属性以使外部用户能够发现其当前计算的卷是有用的。
财产观察员
财产观察员观察并回应财产价值的变化。每次设置属性值时都会调用属性观察者,即使新值与属性的当前值相同。
您可以将属性观察器添加到您定义的任何存储属性,但惰性存储属性除外。您还可以通过覆盖子类中的属性,将属性观察器添加到任何继承的属性(无论是存储还是计算)。您不需要为非重写的计算属性定义属性观察器,因为您可以在计算属性的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参数的copy-in copy-out内存模型:该值总是写回函数末尾的属性。有关输入输出参数行为的详细讨论,请参阅输入输出参数。
全局和局部变量
上面描述的用于计算和观察属性的功能也可用于全局变量和局部变量。全局变量是在任何函数,方法,闭包或类型上下文之外定义的变量。局部变量是在函数,方法或闭包上下文中定义的变量。
您在前面章节中遇到的全局变量和局部变量都是存储变量。存储的变量(如存储的属性)为特定类型的值提供存储,并允许设置和检索该值。
但是,您还可以在全局或本地范围内定义计算变量并为存储变量定义观察者。计算变量计算它们的值,而不是存储它们,它们的编写方式与计算属性相同。
输入属性
实例属性是属于特定类型的实例的属性。每次创建该类型的新实例时,它都有自己的一组属性值,与任何其他实例分开。
您还可以定义属于该类型本身的属性,而不是该类型的任何一个实例。无论您创建的该类型的实例有多少,这些属性都只会有一个副本。这些属性称为类型属性。
类型属性对于定义对特定类型的所有实例通用的值很有用,例如所有实例都可以使用的常量属性(如C中的静态常量),或者存储全局值的变量属性该类型的实例(如C中的静态变量)。
存储的类型属性可以是变量或常量。计算类型属性始终声明为变量属性,与计算实例属性的方式相同。
注意
与存储的实例属性不同,您必须始终为存储的类型属性提供默认值。这是因为类型本身没有初始化程序,可以在初始化时为存储的类型属性赋值。
存储类型属性在首次访问时会被初始化。它们只保证初始化一次,即使在同时由多个线程访问时也是如此,并且它们不需要用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
- }
- }
注意
上面的计算类型属性示例用于只读计算类型属性,但您也可以使用与计算实例属性相同的语法定义读写计算类型属性。
查询和设置类型属性
查询类型属性并使用点语法进行设置,就像实例属性一样。但是,将在类型上查询和设置类型属性,而不是在该类型的实例上。例如:
- 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
为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属性更新为7
:
- leftChannel.currentLevel = 7
- print(leftChannel.currentLevel)
- // Prints "7"
- print(AudioChannel.maxInputLevelForAllChannels)
- // Prints "7"
如果您尝试currentLevel
将右侧通道设置为,则11
可以看到右侧通道的currentLevel
属性上限为最大值10
,并且maxInputLevelForAllChannels
type属性更新为10
:
- rightChannel.currentLevel = 11
- print(rightChannel.currentLevel)
- // Prints "10"
- print(AudioChannel.maxInputLevelForAllChannels)
- // Prints "10"
所有评论(0)