Swift 进阶:属性

一、存储属性

1.1 概念

存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var 关键字引入),要么是常量存储属性(由 let 关键字引入)。

1.2  let 修饰和用 var 修饰的区别

class Person {
    let name: String
    var age: Int
    init(_ name:String, _ age: Int) {
        self.name = name
        self.age = age
    }
}

我们定义了一个 Person 的,并且有两个存储属性 name 和 age。然后我们在实际调用时去修改其属性,会发现:

  1. 使用 let 修饰的实例,其 let 修饰的属性不允许修改, var 修饰的属性可以修改;
  2. 使用 let 修饰的实例,其自身不允许被修改;
  3. 使用 var 修饰的实例,其 let 修饰的属性不允许修改, var 修饰的属性可以修改;
  4. 使用 var 修饰的实例,其自身可以被修改;

而对于结构体

struct Person {
    let name: String
    var age: Int
    init(_ name:String, _ age: Int) {
        self.name = name
        self.age = age
    }
}

在实际调用时去修改其属性,会发现:

  1. 但凡使用 let 修饰的实例,不论属性是否可变,实例的属性均不允许修改
  2. 使用 var 修饰的实例,可以修改使用 var 修饰的属性

1.3 总结

对于类来说,不论实例被修饰为变量(即用 let 修饰)还是常量(即用 var 修饰),均可以修改其变量存储属性,不可修改其常量存储属性。而如果实例为常量,则不可修改其自身。

对于结构体来说,如果实例为常量(即用 let 修饰),由于结构体是值类型,所以实例的任何属性及其自身都不可被修改;如果实例为变量(即用 var 修饰),则可以修改其变量存储属性以及自身。

二、计算属性

2.1 概念

除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,可提供一个获取来获取值,一个选择的设置器来设置其他属性或变量的值。

注意:

对于存储属性来说可以是常量或变量,但计算属性必须定义为变量

计算属性必须包含类型,因为编译器需要知道期望返回值是什么

2.2 计算属性和存储属性的区别

  1. 存储属性存储常量或变量作为实例的一部分。而计算属性计算(而不是存储)一个值。
  2. 存储属性可用于类和结构体,计算属性可用于类、结构体和枚举。

2.3 只读计算属性

struct Person {
//    存储属性
    var age = 18
    let height: Double = 120.0
//    计算属性
    var name: String {
        get {
            return "zzm"
        }
        set {
            self.age = 20
        }
    }
//    只读计算属性
    var sex: Bool {
        get {
            return true
        }
    }
}

另:我们可以通过 private(set) 将一个属性的 set 方法声明为私有。

三、属性观察者

3.1 概念

属性观察者用来观察属性值的变化,一个 willSet 当属性将被改变调用,即使这个值与原有的值相同,而 didSet 在属性已经改变之后调用。

class Person {
// 存储属性
    var name: String = "" {
        willSet{
            print("name will set value \(newValue)")
        }
        didSet{
            print("name has been changed \(oldValue)")
        }
    }
// 计算属性
    var age: Int {
        get {
            return 18
        }
        set {
            print("name will set value \(newValue)")
        }
    }

}

注意:在初始化期间设置属性时不会调用 willSet 和 didSet 观察者。只有在为完全初始化的实例分配新值时才会调用。

3.2 子类调用 set 的顺序

class Person {
    var name: String = "" {
        willSet{
            print("name will set value \(newValue)")
        }
        didSet{
            print("name has been changed \(oldValue)")
        }
    }
}

class Programmer: Person {
    override var name: String {
        willSet{
            print("override name will set value \(newValue)")
        }
        didSet{
            print("override name has been changed \(oldValue)")
        }
    }
}

class ViewController: UIViewController{

    override func viewDidLoad() {
        var p = Programmer()
        p.name = "zzm"
    }
}

通过这一段代码的打印我们可以看到:

当子类重写属性后,属性观察器调用的顺序是:

自己的 willSet() ——> 父类的willSet() ——>父类的didSet() ——>自己的 didSet()

四、延迟存储属性

4.1 概念

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

class Person {
    lazy var name = "zzm"
}

class ViewController: UIViewController{

    override func viewDidLoad() {
        var p = Person()
        let name = p.name
        print(name)
    }
}

针对这段代码,我们在  let name = p.name 处打上断点并运行,并在控制台查看 p会发现:

p 的地址是 0x60000123a320,可以看到这个地址的值,前 16 个字节是 metadata 和 refCount。后 8 个字节的值是空的,也就意味着在这个时候,p 的延迟存储属性 name 并没有初始化赋值。

当我们把断点向后走一步,再同样查看 p 会发现:

这个时候对应的属性 name 被初始化了。 

上面的代码编译成 SIL 文件可以看到

 延迟存储属性的实质,实际上是一个可选类型。

4.2 线程安全

延迟加载的属性并不是线程安全的。

五、类型属性

5.1 概念

类型属性是作为类型定义的一部分写在类型最外层的花括号({})内。使用关键字 static 来定义值类型的类型属性,关键字 class 来为类定义类型属性。

类型属性其实就是一个全局变量,类型属性只会被初始化一次

struct Person {
    static var sex = true
    var name = "zzm"
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        print(Person.sex)
    }
}

于是在 Swift 中的单例写法就应该这样写:

struct Person {
    static let sharedInstance = Person()
    private init() {}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值