Swift基础语法: 26 - Swift的存储属性, 计算属性, 属性监视器

前面讲完了类和结构体,现在我们来讲讲属性,在Swift当中也有属性一说,但这个属性的概念和我们之前接触的不太一样.

在Swift中有两种属性,一种是计算属性,另一种是存储属性,计算属性可以用于类、结构体和枚举里,存储属性只能用于类和结构体,让我们一起来看看吧:

1.存储属性

简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量,存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。

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

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0,length: 3)
// 该区间表示整数0,1,2
println(rangeOfThreeItems.firstValue)
// 打印出来的结果: 0

rangeOfThreeItems.firstValue = 6
// 现在的去见是 6,7,8
println(rangeOfThreeItems.firstValue)
// 打印出来的结果: 6

这里要注意一下,只有在定义var变量时才可以对结构体里面的变量进行修改,如果是let的话,就会报错,比如:

rangeOfThreeItems.length = 3
// 报错: Connot assign to 'length' in 'rangeOfThreeItems'

2.延迟存储属性

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

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")

println(manager.importer.fileName)
// 打印出来的结果: data.txt

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

解释一下这个例子,DataManager这个类里面有一个用来存储字符串数组的变量data,而DataManager需要一个用来导入文件数据的类,这个功能由Dataimporter完成,但在这里一个问题,Dataimporter这个类如果要初始化的话,那么就会影响一定的性能,因为它在初始化的时候还有可能要去打开文件,并且还要读取到文件内容,所以在实例的时候,没必去创建一个Dataimporter,而是在我们需要用到的时候才去创建,而lazy就是为了这个理念而创建的关键字.

例子中就只有println的时候才会去用到Dataimporter,所以在println的时候才会被创建.

3.存储属性和实例变量

在Swift中的属性和在OC中的属性不太一样,在OC中有两种方式在类实例存储值和引用,对于属性来说,也可以使用实例变量作为属性值的后端存储.

Swift 编程语言中把这些理论统一用属性来实现,Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问,这就避免了不同场景下访问方式的困扰,同时也将属性的定 义简化成一个语句,一个类型中属性的全部信息——包括命名、类型和内存管理特征—— 都在唯一一个地方(类型定义中)定义.

4.计算属性

除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 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)

println("square.origin is Now at (\(square.origin.x),\(square.origin.y))")
// 打印出来的结果: square.origin is Now at (10.0,10.0)

PS: 这个例子定义了三种不同的几何形状的结构体:
Point 封装了一个(x,y)的坐标
Size 封装了一个 width 和 height
Rect 表示一个有原点和尺寸的矩形

Rect 也提供了一个名为 center 的计算属性一个矩形的中心点可以从原点和尺寸来算出,所以不需要将它以显式声明的Point来保存。Rect的计算属性center提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。

例子中接下来创建了一个名为 square 的 Rect 实例,初始值原点是(0,0),宽度高度都是 10。 如图所示蓝色正方形。

square 的 center 属性可以通过点运算符(square.center)来访问,这会调用 getter 来获 取属性的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的Point 来表示 square 的中心点。如代码所示,它正确返回了中心点(5,5)。

center 属性之后被设置了一个新的值(15,15),表示向右上方移动正方形到如图所示橙色正 方形的位置。设置属性 center 的值会调用 setter 来修改属性 origin 的 x 和 y 的值,从而实 现移动正方形到新的位置。

5.便捷setter声明

如果计算属性的 setter 没有定义表示新值的参数名,则可以使用名称 newValue。下面是使用了便捷 setter 声明的 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)
        }
    }
}

6.只读计算属性

所谓的只读计算属性其实就是getter属性,只读计算属性总是返回一个值,可 以通过点运算符访问,但不能设置新的值,只读计算属性的声明可以去掉 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)
println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 打印出来的结果: the volume of fourByFiveByTwo is 40.0

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

7.属性监视器

属性监视器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性监视器,甚至新的值和现在的值相同的时候也不例外.

可以为除了延迟存储属性之外的其他存储属性添加属性监视器,也可以通过重载属性的方式 为继承的属性(包括存储属性和计算属性)添加属性监视器.

PS: 不需要为无法重载的计算属性添加属性监视器,因为可以通过 setter 直接监控和响应值的变化.

属性监视器的添加有几种方式,比如:

1.willSet 在设置新的值之前调用

2.didSet 在新的值被设置之后立即调用

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

PS: willSet 和 didSet 监视器在属性初始化过程中不会被调用,他们只会当属性的值在初始化之外的地方被设置时被调用.

让我们来看看例子吧:

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            println("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue {
                println("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 200
// 打印出来的结果: Added 160 steps
stepCounter.totalSteps = 896
// 打印出来的结果: About to set totalSteps to 896
// 打印出来的结果: Added 536 steps

PS: 根据我们前面对两个监视器的解释,我们可以很简单的去了解例子中的运行,首先我们存入第一个数值200,会打印一句提示,说我们已经存储了一个值,由于里面没有老值,所以他在didSet里面的运算结果是200 - 0,最后的结果是200,第二个存入的数值是360,系统也会提示我们说有一个新值存入了,然后继续在didSet里面运算,360 - 200,所以得出来的结果是160,最后一个也是如此,这里就不解释了,还有一个注意点就是如果在 didSet 监视器里为属性赋值,这个值会替换监视器之前设置的值.

好了,这次我们就讲到这里,下次我们继续~

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...