在本文会使用swift底层探索 01 - Swift类初始化&类结构提到的
sil
的方式来进行探索
swift
文件到可执行文件.o
的整个编译过程。在当前文件路径下使用该命令:
// 单纯转换sil
swiftc -emit-sil main.swift > ./main.sil
// 反解sil中混淆的字符串
xcrun swift-demangle s4main1tAA10TeachModelCvp
// 完整版
swiftc -emit-sil `文件名`.swift | xcrun swift-demangle > `文件名`.sil
cpp
文件,sil
、cpp
都是编译之后的产物sil
可以更加深刻的理解swift的一些内部机制。对于学习swift很有帮助。swiftc -dump-ast main.swift ast抽象语法树
sil
的上一步生成的文件,主要是做一些语法、词法的分析。可以保存各类信息的属性,需要占用内存空间
。
let
var
class TeachModel{
let age:Int = 18
var name:String = "Henry"
}
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是不可修改的.计算属性的本质就是get、set
方法,并不占用内存
String值
。String在swift中是一个字面量,
及将String值存在内存中
。String是一个结构体,而结构体是值类型
。
class TeachModel{
var name:String{
get{
return "Henry"
}
set{
print(newValue)
}
}
}
class TeachModel {
var name: String { get set }
@objc deinit
init()
}
@_hasStorage ,@_hasInitialValue
.get,set
方法。get
方法的sil实现作用可以简单的理解为oc中的KVO
,区别是使用更加简单,但也有自己的一些规则.
willSet
:新值存储之前调用. 内建变量newValue
didSet
:新值存储之后调用. 内建变量OldValue
属性观察者(willSet、didSet)
之后,在编译阶段会在set
方法中增加调用这两个方法的代码。当然这些都是编译器完成的,不需要我们再去进行额外的操作。答案是不一定
class CJLTeacher{
var name: String = "测试"{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
init() {
self.name = "CJL"
}
}
属性观察者
子类的init
中调用会触发属性观察者
,因为在子类中已经完成了父类的内存布局
已经age的内存布局
,所以可以触发属性观察者
子类的willSet
->父类的wilSet
->父类的didSet
->子类的didset
可以对比oc
中的懒加载
思想来理解。使用时才进行加载
,可以优化类的创建过程。
class TeachModel{
lazy var age : Int = 18
}
lazy
来进行表示第一次使用时
才进行初始化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方法时,进行初始化
。后续使用则直接返回内存中的值.
将新值包装为可选类型
。保证变量数据类型的一致。在查看sil过程中并没有发现线程锁
之类的代码。所以在get
方法的switch判断那存在多线程问题,一定概率会出现多次初始化的情况.
class TeachModel{
//声明
static var age : Int = 18
}
//使用
TeachModel.age = 20
类型属性,主要有以下几点说明:
查看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的单例要简单很多