属性

属性将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。

属性也可以直接作用于类型本身,这种属性称为类型属性。

可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作.可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。

存储属性

存储属性就是存储在特定类或结构体实例里的一个常量或变量,可以为变量或者常量
可以在定义存储属性时指定默认值,也可在构造过程中设置或修改存储属性值,甚至修改常量存储属性值

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
rangeOfThreeItems.firstValue = 6

定义一个结构体,包含firstValue变量存储属性,和length常量存储属性。length在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值

常量结构体的存储属性

若结构体声明为常量,则无法修改实例的任何属性,即使为变量属性:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = 6//报错不可修改
// 尽管 firstValue 是个变量属性,这里还是会报错

这是由于结构体为值类型,值类型为常量则所有属性也变为常量

注意

对于引用类型的类来说,将引用类型赋值给一个常量,仍然可以修改实例的变量属性

延迟存储属性

延迟属性第一次调用时才会结算其初始化值的属性
在属性声明前加lazy来标示一个延迟存储属性

注意:

必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

class DataImporter {
    /*
    DataImporter 是一个负责将外部文件中的数据导入的类。
    这个类的初始化会消耗不少时间。
    */
    var fileName = "data.txt"
    // 这里会提供数据导入功能
}
class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 这里会提供数据管理功能
}
let manager = DataManager()
manager.data.append("Some data")
//此时实例的import属性还没创建
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”

注意

如果一个被标记为lazy的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次**

存储属性和实例变量

与OC不同Swift没有属性对应的实例变量,因此没有办法直接访问属性的后端存储,方便统一访问方式
使属性的全部信息包括命名、类型和内存管理特征都在一个地方定义

计算属性

计算属性不存储值而是提供一个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()
    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))")

可以通过square.center访问计算属性center 会调用getter计算新的值返回
center可以设置一个新值,会调用setter来修改origin来移动正方形

简化setter声明

如果计算属性的setter没有定义表示新值,就可以使用默认名称newValue

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没有setter的计算属性就是只读计算属性,可以访问值,不能设置新值

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

只读属性可以省略掉 get关键字和花括号

属性观察器

属性观察器监控和响应属性值变化,每次属性被设置时会调用属性观察器,即使新值和当前值相同时也不例外

可以为除了延迟加载存储属性外其他存储属性添加属性观察器,对于继承的存储属性和计算属性,通过重写的方式来添加属性观察器
对于非继承的计算属性没必要添加属性观察器,我们可以通过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

注意

在willSet或者didSet方法中设置属性值是不会触发的属性观察器方法

如果将属性通过in-out方式传入函数,willSetdidSet也会调用。这是因为in-out参数采用了拷入拷出模式:即在函数内部使用的是参数的copy,函数结束后,又对参数重新赋值。

全局变量和局部变量

计算属性和属性观察器所描述的功能也可以用于全局变量和局部变量

我们之前用的变量都是存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入

我们可以在全局或局部范围为存储型变量定义观察器

在全局或局部范围都可以定义计算型变量。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样

注意:

  1. 全局的常量或变量都是延迟计算,类似延迟属性但是不需要lazy声明
  2. 局部的常量或变量从不延迟计算

类型属性

可以为类型本身定义属性

类型属性用于定义某个类型所有实例共享的数据

类型属性也可以有 存储型的常量或变量 计算型的只能为变量

注意:

存储型类型属性必须指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值

存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用lazy修饰符。

类型属性语法

OC中,在类中关联的静态变量和常量是作为全局静态变量定义
Swift中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,作用范围就在类型支持的范围内

使用static来定义类型属性。

在为类定义计算型类型属性时,可以改用关键字class来支持子类对父类的实现进行重写

注意

只有计算型类型属性,采用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)
    // 打印 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
    // 打印 "Another value.”
print(SomeEnumeration.computedTypeProperty)
    // 打印 "6"
print(SomeClass.computedTypeProperty)
    // 打印 "27"