属性将值跟特定的类、结构或枚举关联。
-
存储属性: 存储常量或变量作为实例的一部分。
- 计算属性可以用于 类、结构体 和 枚举
-
计算属性: 计算(不是存储)一个值。
- 存储属性只能用于 ** 类** 和 结构体。*
属性分为 实例属性 和 类型属性
1. 存储属性
存储属性:就是存储在特定类或结构体实例里的一个 常量 或 变量。
- 变量存储属性(用关键字 var 定义)
- 常量存储属性(用关键字 let 定义)
** 存储属性的默认值 **
- 存储属性可以在定义的时候赋值默认值。
- 可以在构造过程中设置和修改存储属性的值。(常量的值只可以在构造函数中进行修改)
常量结构体的存储属性
创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行。
(常量结构体的变量属性是不能被修改的,这个是由于结构体的值类型决定)
** 延迟存储属性**
延迟存储属性是指 当第一次被调用的时候 才会计算其 初始值 的属性。在属性声明前使用 lazy 来标示一个延迟存储属性。
- 延迟存储属性必须用 var 声明
- 被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。(加锁)
实例属性使用场景
- 属性值对实例的构造过程不依赖
- 属性的值需要经过大量计算才可以得到(耗时)
(加载本地文件数据就可以使用延迟属性)
2. 计算属性
- ** 计算属性不直接存储值,**而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
- 计算属性只能用 var 来声明。(由于计算属性值的不确定性决定)
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()
// 计算属性, 中心点是依赖 origin 和 size 计算得出具体的值。本身不能保存值。
var center: Point {
// getter
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
// setter (可选的)
/**
set 方法默认有一个参数 newValue 用来表示我们传入的值。
我可以对set 的参数名称进行进行修改,增强代码的可读性。
这个是 编辑 set 声明的使用,直接使用 newValue 并且 set 的参数可以省略。
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
*/
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
只读计算属性:
只有 getter 没有 setter 的计算属性就是只读计算属性。
(只读计算属性总是返回一个固定的值,不能赋新值)
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
// 只读计算属性,可以省略 get 个花括号
var volume: Double {
return width * height * depth
}
}
3. 属性观察器(存储属性和计算属性都可以添加属性观察器)
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同。
- 延迟存储属性 不能有 属性观察器。(重点)
- 可以通过 重写 属性的方式为 继承的属性(包括存储属性和计算属性)添加属性观察器。
- 可以给所有的存储属性添加属性观察器。
- 不必为 非重写 (非继承) 的 ** 计算属性 ** 添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。
理论上属性观察器是可以给所有的属性添加。(不用区分是否是存储是否是计算。主要是要区分一下使用的场景)
可以为属性添加如下一个或多个属性观察器:
- willSet: 在新的值被设置之前调用
- willSet 观察器会将新的属性值作为 常量参数 传入。
在 willSet 的实现代码中可以为这个参数指定一个名称,不指定可以使用默认参数 newValue。
- willSet 观察器会将新的属性值作为 常量参数 传入。
- didSet: 在新的值被设置之后调用
- didSet 观察器会将旧值作为参数传入。
在 didSet 的实现代码中可以为这个参数指定一个名称, 不指定可以使用默认的参数 oldValue
- didSet 观察器会将旧值作为参数传入。
class StepCounter {
// 这是一个存储属性
var totalSteps: Int = 0 {
// willSet 属性观察器 , 指定的传入参数名称为 newTotalSteps。
/*
// 这个为使用默认参数
willSet {
print("About to set totalSteps to \(newValue)")
}
*/
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
/*
// 自己定义一个参数使用,主要是为了代码的语义化。
didSet ( tempOldValue ) {
// totalSteps 就是属性本身。
if totalSteps > tempOldValue {
print("Added \(totalSteps - tempOldValue) steps")
}
}
*/
// 这个为使用默认参数
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
存储属性的使用注意:
- 父类的属性在子类中赋值时,父类的 属性观察器会先被调用,后才会调用子类的属性观察器。
- 在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。
- 属性是通过 in-out 方式传入, 属性观察器也会被调用。
* in-out 是用的是值的拷贝使用, 使用的是值的拷贝,值使用完毕后再将值拷贝出来。
4. 全局变量 和 局部变量
全局变量:是在函数、方法、闭包或任何类型之外定义的变量。
局部变量:是在函数、方法或闭包内部定义的变量。
全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
注意:
全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记lazy修饰符。
局部范围的常量或变量从不延迟计算。
5. 类型属性
实例属性: 特定类型的实例的属性叫实例属性。实例之间的属性是相互独立的。
类型属性:特定类型的属性叫实例属性。类型之间的属性是共享的。只有唯一一份。
(类型属性: 特么第一眼我也没看明白。其实就是特么写在类里面的静态变量和静态常量)
使用 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 表示不允许子类重写
static var computedTypeProperty: Int {
return 27
}
// 使用 class 表示允许子类重写。
class var overrideableComputedTypeProperty: Int {
return 107
}
}