前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swift底层探索 02 - 属性swift底层探索 02 - 属性

swift底层探索 02 - 属性swift底层探索 02 - 属性

作者头像
用户8893176
发布2021-08-09 11:28:26
9380
发布2021-08-09 11:28:26
举报
文章被收录于专栏:小黑娃Henry

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

获取sil文件

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

代码语言:javascript
复制
// 单纯转换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抽象语法树
代码语言:javascript
复制
swiftc  -dump-ast main.swift ast抽象语法树
  • 这是在sil的上一步生成的文件,主要是做一些语法、词法的分析。

Swift的属性分为:

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

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

  • 根据对象内存分布可以证明
存储属性分为
  • 常量存储属性,及let
  • 变量存储属性,及var
代码语言:javascript
复制
class TeachModel{
    let age:Int = 18
    var name:String = "Henry"
}
sil文件,这部分需要对照观察
代码语言:javascript
复制
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是一个结构体,而结构体是值类型

代码语言:javascript
复制
class TeachModel{
    var name:String{
        get{
            return "Henry"
        }
        set{
            print(newValue)
        }
    }
}
sil文件
代码语言:javascript
复制
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中会不会触发属性观察者

答案是不一定

代码语言:javascript
复制
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中的懒加载思想来理解。使用时才进行加载,可以优化类的创建过程。

代码语言:javascript
复制
class TeachModel{
    lazy var age : Int = 18
}
    1. 用关键字lazy来进行表示
    1. 第一次使用时才进行初始化
sil文件
代码语言:javascript
复制
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
代码语言:javascript
复制
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
单例
  • 线程安全 + 只进行一次初始化;这不就是单例吗~~
代码语言:javascript
复制
class Teacher{
    //1、使用 static + let 创建声明一个实例对象
    static let shareInstance = Teacher.init()
    //2、给当前init添加private访问权限
    private init(){ }
}

//使用(只能通过单例,不能通过init)
var t = CJLTeacher.shareInstance
  • swift的单例相比于OC的单例要简单很多
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/12/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 获取sil文件
    • 获取ast抽象语法树
    • Swift的属性分为:
      • 1. 存储属性:
        • 存储属性分为
        • sil文件,这部分需要对照观察
      • 2. 计算属性:
        • 3. 属性观察者(willSet、didSet)
          • 在使用过程中有几个问题:
          • 1. 在init中会不会触发属性观察者
          • 2. 子类和父类同时存在didset、willset时,其调用顺序
        • 4. 延迟存储属性-lazy
          • sil文件
          • 无法保证线程安全
        • 5. 类型属性static
          • 使用关键字static修饰,且是一个全局变量
          • 线程安全
          • 单例
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档