Swift之属性

知识点:存储属性、计算属性(setter、getter)、类型属性、属性观察(property observation)、属性访问控制

存储属性

存储属性就是存储在特定类或结构体实例里的常量或变量。只能用于类和结构体。分为变量存储属性和常量存储属性。

定义存储属性的时候指定默认值,也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值。

关于构造过程中常量属性的修改说明:

你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可修改。对于类的实例,它的常量属性只能在定义它的类的构造过程中修改,不能在子类中修改。

示例:定义一个结构体FixedLengthRange,用于描述整数的范围,且这个范围值在被创建后不能被修改。

1
2
3
4
5
6
7
8
9
10
11
struct FixedLengthRange{
var firstValue: Int
let length: Int
}

//length在创建实例的时候被初始化,之后无法修改它的值
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
//该区间表示整数0,1,2

rangeOfThreeItems.firstValue = 6
//该区间现在表示整数6,7,8
常量结构体的存储属性

如果创建结构体实例赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量。因为结构体属于值类型,当值类型的实例被声明为常量的时候,它的所有属性也就成了常量

1
2
3
4
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0,1,2,3
rangeOfFourItems.firstValue = 5
//尽管 firstValue 是个变量属性,这里还是会报错
延迟存储属性
  1. 延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。

    延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用lazy来标示一个延迟存储属性。

  2. 必须将延迟存储属性声明成变量(使用var关键字)

    因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

  3. 延迟存储属性只会被计算一次。

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

  4. 使用场景:当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以使用延迟存储属性以便只在需要的时候计算它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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 属性还没有被创建

    //由于使用了 lazy ,importer 属性只有在第一次被访问的时候才被创建:
    print(manager.importer.fileName)
    // DataImporter 实例的 importer 属性现在被创建了
    // 输出 "data.txt”
存储属性和实例变量

在Swift中,属性没有对应的实例变量,属性的后端存储也无法直接访问。

计算属性

  1. 可用于类、结构体和枚举。

  2. 计算属性不直接存储值,而是提供一个getter和一个可选的setter,来间接获取和设置其他属性或变量的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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.origin is now at (10.0, 10.0)”
简化setter

如果计算属性的setter没有定义表示新值的参数名,可以使用默认名称。简化用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。

必须使用var关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。

示例:

1
2
3
4
5
6
7
8
9
10
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)")

属性观察器(property observation)

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

可为属性添加的观察器:

  • willSet在新的值被设置之前调用
  • didSet在新的值被设置之后立即调用

willSet观察器会将新的属性值作为常量参数传入,在willSet的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称newValue表示。

同样didSet观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名oldValue。如果在didSet方法中再次对该属性赋值,那么新值会覆盖旧的值。

父类的属性在子类的构造器中被赋值时,它在父类中的willSetdidSet观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方式传入函数,willSetdidSet也会调用。这是因为in-out参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。

全局变量和局部变量

全局变量是在函数、方法、闭包或任何类型之外定义的变量。
局部变量是在函数、方法、或闭包内部定义的变量。

全局变量和局部变量都属于存储型变量,为特定类型的值提供存储空间,并允许读取和写入。

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

全局的常量或变量都是延迟计算的,跟延迟存储属性相似。不同点在于,全局的常量或变量不需要标记lazy修饰符。局部范围的常量或变量从不延迟计算。

类型属性

定义:为类型本身定义属性,无论创建多少该类的实例,这些属性都只有唯一一份。

使用场景:用于定义某个类型所有实例共享的数据。比如所有实例都能用的一个常量,或者所有实例都能访问的一个变量。

类型:存储型类型属性可以是变量或常量,计算型类型属性只能定义成变量。

存储类型属性必须有默认值,因为类型没有构造器,无法在初始化过程中给类型属性赋值。存储型类型属性是延迟初始化的,它们只有在第一次被访问时才会被初始化。即使被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要使用lazy修饰符。

使用:Swift中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用作为也就在类型支持的范围内。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct SomeStructure{
static var storedTypeProperty = "Some value."
static var comeputedTypeProperty :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 12
}
//子类可以重写的类型属性
class var overrideableComputedTypeProperty: Int{
return 25
}
}

//子类重写父类的类型属性
class AnotherClass: SomeClass{
override class var overrideableComputedTypeProperty: Int{
return 10
}
}

print(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "Another value."
print(SomeEnumeration.comeputedTypeProperty)
print(SomeClass.computedTypeProperty)
print(AnotherClass.overrideableComputedTypeProperty)

访问控制

访问控制指给某些组件访问其他组件的一定级别的访问权限。
模块(module):分发代码的单位,import关键字可以把一个模块引入另一个模块。

源代码文件(source file):表示一个文件,并且存在于特定的模块。

Swift的五个访问层级:

访问层级 描述 对……可见 能在……中继承
open 实体对模块内的所有文件以及引入了该模块的文件都可见,并且可以继承 所在模块以及引入实体所在模块的模块 所在模块以及引入实体所在模块的模块
public 实体对模块内的所有文件以及引入了该模块的文件都可见 所在模块以及引入实体所在模块的模块 所在的模块
internal(默认层级) 实体对模块内的文件可见 所在的模块 所在的模块
fileprivate 实体只对所在的源文件可见 所在的源文件 所在的文件
private 实体只对所在的作用域可见 所在的作用域 所在的作用域
1
2
3
4
5
6
7
class People{
private var language: String = "Chinese"
func PrintDesc(){
print("People's language is \(language)")
}
}

控制setter和getter方法的可见度

默认情况下,setter和getter的可见度相同。

改写可见度示例:

1
2
3
class Zombie{
internal private(set) var isFallingApart = false
}