美文网首页专注iOS开发的小渣渣
swift底层探索 02 - 属性

swift底层探索 02 - 属性

作者: Henry________ | 来源:发表于2020-12-14 20:39 被阅读0次

    在本文会使用swift底层探索 01 - Swift类初始化&类结构提到的sil的方式来进行探索

    获取sil文件

    在当前文件路径下使用该命令:

    // 单纯转换sil
    swiftc -emit-sil main.swift > ./main.sil
    // 反解sil中混淆的字符串
    xcrun swift-demangle s4main1tAA10TeachModelCvp
    // 完整版
    swiftc -emit-sil `文件名`.swift | xcrun swift-demangle > `文件名`.sil 
    
    • sil文件相当于OC探索中的cpp文件,silcpp都是编译之后的产物
    • sil语法官方文档,阅读sil可以更加深刻的理解swift的一些内部机制。对于学习swift很有帮助。

    获取ast抽象语法树

    swiftc  -dump-ast main.swift ast抽象语法树
    
    • 这是在sil的上一步生成的文件,主要是做一些语法、词法的分析。

    Swift的属性分为:

    • 存储属性
    • 计算属性
    • 属性观察者(didSet、willSet)
    • 延迟存储属性
    • 类型属性

    1. 存储属性:

    可以保存各类信息的属性,需要占用内存空间

    • 根据对象内存分布可以证明
    存储属性分为
    • 常量存储属性,及let
    • 变量存储属性,及var
    class TeachModel{
        let age:Int = 18
        var name:String = "Henry"
    }
    
    sil文件,这部分需要对照观察
    class TeachModel {
      @_hasStorage @_hasInitialValue final let age: Int { get }
      @_hasStorage @_hasInitialValue var name: String { get set }
      @objc deinit
      init()
    }
    
    • let修饰的变量在编译之后会增加一个final修饰符,表明常量存储属性是不可继承的.
    • var修饰的变量有get,set方法。而let修饰的变量只有get方法,没有set方法直接印证了let是不可修改的.

    2. 计算属性:

    计算属性的本质就是get、set方法,并不占用内存

    • 并没有在内存中找到具体的String值

    String在swift中是一个字面量,及将String值存在内存中String是一个结构体,而结构体是值类型

    class TeachModel{
        var name:String{
            get{
                return "Henry"
            }
            set{
                print(newValue)
            }
        }
    }
    
    sil文件
    class TeachModel {
      var name: String { get set }
      @objc deinit
      init()
    }
    
    • 相比于存储属性少了2个关键字:@_hasStorage ,@_hasInitialValue.
    • 声明了get,set方法。
    • get方法的sil实现

    3. 属性观察者(willSet、didSet)

    作用可以简单的理解为oc中的KVO,区别是使用更加简单,但也有自己的一些规则.

    • willSet:新值存储之前调用. 内建变量newValue
    • didSet:新值存储之后调用. 内建变量OldValue
    • 在你使用属性观察者(willSet、didSet)之后,在编译阶段会在set方法中增加调用这两个方法的代码。当然这些都是编译器完成的,不需要我们再去进行额外的操作。
    在使用过程中有几个问题:
    1. 在init中会不会触发属性观察者

    答案是不一定

    class CJLTeacher{
        var name: String = "测试"{
            //新值存储之前调用
            willSet{
                print("willSet newValue \(newValue)")
            }
            //新值存储之后调用
            didSet{
                print("didSet oldValue \(oldValue)")
            }
        }
        
        init() {
            self.name = "CJL"
        }
    }
    
    • 事实证明在init方法中不会触发属性观察者

    • 因为在初始化过程中内存中的对应地址可能是脏的,获取oldvalue可能会造成问题

    • 【反例】但是在子类的init中调用会触发属性观察者,因为在子类中已经完成了父类的内存布局已经age的内存布局,所以可以触发属性观察者

    2. 子类和父类同时存在didset、willset时,其调用顺序
    • 调用顺序:子类的willSet->父类的wilSet->父类的didSet->子类的didset

    4. 延迟存储属性-lazy

    可以对比oc中的懒加载思想来理解。使用时才进行加载,可以优化类的创建过程。

    class TeachModel{
        lazy var age : Int = 18
    }
    
      1. 用关键字lazy来进行表示
      1. 第一次使用时才进行初始化
    sil文件
    class TeachModel {
      lazy var age: Int { get set } //计算属性
      @_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }  //存储属性
      @objc deinit
      init()
    }
    
    • 加了lazy在编译之后,编译器会添加对应的计算属性,已经可选类型的存储属性。这样会导致对象的内存大小发生变化.

    可选类型是一个enum+关联值(当前类型).
    结果:内存占用需要在Int(8字节)+ enum(1字节) -> 字节对齐 (16字节)


    sil文件中get方法的实现


    • get方法简单理解: 第一次使用时,变量内存为空,调用get方法时,进行初始化。后续使用则直接返回内存中的值.
    • set方法简单理解: 将新值包装为可选类型。保证变量数据类型的一致。
    无法保证线程安全

    在查看sil过程中并没有发现线程锁之类的代码。所以在get方法的switch判断那存在多线程问题,一定概率会出现多次初始化的情况.

    5. 类型属性static

    class TeachModel{
        //声明
        static var age : Int = 18
    }
    //使用
    TeachModel.age = 20
    

    类型属性,主要有以下几点说明:

    使用关键字static修饰,且是一个全局变量

    查看sil文件

    • 定义为全局变量


    • 全局初始化的时候就完成了唯一一次初始化,并不需要依赖类对象的初始化.
    • 因为需要定义到全局,所以一定要提供初始化值.
    线程安全
    • 发现会调用build once。可这个build once是什么呢?
    • 通过xcode汇编调试,会发现调用了swift_once
    • 打开源码搜索swift_once,在Once.cpp文件中发现了具体实现。发现调用了熟悉的dispathch_once_f
    单例
    • 线程安全 + 只进行一次初始化;这不就是单例吗~~
    class Teacher{
        //1、使用 static + let 创建声明一个实例对象
        static let shareInstance = Teacher.init()
        //2、给当前init添加private访问权限
        private init(){ }
    }
    
    //使用(只能通过单例,不能通过init)
    var t = CJLTeacher.shareInstance
    
    • swift的单例相比于OC的单例要简单很多

    相关文章

      网友评论

        本文标题:swift底层探索 02 - 属性

        本文链接:https://www.haomeiwen.com/subject/ihsbgktx.html