跳转至

属性

存储属性

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

class LGTeacher{
      //存储属性
    var age: Int
    var name: String
}

let 和 var 两者的区别:

  • let 用来声明常量,常量的值一旦设置好便不能再被更改。
  • var 用来声明变量,变量的值可以在将来设置为不同的值。

这里我们来看几个案例:

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

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

let t = LGTeacherP(age: 18, name: "Hello")//t不能修改 t存储的实例对象的内存,内存地址不可以改变
//t.age = 20//age是let,不能修改
t.name = "Logic"
//t = LGTeacherP(age: 30, name: "Kody")//t是let,t不能修改

var t1 = LGTeacherP(age: 18, name: "Hello")
//t1.age = 20//age是let,不能修改
t1.name = "Logic"
t1 = LGTeacherP(age: 30, name: "Kody")

let s = LGStudentP(age: 18, name: "Hello")//s是结构体 值存储 都不能修改
//s.age = 25
//s.name = "Doman"
//s = LGStudentP()

var s1 = LGStudentP(age: 18, name: "Hello")
//s.age = 25
s.name = "Doman"
s = LGStudentP()

let 和 var 的区别:

  • 从汇编的角度

  • 从 SIL的角度

var有set,let没有set

计算属性

类、结构体和枚举也能够定义计算属性。

计算属性不存储值,不占用内存空间。本质是提供 getter 和 setter 来修改和获取值。

存储属性可以是常量或变量。

计算属性必须定义为变量。书写计算属性时候必须包含类型,因为编译器需要知道期望返回值是什么。

//struct静态调用
struct square{
  //存储属性 实例当中占据内存
  var width: Double
  //计算属性是方法,不占用内存空间
  var area: Double {
    get{
      return width * width
    }
    set{
      self.width = newValue//newValue编译器自动生成的
    }
  }

  private(set) var area_p: Double//只能结构体内部set,外部是只读属性

  mutating func test(){
    self.area_p = 2.0
  }
}

var s = square(width: 10.0, area_p: 20.0)
s.area = 30

只读属性

private(set) var captureSession: AVCaptureSession? 

延迟存储属性 lazy

节省内存空间,不是线程安全的。

  • 延迟存储属性的初始值在其第一次访问使用时才进行计算
  • 用关键字 lazy 来标识一个延迟存储属性
// MARK: 延迟存储属性lazy
class Subject{
  lazy var age: Int = 18//必须有初始值,即使改成Optional也不行。
}
//test
var sb = Subject()
print(sb.age)

这里我们来打印一下使用lazy 和不使用lazy的时候,当前对象的大小有什么变化?

class Subject{
    lazy var age: Int = 10
}
var t = Subject()
print(class_getInstanceSize(Subject.self))//32
class Subject{
    var age: Int = 10
}
var t = Subject()
print(class_getInstanceSize(Subject.self))//24

为什么会有 8 字节的差距?我们通过SIL来查看一下:

当我们第一访问他的时候发生了什么事情?

在回过头来看我们刚才那两句话,这个时候大家理解清楚了没?同样的,这里其实底层是一个Optional,我们可以通过MemoryLayout来测量一下需要多少大小?(8字节对齐之后是不是就是32)

其次我们在来理解一句话:

如果被标记为 lazy 修饰符的属性同时被多个线程访问并且属性还没有被初始化,则无法保证属性只初始化一次。

这个该怎么理解,其实很简单,比如多线程我们是不是没办法确定当前代码的执行顺序啊!假设有两个线程同时访问我们当前的age变量,这个时候都是第一次访问!

当然这里还有一种写法 这个和我们之前直接初始化有什么区别吗?这个能保证我们的变量只初始化一次吗?

类型属性 static

也是一个存储属性,static修饰,全局变量

  • 类型属性其实就是一个全局变量
  • 类型属性只会被初始化一次,可以被修改
class HHTeacherT{
  static var age: Int = 18
}
HHTeacherT.age = 30
HHTeacherT.age = 40

属性观察者

属性观察者会观察用来观察属性值的变化

1、存储属性添加属性观察者

  • willSet 当属性将被改变调用,即使这个值与原有的值相同
  • didSet 在属性已经改变之后调用。

注:init初始化不会调willSet和didSet

初始化期间设置属性时不会调用 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) {//初始化的操作,第一次调用不是调get和set,直接把值拷贝到内存地址中。
        self.subjectName = subjectName//不会调用willSet和didSet
    }

}

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

上面的属性观察者是对存储属性起作用。如果想对计算属性起作用,只需将相关代码添加到属性的 setter。

继承属性下的观察者

编译器调用顺序:

子类的willSet

父类的setter。willSet didSet

子类的didSet

class HHTeacher{
    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 HHParTimeTeacher: HHTeacher {
    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(18, 10.0)
        //初始化完成了,第二次访问的
        self.age = 20
    }
}

let t = HHParTimeTeacher("Swift")

2、计算属性添加观察者

class Square{
    var width: Double

    var area: Double{
        get{
            return width * width
        }
        set{
            //willSet
            print("area will set value \(newValue)")
            self.width = sqrt(newValue)
            //didSet
            print("area has been changed \(self.width)")
        }
    }

    init(width: Double) {
        self.width = width
    }
}