Swift踩坑——对“属性”的理解

本文原载于我的博客:https://www.seekingmini.top/archives/swift踩坑对属性的理解

写在前面

在参考了官方英文文档以后,对于属性这个概念有了一些自己的思考。

属性

官方没有给出一个明确的定义,只是告诉我们属性是干什么的:

Properties associate values with a particular class, structure, or enumeration.

属性将值和特定的类、结构体或者枚举关联。这话说了跟白说一样。我们先暂且不给属性下定义,先来看看属性的分类。属性有两种——存储属性计算属性

存储属性

In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure.

提取关键句——存储属性是一个常量或者变量,这个变量或者常量是特定的类、结构体或者枚举的实例的一部分。这里我们至少知道了存储属性是一个常量或者变量。我们继续看:

Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).

存储属性分为变量存储属性常量存储属性。来看个例子:

struct Person {
    let name: String
    var age: Int
}

var person = Person(name: "Bob", age: 22)

上面的例子中,name是一个常量存储属性,而age是一个变量存储属性。从字面上来看,存储属性是用来存值的,我们可以对这个属性读或写(取决于它是常量或者变量)。

延迟存储属性

A lazy stored property is a property whose initial value is not calculated until the first time it is used.

当一个存储属性加了修饰符lazy时,只有当我们第一次使用这个属性时,它的值才会被计算。看个例子:

class DataImporter {
    var filename = "data.txt"
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
}

var manager = DataManager()
manager.data.append("data one")
manager.data.append("data two")
print(manager.importer.filename)  // 第一次使用

当我们初始化变量manager时,变量importer事实上还没有被初始化,或者说不存在,只有第一次使用它时(即运行代码print(manager.importer.filename))它才会被初始化。这么做可以一定程度上减少计算资源的消耗。

计算属性

官方的定义如下:

In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.

可见计算属性是不存值的,只有在我们getset这个变量的时候才会采取一些行动。首先来看看get的用法:

struct Person {
    let name: String
    var age: Int
    var type: String {
        get {
            if self.age > 18 {
                return "adult"
            } else {
                return "teenager"
            }
        }
    }
}

var person = Person(name: "Bob", age: 14)
print(person.type)

在变量person初始化时,type是没有初始化的,只有当运行代码print(person.type)时,才会根据get语句里的代码进行计算。这与下面的代码有根本性区别:

struct Person {
    let name: String
    var age: Int
    var type: String { self.age > 18 ? "adult" : "teenager" }
}

var person = Person(name: "Bob", age: 14)
print(person.type)

这段代码里的type在变量person初始化的时候就已经计算出值了。接下来看看set的用法:

struct Person {
    let name: String
    var age: Int
    var type: String {
        get {
            if self.age > 18 {
                return "adult"
            } else {
                return "teenager"
            }
        }
        set (t) {
            if t == "old" {
                self.age = 65
            }
        }
    }
}

var person = Person(name: "Bob", age: 14)
print(person.type)
person.type = "old"
print(person.age)
print(person.type)

以上代码的运行结果是:

teenager
65
adult

我们来分析一下过程。首先第一个print(person.type)就不用分析了,直接来看person.type = "old"这段代码。当运行这段代码时,触发了set语句,把age重新赋值为65,但是第二个print(person.type)的结果仍然是"adult"而不是"old",是因为get语句的作用。

属性观察者

Property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.

当一个属性值被set的时候,属性观察者会采取一些行动。属性观察者分为willSetdidSet两种:

  • willSet is called just before the value is stored.
  • didSet is called immediately after the new value is stored.

来看个例子:

struct Town {
    var population: Int = 0{
        willSet(newPopulation) {
            print("小镇的人口马上要变成\(newPopulation)了!")
        }
        didSet {
            if (population > oldValue) {
                print("小镇的人口从原来的\(oldValue)增长了\(population - oldValue)!")
            } else if population == oldValue {
                print("小镇的人口没有增长,还是\(oldValue)。")
            } else {
                print("小镇的人口从原来的\(oldValue)减少了\(oldValue - population)!")
            }
        }
    }
}

var town = Town()
town.population = 100
town.population = 500
town.population = 50

以上代码的运行结果是:

小镇的人口马上要变成100了!
小镇的人口从原来的0增长了100!
小镇的人口马上要变成500了!
小镇的人口从原来的100增长了400!
小镇的人口马上要变成50了!
小镇的人口从原来的500减少了450!

我们来逐行分析一下。

  1. 对于town.population = 100这段代码,首先运行willSet中的代码,即打印小镇的人口马上要变成100了!,由于100大于初始值0,所以运行didSet中的代码,即打印小镇的人口从原来的0增长了100!
  2. 对于town.population = 500这段代码,首先运行willSet中的代码,即打印小镇的人口马上要变成500了!,由于500大于100,所以运行didSet中的代码并且选择if (population > oldValue)这个分支,即打印小镇的人口从原来的100增长了400!
  3. 对于town.population = 50这段代码,首先运行willSet中的代码,即打印小镇的人口马上要变成50了!,由于50大于500,所以运行didSet中的代码并且选择else这个分支,即打印小镇的人口从原来的100减少了450!

(未完待续)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值