第四节课:内存管理&Runtime
内存管理 - 强引用
在swift中也是使用ARC
来追踪和管理内存的,下面我们先简单看一段代码来进行分析
class HZMTeacher {
var age: Int = 18
var name: String = "HZM"
}
var t = HZMTeacher()
var t1 = t
var t2 = t
- 第一位为
metadata
但是后面的0x0000000600000002
跟我们之前的refCounts
并不一样
我们分析下源码 HeapObject -> InlineRefCounts
struct HeapObject {
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
...
}
👇
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
- 进入
InlineRefCounts
定义,是RefCounts
类型的别名,而RefCounts
是模板类,真正决定的是传入的类型InlineRefCountBits
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
👇
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
- 分析
InlineRefCountBits
,是RefCountBitsT
类的别名
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- 分析
RefCountBitsT
,有bits
属性
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
...
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
...
BitsType bits;
...
}
👇
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
//类型
typedef uint64_t Type;
typedef int64_t SignedType;
};
其中bits
其实质是将RefCountBitsInt
中的type
属性取了一个别名,所以bits
的真正类型是uint64_t
即64位整型数组
然后来继续分析swift中对象创建的底层方法swift_allocObject
分析初始化源码swift_allocObject
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
...
new (object) HeapObject(metadata);
...
}
👇
<!--构造函数-->
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 进入
Initialized
定义,是一个枚举,其对应的refCounts
方法中
enum Initialized_t { Initialized };
//对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
从这里看出真正干事的是
RefCountBits
进入
RefCountBits
定义,也是一个模板定义
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
- 所以真正的初始化地方是下面这个,实际上是做了一个位域操作,根据的是
Offsets
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
分析RefCountsBit
的结构,如下图所示
isImmortal
(0)UnownedRefCount
(1-31): unowned的引用计数isDeinitingMask
(32):是否进行释放操作StrongExtraRefCount
(33-62): 强引用计数UseSlowRC
(63)
重点关注UnownedRefCount
和StrongExtraRefCount
将t
的refCounts
用二进制展示,我们发现:
1~31的UnownedRefCount
为1
33~62的StrongExtraRefCount
为2
当只有t实例变量时
当有t + t1
时,查看是否有 strong_retain
操作
//SIL中的main
alloc_global @main.t1 : main.HZMTeacher // id: %8
%9 = global_addr @main.t1 : main.HZMTeacher : $*HZMTeacher // user: %11
%10 = begin_access [read] [dynamic] %3 : $*HZMTeacher // users: %12, %11
copy_addr %10 to [initialization] %9 : $*HZMTeacher // id: %11
//其中copy_addr等价于
- %new = load s*HZMTeacher
- strong_retain %new
- store %new to %9
//内部是一个宏定义
HeapObject *swift::swift_retain(HeapObject *object) {
CALL_IMPL(swift_retain, (object));
}
👇
//本质调用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
👇
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// constant propagation will remove this in swift_retain, it should only
// be present in swift_retain_n
if (inc != 1 && oldbits.isImmortal(true)) {
return;
}
//64位bits
RefCountBits newbits;
do {
newbits = oldbits;
bool fast = newbits.incrementStrongExtraRefCount(inc);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false))
return;
return incrementSlow(oldbits, inc);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
- 回退到
HeapObject
,从InlineRefCounts
进入,其中是c++中的模板定义,是为了更好的抽象,在其中查找bits
(即decrementStrongExtraRefCount
方法)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 对inc做强制类型转换为 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000
//这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
例如以t
的refCounts
为例(其中62-33位是strongCount
,每次增加强引用计数增加都是在33-62位上增加的,固定的增量为1左移33位
,即0x200000000
)
只有
t
时的refCounts
是 0x0000000200000002t + t1
时的refCounts
是 0x0000000400000002 = 0x0000000200000002 + 0x200000000t + t1 + t2
时的refCounts
是 0x0000000600000002 = 0x0000000400000002 + 0x200000000
针对上面的例子,可以通过CFGetRetainCOunt
获取引用计数,发现依次是 2、3、4,默认多了一个1
- 如果将
t、t1、t2
放入函数中,还会再次retain一次
为什么是0x200000000
?
因为1左移33位,其中4位为一组,计算成16进制,剩余的33-32位0x10
,转换为10进制为2。其实际增加引用计数就是1
swift与OC强引用计数对比
OC
中创建实例对象时为0
swift
中创建实例对象时默认为1
内存管理 - 弱引用
我们先看下面的代码
class HZMTeacher{
var age: Int = 20
var name: String = "HZM"
var stu : HZMStudent?
}
class HZMStudent {
var age: Int = 18
var name: String = "HZM2"
var teacher: HZMTeacher?
}
func testCount() {
var t = HZMTeacher()
weak var t1 = t
print("end")
}
查看t
的引用计数变化
- 本质上
t1 = t
并没有增加引用计数,但是t的地址存放的内容却发生了变化 - 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为
nil
的
在t1
处加断点,查看汇编
查看 源码中的 swift_weakInit
函数,这个函数是由WeakReference
来调用的,相当于weak
字段在编译器声明过程中就自定义了一个WeakReference
的对象,其目的在于管理弱引用
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
进入nativeInit
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
进入formWeakReference
,创建sideTable
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
//创建 sideTable
auto side = allocateSideTable(true);
if (side)
// 如果创建成功,则增加弱引用
return side->incrementWeak();
else
return nullptr;
}
进入allocateSideTable
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
// 第一步、先拿到原本的引用计数
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
// FIXME: custom side table allocator
//第二步、创建sideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//第三步、将创建的地址给到InlineRefCountBits
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
1、先拿到原本的引用计数
2、创建sideTable
3、将创建的sideTable
地址给InlineRefCountBits
,并查看其初始化方法,根据sideTable
地址作了偏移操作并存储到内存,相当于将sideTable
直接存储到了64位的变量中
所以上面的0xc0000000200abb32
是HeapObjectSideTableEntry
实例对象的内存地址,即散列表的地址
(除去63、62位)
第一反应为啥要存它?我们继续看
查看HeapObjectSideTableEntry
定义,其中有object
对象、refCounts
继续往里进
进入SideTableRefCounts
,同InlineRefCounts
类似,实际做事的是SideTableRefCountBits
,继承自RefCountBitsT
(存的是uint64_t类型的64位的信息),还有一个uint32_t
的weakBits
,即32位的位域信息
以0xc0000000200a45c4
为例,将62、63位清零,变成0x200A45C4
,然后左移3位(即InlineRefCountBits
初始化方法),变成0x100522E20
即HeapObjectSideTableEntry
对象地址,即散列表地址,然后通过x/8g
读取(直接用编程计算器敲的)
发现最终结果的首地址就是我们当前的实例对象
并且第二行是我们的强引用计数
,弱引用计数
总结一下
对于HeapObject
来说,其refCounts
有两种:
- 无弱引用:
strongCount
+unownedCount
- 有弱引用:
object + xxx + (strongCount + unownedCount) + weakCount
HeapObject {
InlineRefCountBit {strong count + unowned count }
HeapObjectSideTableEntry{
HeapObject *object
xxx
strong Count + unowned Count(uint64_t)//64位
weak count(uint32_t)//32位
}
}
循环引用
func test(){
var age = 10
let closure = {
age += 1
}
closure()
print(age)
}
test()
<!--打印结果--!>
11
从输出结果中可以看出:闭包内部对变量的修改将会改变外部原始变量的值
,主要原因是闭包会捕获外部变量,这个与OC中的block是一致的
来看下面代码
class HZMTeacher {
var age = 18
//反初始化器(当前实例对象即将被回收)
deinit {
print("HZMTeacher deinit")
}
}
func test(){
var t = HZMTeacher()
}
test()
<!--打印结果--!>
HZMTeacher deinit
修改例子,通过闭包修改其属性值
class HZMTeacher {
var age = 18
//反初始化器(当前实例对象即将被回收)
deinit {
print("HZMTeacher deinit")
}
}
var t = HZMTeacher()
let clourse = {
t.age += 1
}
clourse()
<!--打印结果--!>
19
再次修改
class HZMTeacher {
var age = 18
//反初始化器(当前实例对象即将被回收)
deinit {
print("HZMTeacher deinit")
}
}
func test(){
var t = HZMTeacher()
let clourse = {
t.age += 1
}
clourse()
}
<!--运行结果-->
HZMTeacher deinit
根据运行结果我们发现,闭包对 t
并没有强引用
继续修改
class HZMTeacher {
var age = 18
var completionBlock: (() ->())?
deinit {
print("HZMTeacher deinit")
}
}
func test(){
var t = HZMTeacher()
t.completionBlock = {
t.age += 1
}
}
test()
从运行结果发现,没有执行deinit
方法,即没有打印HZMTeacher deinit
,所以这里有循环引用
循环引用的解决方法
Swift中有两种解决方式
1.weak
使用weak
修饰闭包传入的参数,其中参数的类型是optional
func test(){
var t = HZMTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
}
2.unowned(无主引用)
使用unowned
修饰闭包参数,与weak
的区别在于unowned
不允许被设置为nil
,即总是假定有值
的,容易出现野指针情况。
func test(){
var t = HZMTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
}
捕获列表
[weak t] / [unowned t]
在swift中被称为捕获列表
1.定义在参数列表之前
2.[书写方式]捕获列表被写成用逗号括起来的表达式列表,并用方括号括起来
3.如果使用捕获列表,则即使省略参数名称、参数类型和返回类型,也必须使用in关键字
4.[weak t] 就是取t的弱引用对象 类似weakself
我们看下面代码
func test(){
var age = 0
var height = 0.0
//将变量age用来初始化捕获列表中的常量age,即将0给了闭包中的age(值拷贝)
let clourse = {[age] in
print(age)
print(height)
}
age = 10
height = 1.85
clourse()
}
<!--打印结果--!>
0
1.85
所以从结果中可以得出:对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量/变量,来初始化捕获列表中定义的常量。有以下几点说明:
捕获列表中的常量是值拷贝,而不是引用
捕获列表中的常量的相当于复制了变量age的值
捕获列表中的常量是只读的,即不可修改
swift中Runtime探索
class HZMTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
let t = HZMTeacher()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(HZMTeacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[I]{
let methodName = method_getName(method)
print("方法列表:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(HZMTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[I]{
let propertyName = property_getName(property)
print("属性成员属性:\(property)")
}else{
print("没有找到你要的属性")
}
}
print("test run")
}
test()
<!--打印结果--!>
test run
结果发现并没有打印方法和属性
- 给属性 和 方法 都加上
@objc
,可以打印吗?
从运行结果看,是可以打印,但是由于类并没有暴露给OC,所以OC是无法使用的,这样做是没有意义的
- 如果
swift
的类继承NSObject
,没有@objc
修饰属性和方法,是否可以打印全部属性+方法?
从结果发现获取的只有init
方法,主要是因为在 swift.h
文件中暴露出来的只有init
方法
如果想让OC能使用,必须类继承NSObject + @objc修饰属性、方法
- 如果去掉
@objc
修饰属性,将方法改成dynamic
修饰,是否可以打印方法?
从结果可以看出,依旧不能被OC获取到,需要修改为@objc dynamic
修饰
结论:
对于纯swift类
来说,没有
动态特性dynamic
(因为swift是静态语言),方法和属性不加任何修饰符的情况下,已经不具备runtime
特性,此时的方法调度,依旧是函数表调度即V_Table调度
对于纯swift类
,方法和属性添加@objc
标识的情况下,可以通过runtime API获取
到,但是在OC
中是无法进行调度
的,原因是因为swift.h文件中没有swift类的声明
对于继承自NSObject类
来说,如果想要动态的获取当前属性+方法
,必须在其声明前添加 @objc
关键字,如果想要使用方法交换
,还必须在属性+方法前添加dynamic关键字
,否则当前属性+方法只是暴露给OC使用,而不具备任何动态特性
objc源码验证(由于xcode12.2暂时无法运行objc源码,下列验证图片仅供参考)
- 进入
class_copyMethodList
源码,断住,查看此时的cls
,其中data()
存储类的信息
进入data
,打印bits、superclass
从这里可以得出swift中有默认基类
,即_SwiftObject
打印methods
swift源码中搜索_SwiftObject
,继承自NSObject,在内存结构上与OC基本类似
的
#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
@private
Class isa;
//refCounts
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
在之前的文章中有提到,其中TargetAnyClassMetadata
继承自TargetHeapMetaData
,其中只有一个属性kind
,TargetAnyClassMetadata
有四个属性:isa、superclass、cacheData、data即bits
所以swift为了保留和OC交互,其在底层存储的数据结构上和OC是一致的
objc源码
中搜索swift_class_t
,继承自objc_class
,保留了OC模板类的4个属性,其次才是自己的属性
struct swift_class_t : objc_class {
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
为什么继承NSObject?
必须通过NSObject声明,来帮助编译器判断,当前类是一个和OC交互的类
元类型、AnyClass、Self
AnyObject
AnyObject
:代表任意类的instance、类的类型、仅类遵守的协议
class HZMTeacher: NSObject {
var age: Int = 18
}
var t = HZMTeacher()
//此时代表的就是当前HZMTeacher的实例对象
var t1: AnyObject = t
//此时代表的是HZMTeacher这个类的类型
var t2: AnyObject = HZMTeacher
//继承自AnyObject,表示JSONMap协议只有类才可以遵守
protocol JSONMap: AnyObject { }
如果是结构体遵守协议,会报错,表示JSONMap协议只有类才可以遵守
Any
Any
:代表任意类型,包括 function类型 或者Optional类型
,可以理解为AnyObject是Any的子集
//如果使用AnyObject会报错,而Any不会
var array: [Any] = [1, "HZM", "", true]
AnyClass
AnyClass
:代表任意类的实例的类型 ,类型是AnyObject.Type
查看定义,是public typealias AnyClass = AnyObject.Type
T.self & T.Type
T.self
:如果T是实例对象,返回的就是它本身,如果T是类,那么返回的是MetaData
T.Type
:一种类型
T.self
是 T.Type
类型
简单看一个例子
//此时的self类型是 HZMTeacher.Type
var t = HZMTeacher.self
再看下下面的例子:
var t = HZMTeacher()
//实例对象地址:实例对象.self 返回实例对象本身
var t1 = t.self
//存储metadata元类型
var t2 = HZMTeacher.self
type(of:)
type(of:):
用来获取一个值的动态类型
var age = 10 as NSNumber
print(type(of: age))
<!--打印结果--!>
__NSCFNumber
//value - static type 静态类型:编译时期确定好的
//type(of:) - dynamic type:Int
var age = 10
//value的静态类型就是Any
func test(_ value: Any){
print(type(of: value))
}
test(age)
<!--打印结果--!>
Int
实践一下下
class HZMTeacher{
var age = 18
var double = 1.85
func teach(){
print("HZMTeacher teach")
}
}
class HZMPartTimeTeacher: HZMTeacher {
override func teach() {
print("HZMPartTimeTeacher teach")
}
}
func test(_ value: HZMTeacher){
let valueType = type(of: value)
value.teach()
print(value)
}
var t = HZMPartTimeTeacher()
test(t)
<!--打印结果--!>
HZMPartTimeTeacher teach
HZMTest.HZMPartTimeTeacher
运行时value
的类型还是HZMPartTimeTeacher
,只是传参的时候告诉编译器是HZMTeacher
类型
protocol TestProtocol {
}
class HZMTeacher: TestProtocol{
var age = 18
var double = 1.85
func teach(){
print("HZMTeacher teach")
}
}
func test(_ value: TestProtocol){
let valueType = type(of: value)
print(valueType)
}
var t = HZMTeacher()
let t1: TestProtocol = HZMTeacher()
test(t)
test(t1)
<!--打印结果--!>
HZMTeacher
HZMTeacher
3.如果将test中参数的类型修改为泛型,此时的打印是什么?
func test<T>(_ value: T){
let valueType = type(of: value)
print(valueType)
}
<!--打印结果--!>
HZMTeacher
TestProtocol
从结果中发现,打印并不一致,原因是因为当有协议、泛型时
,当前的编译器并不能推断出准确的类型,需要将value转换为Any
,修改后的代码如下:
func test<T>(_ value: T){
let valueType = type(of: value as Any)
print(valueType)
}
总结
当无弱引用时,HeapObject
中的refCounts
等于 strongCount
+ unownedCount
当有弱引用时,HeapObject
中的refCounts
等于 object
+ xxx
+ (strongCount + unownedCount)
+ weakCount
循环应用可以通过weak / unowned
修饰参数来解决
swift中闭包的捕获列表
是值拷贝,即深拷贝
,是一个只读的常量
swift由于是静态语言
,所以属性、方法
在不加任何修饰符的情况下时是不具备动态性即Runtime特性
的,此时的方法调度是V-Table函数表调度
如果想要OC
使用swift类中的方法、属性
,需要class继承NSObject,并使用@objc修饰
如果想要使用方法交换
,除了继承NSObject+@objc修饰
,还必须使用dynamic修饰
Any:任意类型,包括function
类型、optional
类型
AnyObject:任意类的instance
、类的类型、仅类遵守的协议,可以看作是Any的子类
AnyClass:任意实例类型,类型是AnyObject.Type
T.self:如果T是实例对象,则表示它本身,如果是类,则表示metadata.T.self
的类型是T.Type