属性¶
存储属性¶
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量(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
}
}