swfit进阶-04-swift属性

221 阅读5分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

  • 本文主要介绍swift中的属性

1. 存储属性

主要分为常量和变量存储属性

  • let 修饰常量
  • var 修饰变量

我们是class类型的时候

image.png

  1. 属性是let修饰的时候,赋值后不能修改了
  2. 修饰类的时候,也不能修改了。
    我们是struct类型的时候

image.png struct是值类型,因此let修饰后不能对它的属性在进行修改

1.1 汇编分析

我们定义一个常量和变量

let a = 10
var b = 20

汇编断点

image.png

从汇编调试来看没有区别,都是将值存储到了寄存器中

1.2 sil分析

class Person {
//_hasStorage 表示是存储属性
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue final let age: Int { get }
@objc deinit
init()
}

通过 sil 我们可以发现,var 修饰的属性有 getset方法

let 修饰的属性只有 get 方法,所有 let 修饰的属性不能修改

2.计算属性

计算属性:本质是不占用内存空间,通过属性的set/get 方法进行计算

image.png

调用的时候会报错

image.png

是因为造成了死循环age的set方法中调用age.set导致了循环引用,即递归

  • 验证:不占内存 对于其不占用内存空间这一特征,我们可以通过以下案例来验证,打印以下类的内存大小
class  Square{
    
    var width :CGFloat = 10.0;
    
    var area:CGFloat {
        
        set {
            self.width = newValue
        }
        get{
            width*width//return 可以省略系统自动帮我们添加
        }
    }
    
}

我们定义一个类,并打印大小

image.png

从打印可以看出类Square的内存大小是24,等于 (metadata + refCounts)类自带16字节 + width(8字节) = 24,是没有加上area的。从这里可以证明 area属性没有占有内存空间。同时我们定义一个类没有添加area属性,内存大小也是24,也说明了area没有占有内存大小

  • 计算属性sil验证
class Square {
@_hasStorage @_hasInitialValue var width: CGFloat { get set }
var area: CGFloat { get set }
@objc deinit
init()
}

计算属性没有@_hasStorage进行修饰

image.png

计算属性只有settergetter方法

  • private(set)私有属性
class  Square1{
    
    var width :CGFloat = 10.0;
    
    private(set) var height:CGFloat = 20;
}

使用private(set)修饰的变量表示当前属性只有在当前类的内部进行赋值,查看sil文件

class Square1 {
@_hasStorage @_hasInitialValue var width: CGFloat { get set }
@_hasStorage @_hasInitialValue private(set) var height: CGFloat { get set }
@objc deinit
init()
}

set方法是私有的,get方法外部可以访问的,相当于我们oc中对属性修饰的readonly

3.属性观察者

  • willset :新值储存前调用newValue。 
  • didset:旧值存储前调用oldValue
class Person {
    
    var name :String = "jj"{
        
        willSet{
            
            print("newValue is : (newValue)")
        }
        
        didSet{
            print("oldValue is :(oldValue)")
        }
    }
}
let  p = Person()
p.name = "ss"
//打印
//newValue is : ss
//oldValue is :jj

我们编译sil文件查看

image.png

说明在setter方法内部进行调用willSetdidSet方法,首先把旧值保存下来,在设置新值前调用willSet,保存newValue,这个时候获取到新值,设置完成后调用didset方法,读取oldValue

3.1.初始化调用setter

class Person {
    
    var name :String = "jj"{
        
        willSet{
            
            print("newValue is : (newValue)")
        }
        
        didSet{
            print("oldValue is :(oldValue)")
        }
    }
    init() {
        self.name = "kk"
    }
}
let  p = Person()
print("hello")

我们在初始化的时候设置属性的值,发现并没有调用willSetdidSet方法,我们编译sil文件

image.png

在初始化方法中对属性直接赋值,没有调用setter方法。

结论:我们初始化的时候设置值或者设置默认值都不会触发观察者属性的方法,只有在有新值的时候才会调用

3.2 继承关系中属性观察者

我们继承关系中,子类重写父类的属性setter方法时

class Animal {
    
    var name :String  {
        
        willSet{
            
            print("newValue is : (newValue)")
        }
        
        didSet{
            print("oldValue is :(oldValue)")
        }
    }
    init(name:String) {
        self.name = name
    }
//    init() {
//        self.name = "kk"
//    }
}
class Dog: Animal {
    
    override var name: String
    {
        
        
        willSet{
            
            print("override newValue is : (newValue)")
        }
        
        didSet{
            print("override oldValue is :(oldValue)")
        }
    }
    
}
let  d = Dog(name: "animal")
d.name = "dog"
print("hello")

调用顺序是

override newValue is : dog
newValue is : dog
oldValue is :animal
override oldValue is :animal
hello

说明先调用子类willSet ,再调用父类willSet,父类的didSet,子类的didSet

  • 子类初始化中设置属性
class Dog: Animal {
    
    override var name: String
    {
        
        
        willSet{
            
            print("override newValue is : (newValue)")
        }
        
        didSet{
            print("override oldValue is :(oldValue)")
        }
    }
    override init(name: String) {
        super.init(name: name)
        self.name = "Cat"
    }
    
}
let  d = Dog(name: "animal")
print("hello")
//打印结果
override newValue is : Cat
newValue is : Cat
oldValue is :animal
override oldValue is :animal
hello

说明调用super.init后已经有了初始化的值animal了,这个时候触发观察属性了。

4.延迟存储属性

延迟属性我们通常使用lazy来进行修饰,但是延迟存储属性必须要有初始化值

class Person {
    
    lazy var name:String = "ss"
}

我们调用

image.png

说明在我们初始化后没有调用的情况下是没有值的,打印p.name 后调用了

image.png

此时属性中有值了,说明对于lazy修饰的存储属性只有当调用它时候才会赋值

  • sil分析 我们编译成sil查看

image.png

使用lazy修饰的String后有说明该属性是可选

image.png 在类初始化的时候我们可以发现对于lazy修饰的属性,这个时候赋值为Optional.none,也就是空。
继续查看getter方法

image.png

  1. 首先判断当前lazy修饰的存储属性是否有值,有值的走bb1直接返回值,没值的话走bb2

  2. 没值的情况,把默认值存储到当前属性的地址上,返回值。

  3. 说明lazy修饰的属性值不是线程安全的。

5.类型属性

class Person {
    
    static var name:String = "ss"
}


let name = Person.name
print(name)
Person.name = "jj"
print(Person.name)

当然也可以进行修改,我们编译下sil文件进行查看

image.png

name属性变成了全局属性

image.png

通过Person.name.unsafeMutableAddressor 获取name的值

  • getter方法

image.png

image.png

通过断点调试进行swift_once 的回调,也就是 sil中的builtin "once"

image.png

  • 源码查看

image.pngonce.cpp 中我们可以查看到swift_once 的定义,本质就是调用我们oc中的dispatch_once_f

class Person {
    
    static var name:String = "ss"
    static let shareInstance = Person.init()
    var age = 19
    
    private init(){//私有初始化
        
    }
}


let  p = Person.shareInstance
p.age = 20