swift属性

139 阅读4分钟

一、存储属性

  • 存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。
区分var和let
  • 汇编角度分析:
    var age = 10
    var name = "Lee"

image.png

将0x12复制到寄存器x10;
将0x14复制到寄存器x8,
汇编角度看起来没区别;

-SIL角度分析:

image.png

从上述SIL代码可以看出,let和get都是存储属性,都有初始值。let只有get方法,没有set方法。本质上let和var也是一种语法糖

二、计算属性

存储的属性是最常⻅的,除了存储属性,类、结构体和枚举也能够定义计算属性,计算属性并不 存储值,他们提供 getter 和 setter 来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。于此同时我们书写计算属性时候必须包含类型,因为编译器需 要知道期望返回值是什么。

struct Square {
    var width: Double
    
    var area: Double {
        get {
            return width * width
        }
        set {
            self.width = newValue
        }
    }
}

class Square {
    var width: Double = 8.0
    var area: Double {
        get {
            return width * width
        }
        set (newValue) {
            width = sqrt(newValue)
        }
    }
}

let s = Square()
print(class_getInstanceSize(Square.self))  //输出24
print(s.area)

通过sil查看:

struct Square {
  //width有存储属性,实例中占据内存,占8字节
  @_hasStorage var width: Double { get set }
  // ,不占据内存
  var area: Double { get set }
  init(width: Double)
}

class Square {
  // width有存储属性,实例中占据内存,占8字节
  @_hasStorage @_hasInitialValue var width: Double { get set }
  // area没有存储属性,不占据内存
  var area: Double { get set }
  @objc deinit
  init()
}

再比如:private(set) var area: Double = 30写法,含义将set方法私有。内部只读,外部不允许访问。 此时查看SIL文件: image.png 总结: 计算属性本质get、set方法。

三、属性观察者

属性观察者会观察用来观察属性值的变化,一个 willSet 当属性将被改变调用,即使这个值与原有的值相同,而 didSet 在属性已经改变之后调用。它们的语法类似于 getter 和 setter

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

    init(subjectName: String) {
        self.subjectName = subjectName
    }
}

let s = SubjectName(subjectName: "swift进阶")
s.subjectName = "swift"

// 输出结果
subjectName will set value swift
subjectName has been changed swift进阶

image.png 总结 :willSet:新值存储之前调用 newValue;didSet:新值存储之后调用 oldValue

  • 这里我们在使用属性观察器的时候,需要注意的一点是在初始化期间设置属性时不会调用 willSet 和 didSet 观察者;只有在为完全初始化的实例分配新值时才会调用它们。运行下面这段代码,你会发现当前并不会有任何的输出。

计算属性观察者

class SubjectName{
    var subjectName: String = "[unnamed]"{

    willSet{
    print("subjectName will set value \(newValue)")

    } didSet{

    print("subjectName has been changed \(oldValue)") }

    }
    init(subjectName: String) { 
    self.subjectName = subjectName
    } 
}

let s = SubjectName(subjectName: "Swift进阶")

image.png 可以看出init方法没有调用willsetdidset方法,而是直接把subjectName拷贝到内存中。

继承属性观察者

class Teacher {
    var age: Int {
        willSet{
            print("age will set value \(newValue)")
        } didSet{
            print("age has been changed \(oldValue)")
        }
    }
    var height: Double
    
    init(_ age: Int, _ height: Double) {
        self.age = age
        self.height = height
    }
}

class ParTimeTeacher: Teacher {
    
    override var age: Int {
        willSet{
            print("override age will set value \(newValue)")
        } didSet{
            print("override age has been changed \(oldValue)")
        }
    }
    
    var subjectName: String
    
    init(_ subjectName: String) {
        self.subjectName = subjectName
        super.init(10, 180)
        self.age = 20
    }
}

var t = ParTimeTeacher("Swift")
//运行结果
//override age will set value 20
//age will set value 20
//age has been changed 10
//override age has been changed 10


当在继承的时候,属性的观察者的调用方式是

  • 调用子类的的willset
  • 调用父类的willset
  • 调用父类的didset
  • 调用子类的didset

延迟存储属性

用关键字 lazy 来标识一个延迟存储属性,延迟存储属性必须有初始值

  • lazy 属性必须是 var,不能是 let,因为 let 必须在实例的初始化方法完成之前就拥有值。
class sub { lazy var age: Int = 18 }
swift 内存独占

必须保证init方法的私有性,只有这样,才能保证单例是真正唯一的,避免外部对象通过访问init方法创建单例类的其他实例。

class LeeTeacher() {
    static let shareInstance = LeeTeacher()
    private init() {}
}

12