➽09属性
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了➽09属性相关的知识,希望对你有一定的参考价值。
属性将值与特定的类、结构体或枚举关联。存储属性会将常量和变量存储为实例的一部分,而计算属性则是直接计算(而不是存储)值。计算属性可以用于类、结构体和枚举,而存储属性只能用于类和结构体。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接与类型本身关联,这种属性称为类型属性。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
存储属性
在最简单的形式中,存储属性是作为特定类或结构实例的一部分存储的常量或变量。存储属性可以是变量存储属性(由var关键字引入)或常量存储属性(由let关键字引入)。
可以为存储的属性提供默认值作为其定义的一部分,还可以在初始化期间设置和修改存储属性的初始值,即使对于常量存储的属性也是如此。
struct FixedLengthRange
var firstValue: Int// 变量属性
let length: Int// 常量属性
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)// 创建后length属性就不可更改了
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
常量结构体实例的存储属性
如果创建结构的实例并将该实例指定给常量,则无法修改该实例的属性,即使它们已声明为变量属性:
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
这种行为是由于结构体是值类型。当值类型的实例标记为常量时,其所有属性也标记为常量。
对于类,情况并非如此,类是引用类型。如果将引用类型的实例指定给常量,仍然可以更改该实例的变量属性。
懒存储属性
懒存储属性是一种直到第一次使用时才计算其初始值的属性。通过在其声明之前写入lazy
修饰符来指示延迟存储的属性。
必须始终将懒属性声明为变量(使用var关键字),因为在实例初始化完成之前可能无法检索其初始值。在初始化完成之前,常量属性必须始终具有值,因此不能声明为lazy。
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实例可以在不从文件导入数据的情况下管理其数据,因此在创建DataManager本身时,DataManager不会创建新的DataImporter实例。相反,在首次使用DataImporter实例时创建该实例更有意义。
因为它是用lazy修饰符标记的,所以导入器属性的DataImporter实例仅在首次访问importer属性时创建,例如在查询其filename属性时:
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
如果标记有lazy修饰符的属性被多个线程同时访问,并且该属性尚未初始化,则不能保证该属性只初始化一次。
存储属性和实例变量
如果您有过 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 now at (\\(square.center.x), \\(square.center.y))")
// Prints "square.center is now at (5.0, 5.0)"
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)"
速记getter和setter声明
如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果,可以省略return关键字。
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue
。
下面是另一个版本的 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)
只读计算属性
只有 getter 没有 setter 的计算属性叫只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只读计算属性的声明可以省略 get 关键字、花括号和return关键字:
struct Cuboid
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double 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"
结构体有一个名为 volume 的只读计算属性用来返回立方体的体积。
属性观察者
属性观察者监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察者,即使新值和当前值相同的时候也不例外。
你可以为除了延时加载存储属性之外的其他存储属性添加属性观察者,你也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察者。你不必为非重写的计算属性添加属性观察者,因为你可以直接通过它的 setter 监控和响应值的变化。
可以为属性添加其中一个或两个观察者:
● willSet
在新的值被设置之前调用
● didSet
在新的值被设置之后调用
willSet 观察者会将新的属性值作为常量参数传入,在 willSet 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 newValue
表示。
同样,didSet 观察者会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 oldValue。如果在 didSet 方法中再次对该属性赋值,那么新值会覆盖旧的值。
在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 willSet 和 didSet 观察者。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察者。
示例:用来统计一个人步行时的总步数。
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
如果将带有观察器的属性通过 in-out 方式传入函数,willSet 和 didSet 也会调用。这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。
属性包装器
属性包装器在管理属性存储方式的代码和定义属性的代码之间添加了一层分隔。例如,如果您有提供线程安全检查或将其底层数据存储在数据库中的属性,则必须在每个属性上编写该代码。使用属性包装时,在定义包装时只需编写一次管理代码,然后通过将其应用于多个属性来重用该管理代码。
要定义属性包装器,可以创建一个定义wrappedValue
属性的结构、枚举或类。
在下面的代码中,TwelveOrLess结构确保其包装的值始终包含小于或等于12的数字。如果你让它存储一个更大的数字,它会存储12:
@propertyWrapper
struct TwelveOrLess
private var number = 0
var wrappedValue: Int
get number
set number = min(newValue, 12)
上面示例中的number声明将变量标记为
private
,这确保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
将包装应用于属性时,编译器将合成为包装提供存储的代码和通过包装提供对属性访问的代码。(属性包装器负责存储包装的值,因此没有用于该值的合成代码。)您可以编写使用属性包装器行为的代码,而无需利用特殊的属性语法。
例如,下面是上一个代码清单中的SmallRectangle版本,它将属性显式包装在TwelveOrLess结构中,而不是将@TwelveOrLess作为属性写入:
struct SmallRectangle
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int
get _height.wrappedValue
set _height.wrappedValue = newValue
var width: Int
get _width.wrappedValue
set _width.wrappedValue = newValue
_height和_width属性存储属性包装器的实例TwelveOrLess。对wrappedValue属性进行高度和宽度换行访问的getter和setter。
为包装的属性设定初值
Swift使用init()
初始值设定项设置包装器。
下面示例定义了设置包装值和最大值的初始值设定项:
@propertyWrapper
struct SmallNumber
private var maximum: Int
private var number: Int
var wrappedValue: Int
get 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)
当你将包装应用于属性且未指定初始值时,会调用init()完成初始化:
struct ZeroRectangle
@SmallNumber var height: Int
@SmallNumber var width: Int
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
为属性指定初始值时,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"
当使用包装器在属性上写入=1时,这将转换为对init(wrappedValue:)初始值设定项的调用。通过调用SmallNumber(wrappedValue:1)创建包裹高度和宽度的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)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 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"
mixedRectangle.width = 20
print(mixedRectangle.width)
// Prints "9"
从属性包装器映射值
除了包装的值之外,属性包装器还可以通过定义映射值来公开其他功能。例如,管理对数据库访问的属性包装器可以在其映射值上公开flushDatabaseConnection()方法。映射值的名称与包装值相同,只是它以美元符号(
)
开
头
。
因
为
您
的
代
码
无
法
定
义
以
)开头。因为您的代码无法定义以
)开头。因为您的代码无法定义以开头的属性,所以映射值永远不会干扰您定义的属性。
在上面的SmallNumber示例中,如果试图将属性设置为太大的数字,则属性包装器会在存储之前调整该数字。下面的代码将projectedValue属性添加到SmallNumber结构中,以跟踪属性包装器在存储新值之前是否调整了该属性的新值。
要在变量类型前加(set)
关键字(包括括号),以表明这个属性是要被映射的。
@propertyWrapper
struct SmallNumber
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int
get number
set
if newValue > 12
number = 12
projectedValue = true
else
number = newValue
projectedValue = false
init()
self.number = 0
self.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. s o m e N u m b e r 访 问 包 装 器 的 映 射 值 。 存 储 一 个 小 的 数 字 ( 如 4 ) 后 , s o m e S t r u c t u r e . someNumber访问包装器的映射值。存储一个小的数字(如4)后,someStructure. someNumber访问包装器的映射值。存储一个小的数字(如4)后,someStructure.someNumber的值为false。但是,在尝试存储过大的数字(如55)后,值为true。
属性包装器可以返回任何类型的值作为其映射值。在本例中,属性包装器仅公开一条信息,说明是否已调整该数字,以便将该布尔值作为其映射值公开。需要公开更多信息的包装器可以返回其他数据类型的实例,也可以返回self
以公开包装器的实例作为其映射值。
当您从属于类型一部分的代码中访问映射值时,如属性获取程序或实例方法,您可以省略self。在属性名之前,就像访问其他属性一样。以下示例中的代码将围绕高度和宽度的包装的映射值引用为 h e i g h t 和 height和 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:)中的代码使用属性包装器访问高度和宽度。如果调用resize(to: .large),则.large的开关大小写将矩形的高度和宽度设置为100。包装器防止这些属性的值大于12,并将映射值设置为true,以记录调整其值的事实。在resize(to:)的末尾,return语句检查 h e i g h t 和 height和 height和width,以确定属性包装器是否调整了高度或宽度。
全局变量和局部变量
计算属性和观察属性所描述的功能也可以用于全局变量和局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
全局的常量或变量都是延迟计算的,跟懒存储属性相似,不同的地方在于,全局的常量或变量不需要标记 lazy 修饰符。
局部范围的常量和变量从不延迟计算。
可以将属性包装应用于本地存储变量,但不能应用于全局变量或计算变量。例如,在下面的代码中,myNumber使用SmallNumber作为属性包装器。
func someFunction()
@SmallNumber var myNumber: Int = 0
myNumber = 10
// now myNumber is 10
myNumber = 24
// now myNumber is 12
与将SmallNumber应用于属性时一样,将myNumber的值设置为10是有效的。因为属性包装不允许大于12的值,所以它将myNumber设置为12而不是24。
类型属性
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。
类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 lazy 修饰符。
类型属性语法
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为 global(全局)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
使用关键字 static
来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class
来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
struct SomeStructur以上是关于➽09属性的主要内容,如果未能解决你的问题,请参考以下文章
09.05 javascript 属性 内置属性 自定义属性 DOM文档对象模型