Swift 属性
属性
- 一、存储属性
- 1、常量结构体实例的存储属性
- 2、延时加载存储属性
- 3、存储属性和实例变量
- 二、计算属性
- 1、简化 Setter 声明
- 2、简化 Getter 声明
- 3、只读计算属性
- 三、属性观察器
- 四、属性包装器
- 1、设置被包装属性的初始值
- 2、从属性包装器中呈现一个值
- 五、全局变量和局部变量
- 六、类型属性
- 1、类型属性语法
- 2、获取和设置类型属性的值
属性将值与特定的类、结构体或枚举关联。存储属性会将常量和变量存储为实例的一部分,而计算属性则是直接计算(而不是存储)值。计算属性可以用于类、结构体和枚举,而存储属性只能用于类和结构体。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接与类型本身关联,这种属性称为类型属性。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
你也可以利用属性包装器来复用多个属性的 getter
和 setter
中的代码。
一、存储属性
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。
可以在定义存储属性的时候指定默认值,请参考 默认构造器 一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考 构造过程中常量属性的修改 一节。
下面的例子定义了一个名为 FixedLengthRange
的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改。
struct FixedLengthRange {var firstValue: Intlet length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数 0,1,2
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数 6,7,8
FixedLengthRange
的实例包含一个名为 firstValue
的变量存储属性和一个名为 length
的常量存储属性。在上面的例子中,length
在创建实例的时候被初始化,且之后无法修改它的值,因为它是一个常量存储属性。
1、常量结构体实例的存储属性
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使被声明为可变属性也不行:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数 0,1,2,3
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个可变属性,但这里还是会报错
因为 rangeOfFourItems
被声明成了常量(用 let
关键字),所以即使 firstValue
是一个可变属性,也无法再修改它了。
这种行为是由于结构体属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
属于引用类型的类则不一样。把一个引用类型的实例赋给一个常量后,依然可以修改该实例的可变属性。
2、延时加载存储属性
延时加载存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy
来标示一个延时加载存储属性。
注意
必须将延时加载属性声明成变量(使用
var
关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。
当属性的值依赖于一些外部因素且这些外部因素只有在构造过程结束之后才会知道的时候,延时加载属性就会很有用。或者当获得属性的值因为需要复杂或者大量的计算,而需要采用需要的时候再计算的方式,延时加载属性也会很有用。
下面的例子使用了延时加载存储属性来避免复杂类中不必要的初始化工作。例子中定义了 DataImporter
和 DataManager
两个类,下面是部分代码:
class DataImporter {/*DataImporter 是一个负责将外部文件中的数据导入的类。这个类的初始化会消耗不少时间。*/var fileName = "data.txt"// 这里会提供数据导入功能
}
class DataManager {lazy var importer = DataImporter()var data: [String] = []// 这里会提供数据管理功能
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
DataManager
类包含一个名为 data
的存储属性,初始值是一个空的字符串数组。这里没有给出全部代码,只需知道 DataManager
类的目的是管理和提供对这个字符串数组的访问即可。
DataManager
的一个功能是从文件中导入数据。这个功能由 DataImporter
类提供,DataImporter
完成初始化需要消耗不少时间:因为它的实例在初始化时可能需要打开文件并读取文件中的内容到内存中。
DataManager
管理数据时也可能不从文件中导入数据。所以当 DataManager
的实例被创建时,没必要创建一个 DataImporter
的实例,更明智的做法是第一次用到 DataImporter
的时候才去创建它。
由于使用了 lazy
,DataImporter
的实例 importer
属性只有在第一次被访问的时候才被创建。比如访问它的属性 fileName
时:
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出“data.txt”
注意
如果一个被标记为
lazy
的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
3、存储属性和实例变量
如果你有过 Objective-C 经验,应该知道 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))
print("square.center is (\(square.center.x), \(square.center.y))")
// 打印“square.center is (5.0, 5.0)”
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.center is (\(square.center.x), \(square.center.y))")
// 打印“square.center is (15.0, 15.0)”
print("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
的计算属性。一个 Rect
的中心点可以从 origin
(原点)和 size
(大小)算出,所以不需要将中心点以 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
的值,从而实现移动正方形到新的位置。
1、简化 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)}}
}
2、简化 Getter 声明
如果整个 getter
是单一表达式,getter
会隐式地返回这个表达式结果。下面是另一个版本的 Rect
结构体,用到了简化的 getter
和 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)}}
}
3、只读计算属性
只有 getter
没有 setter
的计算属性叫只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
注意:
必须使用
var
关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let
关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只读计算属性的声明可以去掉 get
关键字和花括号:
struct Cuboid {var width = 0.0, height = 0.0, depth = 0.0var 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)")
// 打印“the volume of fourByFiveByTwo is 40.0”
这个例子定义了一个名为 Cuboid
的结构体,表示三维空间的立方体,包含 width
、height
和 depth
属性。结构体还有一个名为 volume
的只读计算属性用来返回立方体的体积。为 volume
提供 setter
毫无意义,因为无法确定如何修改 width
、height
和 depth
三者的值来匹配新的 volume
。然而,Cuboid
提供一个只读计算属性来让外部用户直接获取体积是很有用的。
三、属性观察器
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
你可以在以下位置添加属性观察器:
- 自定义的存储属性
- 继承的存储属性
- 继承的计算属性
对于继承的属性,你可以在子类中通过重写属性的方式为它添加属性观察器。对于自定义的计算属性来说,使用他的 setter
监控和响应值的变化,而不是尝试创建观察器。
可以为属性添加其中一个或两个观察器:
willSet
在新的值被设置之前调用didSet
在新的值被设置之后调用
willSet
观察器会将新的属性值作为常量参数传入,在 willSet
的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue
表示。
同样,didSet
观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 oldValue
。如果在 didSet
方法中再次对该属性赋值,那么新值会覆盖旧的值。
注意
在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的
willSet
和didSet
观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。
下面是一个 willSet
和 didSet
实际运用的例子,其中定义了一个名为 StepCounter
的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
class StepCounter {var totalSteps: Int = 0 {willSet(newTotalSteps) {print("将 totalSteps 的值设置为 \(newTotalSteps)")}didSet {if totalSteps > oldValue {print("增加了 \(totalSteps - oldValue) 步")}}}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
StepCounter
类定义了一个叫 totalSteps
的 Int
类型的属性。它是一个存储属性,包含 willSet
和 didSet
观察器。
当 totalSteps
被设置新值的时候,它的 willSet
和 didSet
观察器都会被调用,即使新值和当前值完全相同时也会被调用。
例子中的 willSet
观察器将表示新值的参数自定义为 newTotalSteps
,这个观察器只是简单的将新的值输出。
didSet
观察器在 totalSteps
的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。didSet
没有为旧值提供自定义名称,所以默认值 oldValue
表示旧值的参数名。
注意
如果将带有观察器的属性通过
in-out
方式传入函数,willSet
和didSet
也会调用。这是因为in-out
参数采用了拷入拷出内存模式:即在函数内部使用的是参数的copy
,函数结束后,又对参数重新赋值。
四、属性包装器
属性包装器在管理属性如何存储和定义属性的代码之间添加了一个分隔层。举例来说,如果你的属性需要线程安全性检查或者需要在数据库中存储它们的基本数据,那么必须给每个属性添加同样的逻辑代码。当使用属性包装器时,你只需在定义属性包装器时编写一次管理代码,然后应用到多个属性上来进行复用。
定义一个属性包装器,你需要创建一个定义 wrappedValue
属性的结构体、枚举或者类。在下面的代码中,TwelveOrL
ess 结构体确保它包装的值始终是小于等于 12
的数字。如果要求它存储一个更大的数字,它则会存储 12
这个数字。
@propertyWrapper
struct TwelveOrLess {private var number = 0var wrappedValue: Int {get { return number }set { number = min(newValue, 12) }}
}
这个 setter 确保新值小于 12,而且返回被存储的值。
注意:
上面例子以
private
的方式声明number
变量,这使得number
仅在TwelveOrLess
的实现中使用。写在其他地方的代码通过使用wrappedValue
的getter
和setter
来获取这个值,但不能直接使用number
。
通过在属性之前写上包装器名称作为特性的方式,你可以把一个包装器应用到一个属性上去。这里有个存储小矩形的结构体,通过 TwelveOrLess
属性包装器来确保它的长宽均小于等于 12
。
struct SmallRectangle {@TwelveOrLess var height: Int@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"
rectangle.height = 10
print(rectangle.height)
// 打印 "10"
rectangle.height = 24
print(rectangle.height)
// 打印 "12"
height
和 width
属性从 TwelveOrLess
的定义中获取它们的初始值。该定义把 TwelveOrLess.number
设置为 0
。把数字 10
存进 rectangle.height
中的操作能成功,是因为数字 10
很小。尝试存储 24
的操作实际上存储的值为 12
,这是因为对于这个属性的 setter
的规则来说,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
的 getter
和 setter
把对 wrappedValue
属性的访问包装起来。
1、设置被包装属性的初始值
上面例子中的代买通过在 TwelveOrLess
的定义中赋予 number
一个初始值来设置被包装属性的初始值。使用这个属性包装器的代码没法为被 TwelveOrLess
包装的属性指定其他初始值。举例来说, SmallRectangle
的定义没法给 height
或者 width
一个初始值。为了支持设定一个初始值或者其他自定义操作。属性包装器需要添加一个构造器。这是 TwelveOrLess
的扩展版本,称为 SmallNumber
。 SmallNumber
定义了能设置被包装值和最大值的构造器:
@propertyWrapper
struct SmallNumber {private var maximum: Intprivate var number: Intvar wrappedValue: Int {get { return number }set { number = min(newValue, maximum) }}init() {maximum = 12number = 0}init(wrappedValue: Int) {maximum = 12number = min(wrappedValue, maximum)}init(wrappedValue: Int, maximum: Int) {self.maximum = maximumnumber = min(wrappedValue, maximum)}
}
SmallNumber
的定义包括三个构造器——init()
、init(wrappedValue:)
和 init(wrappedValue:maximum:)
——下面的示例使用这三个构造器来设置被包装值和最大值。
当你把包装器应用于属性且没有设定初始值时,Swift 使用 init()
构造器来设置包装器。举个例子:
struct ZeroRectangle {@SmallNumber var height: Int@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// 打印 "0 0"
调用 SmallNumber()
来创建包装 height
和 width
的 SmallNumber
的实例。构造器内部的代码使用默认值 0 和 12 设置初始的被包装值和初始的最大值。像之前使用在 SmallRectangle
中使用 TwelveOrLess
的例子,这个属性包装器仍然提供所有的初始值。与这个例子不同的是,SmallNumber
也支持把编写这些初始值作为声明属性的一部分。
当你为属性指定初始值时,Swift 使用 init(wrappedValue:)
构造器来设置包装器。举个例子:
struct UnitRectangle {@SmallNumber var height: Int = 1@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 打印 "1 1"
当你对一个被包装的属性写下 = 1
时,这被转换为调用 init(wrappedValue:)
构造器。调用 SmallNumber(wrappedValue: 1)
来创建包装 height
和 width
的 SmallNumber
的实例。构造器使用此处指定的被包装值,且使用的默认最大值为 12
。
当你在自定义特性后面把实参写在括号里时,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)
// 打印 "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// 打印 "5 4"
调用 SmallNumber(wrappedValue: 2, maximum: 5)
来创建包装 height
的 SmallNumber
的一个实例。调用 SmallNumber(wrappedValue: 3, maximum: 4)
来创建包装 width
的 SmallNumber
的一个实例。
通过将实参包含到属性包装器中,你可以设置包装器的初始状态,或者在创建包装器时传递其他的选项。这种语法是使用属性包装器最通用的方法。你可以为这个属性提供任何所需的实参,且它们将被传递给构造器。
当包含属性包装器实参时,你也可以使用赋值来指定初始值。Swift 将赋值视为 wrappedValue 参数,且使用接受被包含的实参的构造器。举个例子:
struct MixedRectangle {@SmallNumber var height: Int = 1@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 打印 "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// 打印 "12"
2、从属性包装器中呈现一个值
除了被包装值,属性包装器可以通过定义被呈现值暴露出其他功能。举个例子,管理对数据库的访问的属性包装器可以在它的被呈现值上暴露出 flushDatabaseConnection()
方法。除了以货币符号(\$
)开头,被呈现值的名称和被包装值是一样的。因为你的代码不能够定义以 $ 开头的属性,所以被呈现值永远不会与你定义的属性有冲突。
在之前 SmallNumber
的例子中,如果你尝试把这个属性设置为一个很大的数值,属性包装器会在存储这个数值之前调整这个数值。以下的代码把被呈现值添加到 SmallNumber
结构体中来追踪在存储新值之前属性包装器是否为这个属性调整了新值。
@propertyWrapper
struct SmallNumber {private var number: Intprivate(set) var projectedValue: Boolvar wrappedValue: Int {get { return number }set {if newValue > 12 {number = 12projectedValue = true} else {number = newValueprojectedValue = false}}}init() {self.number = 0self.projectedValue = false}
}
struct SomeStructure {@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// 打印 "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// 打印 "true"
写下 someStructure.$someNumber
即可访问包装器的被呈现值。在存储一个比较小的数值时,如 4 ,someStructure.$someNumber
的值为 false
。但是,在尝试存储一个较大的数值时,如 55 ,被呈现值变为 true。
属性包装器可以返回任何类型的值作为它的被呈现值。在这个例子里,属性包装器要暴露的信息是:那个数值是否被调整过,所以它暴露出布尔型值来作为它的被呈现值。需要暴露出更多信息的包装器可以返回其他数据类型的实例,或者可以返回自身来暴露出包装器的实例,并把其作为它的被呈现值。
当从类型的一部分代码中访问被呈现值,例如属性 getter
或实例方法,你可以在属性名称之前省略 self.
,就像访问其他属性一样。以下示例中的代码用 $height
和 $width
引用包装器 height
和 width
的被呈现值:
enum Size {case small, large
}
struct SizedRectangle {@SmallNumber var height: Int@SmallNumber var width: Intmutating func resize(to size: Size) -> Bool {switch size {case .small:height = 10width = 20case .large:height = 100width = 100}return $height || $width}
}
因为属性包装器语法只是具有 getter
和 setter
的属性的语法糖,所以访问 height
和 width
的行为与访问任何其他属性的行为相同。举个例子,resize(to:)
中的代码使用它们的属性包装器来访问 height
和 width
。如果调用 resize(to: .large)
,.large
的 switch case 分支语句把矩形的高度和宽度设置为 100。属性包装器防止这些属性的值大于 12,且把被呈现值设置成为 true
来记下它调整过这些值的事实。在 resize(to:)
的最后,返回语句检查 $height
和 $width
来确认是否属性包装器调整过 height
或 width
。
五、全局变量和局部变量
计算属性和观察属性所描述的功能也可以用于全局变量和局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
注意
全局的常量或变量都是延迟计算的,跟 延时加载存储属性 相似,不同的地方在于,全局的常量或变量不需要标记
lazy
修饰符。 局部范围的常量和变量从不延迟计算。
可以在局部存储型变量上使用属性包装器,但不能在全局变量或者计算型变量上使用。比如下面的代码,myNumber
使用 SmallNumber
作为属性包装器。
func someFunction() {@SmallNumber var myNumber: Int = 0myNumber = 10// 这时 myNumber 是 10myNumber = 24// 这时 myNumber 是 12
}
就像将 SmallNumber
应用到属性上一样,将 myNumber
赋值为 10 是有效的。而因为这个属性包装器不允许值大于 12,将 myNumber
赋值为 24 时则会变成 12。
六、类型属性
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。
类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
注意
跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用
lazy
修饰符。
1、类型属性语法
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为 global(全局)静态变量定义的。但是在 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}
}
注意
例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
2、获取和设置类型属性的值
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如:
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”
相关文章:

Swift 属性
属性 一、存储属性1、常量结构体实例的存储属性2、延时加载存储属性3、存储属性和实例变量 二、计算属性1、简化 Setter 声明2、简化 Getter 声明3、只读计算属性 三、属性观察器四、属性包装器1、设置被包装属性的初始值2、从属性包装器中呈现一个值 五、全局变量和局部变量六…...

基于maxkey接入jeecgboot并实现账户同步
1. 注册应用 1.1 在统一认证中心注册第三方应用 1.1.1 填写应用名和登录地址 1.1.2 填写认证地址授权方式和作用域 1.1.3 选择权限范围并提交 1.2 配置访问权限 1.2.1 指定用户组 1.1.2 选择注册的应用 1.1.3 在单点登录认证页面查看添加的应用 1.3 同步一个第三方应用的账号…...

kafka Kerberos集群环境部署验证
背景 公司需要对kafka环境进行安全验证,目前考虑到的方案有Kerberos和SSL和SASL_SSL,最终考虑到安全和功能的丰富度,我们最终选择了SASL_SSL方案。处于知识积累的角度,记录一下kafka keberos安装部署的步骤。 机器规划 目前测试环境公搭建了三台kafka主机服务,现在将详细…...

[C++]debug介绍+debug时如何查看指针指向内存处的值
一、简介 预备工具和知识:使用使用VSCode使用Debug。 本文简介:本文将简要介绍debug中Continue,Step Over,Step Into和Restart的功能。并介绍如何在debug时查看动态内存地址(指针)的值; 二、D…...
AI学习指南数学工具篇-凸优化在支持逻辑回归中的应用
AI学习指南数学工具篇-凸优化在支持逻辑回归中的应用 一、引言 在人工智能领域,逻辑回归是一种常见的分类算法,它通过学习样本数据的特征和标签之间的关系,来进行分类预测。而在逻辑回归算法中,凸优化是一种重要的数学工具&…...
Flutter 中的 AspectRatio 小部件:全面指南
Flutter 中的 AspectRatio 小部件:全面指南 Flutter 是一个流行的跨平台 UI 框架,它提供了丰富的小部件来帮助开发者构建高质量的应用程序。在 Flutter 的小部件库中,AspectRatio 是一个非常有用的小部件,它允许开发者以一种简单…...

应用程序中的会话管理和Cookie安全指南
应用程序中的会话管理和Cookie安全指南 在现代应用程序中,会话管理和Cookie安全是确保用户信息和数据安全的重要组成部分。本文将详细介绍会话管理的最佳实践以及如何通过安全的Cookie设置来保护会话ID的交换。 单点登录(SSO)及会话管理机制…...

备战秋招c++ 【持续更新】
T1 牛牛的快递 原题链接:牛牛的快递_牛客题霸_牛客网 (nowcoder.com) 题目类型:模拟 审题&确定思路: 1、超过1kg和不足1kg有两种不同收费方案 ---- 起步价问题 2、超出部分不足1kg的按1kg计算 ----- 向上取整 3、向上取整的实现思路…...
整数拆分~
way:process //上一个拆出来的数是pre //还剩下rest需要去拆 //返回拆解的方法数 #include<iostream> using namespace std;//上一个拆出来的数是pre //还剩下rest需要去拆 //返回拆解的方法数 int process(int pre, int rest) {if(rest0) return 1;//因为后…...

【Qt Creator】跨平台的C++图形用户界面应用程序开发框架---QT
🍁你好,我是 RO-BERRY 📗 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 目录 1.互联网的核心岗位以及职…...

KingbaseES数据库物理备份还原sys_rman
数据库版本:KingbaseES V008R006C008B0014 简介 sys_rman 是 KingbaseES 数据库中重要的物理备份还原工具,支持不同类型的全量备份、差异备份、增量备份,保证数据库在遇到故障时及时使用 sys_rman 来恢复到数据库先前状态。 文章目录如下 1.…...
【CV】视频图像背景分割MOG2,KNN,GMG
当涉及背景分割器(Background Subtractor)时,Mixture of Gaussians(MOG2)、K-Nearest Neighbors(KNN)和Geometric Multigid(GMG)是常用的算法。它们都用于从视频流中提取…...
使用 Python 简单几步去除 PDF 水印
推荐一个AI网站,免费使用豆包AI模型,快去白嫖👉海鲸AI 在处理 PDF 文件时,水印有时会影响文件的可读性或美观性。幸运的是,Python 提供了多种库来操作 PDF 文件,其中 PyMuPDF(又名 fitz…...

【香橙派 AIpro】OrangePi AIpro :教育、机器人、无人机领域的超级AI大脑,华为昇腾处理器驱动的AI开发板新标杆
【OrangePi AIpro:教育、机器人、无人机领域的超级AI大脑,华为昇腾处理器驱动的AI开发板新标杆】 文章目录 一、开箱与初印象1. 初印象2. 上手开机3. 安装和运行 TightVNC 远程桌面3.1. 安装 TightVNC 服务器3.2. 启动 VNC 服务器3.3. 在 Windows 上使用…...

【Mac】 CleanMyMac X for mac V4.15.2中文修复版安装教程
软件介绍 CleanMyMac X是一款为Mac设计的优秀软件,旨在帮助用户优化其设备的性能并提供清理和维护功能。以下是 CleanMyMac X的一些主要功能和特点: 1.系统性能优化:软件可以扫描和修复潜在的性能问题,包括无效的登录项、大文件…...

单片机通信协议(1):SPI简介
关于SPI SPI(串行外设接口)是板载设备间通信接口之一。它是由摩托罗拉公司(飞思卡尔半导体)推出的。由于其简单性和通用性,它被纳入各种外围设备中,并与飞利浦I2C总线并列。 SPI的三线或四线信号数量比IIC…...

免税商品优选购物商城,基于 SpringBoot+Vue+MySQL 开发的前后端分离的免税商品优选购物商城设计实现
目录 一. 前言 二. 功能模块 2.1. 登录界面 2.2. 管理员功能模块 2.3. 商家功能模块 2.4. 用户前台功能模块 2.5. 用户后台功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过…...
京准电子、NTP电子时钟系统(网络时钟系统)概述
京准电子、NTP电子时钟系统(网络时钟系统)概述 京准电子、NTP电子时钟系统(网络时钟系统)概述 时钟系统工作原理是由母钟接收GPS/北斗卫星的时间信息,母钟通过串口和NTP以太网接口为其他各系统提供统一的标准时间信号&…...

【常用的队列总结】
文章目录 队列的介绍Queue队列的基本概念与操作队列的基本概念 常见的队列介绍非阻塞队列LinkedList:ArrayDeque:PriorityQueue: 阻塞队列ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue DelayQueueSynchronousQueue 队列的介绍 Queue队列的基本概念与操作 在 …...
机器学习过拟合和欠拟合!看这一篇文章就够了 建议收藏!(上篇)
在机器学习中,有一项非常重要的概念,那就是:过拟合(Overfitting)和欠拟合(Underfitting)。 它们涉及到机器学习中常见的两种模型性能问题,分别表示模型在训练数据上表现得过于复杂或…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...

对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...
用 FFmpeg 实现 RTMP 推流直播
RTMP(Real-Time Messaging Protocol) 是直播行业中常用的传输协议。 一般来说,直播服务商会给你: ✅ 一个 RTMP 推流地址(你推视频上去) ✅ 一个 HLS 或 FLV 拉流地址(观众观看用)…...
基于Java项目的Karate API测试
Karate 实现了可以只编写Feature 文件进行测试,但是对于熟悉Java语言的开发或是测试人员,可以通过编程方式集成 Karate 丰富的自动化和数据断言功能。 本篇快速介绍在Java Maven项目中编写和运行测试的示例。 创建Maven项目 最简单的创建项目的方式就是创建一个目录,里面…...