★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ ➤微信公众号:山青咏芝(shanqingyongzhi) ➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/) ➤GitHub地址:https://github.com/strengthen/LeetCode ➤原文地址:https://www.cnblogs.com/strengthen/p/9729365.html ➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。 ➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创! ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
热烈欢迎,请直接点击!!!
进入博主App Store主页,下载使用各个作品!!!
注:博主将坚持每月上线一个新app!!!
属性将值与特定的类,结构或枚举关联。存储的属性将常量和变量值存储为实例的一部分,而计算的属性将计算(而不是存储)值。计算的属性由类,结构和枚举提供。存储的属性仅由类和结构提供。
存储和计算的属性通常与特定类型的实例相关联。但是,属性也可以与类型本身关联。这样的属性称为类型属性。
此外,您可以定义属性观察器以监视属性值的更改,您可以使用自定义操作对其进行响应。可以将属性观察器添加到您自己定义的存储属性中,也可以添加到子类从其超类继承的属性中。
您还可以使用属性包装器在多个属性的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 和a height 。
-
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"
写入
|
请发表评论