swift内存管理探索

1、概念

swift中使用自动引用计数(ARC)机制来追踪和管理内存

2、强引用

当前环境:Xcode 13.1,swift源码5.5.2
默认创建的对象,都是强引用的,这点跟oc是一样的。

2.1、打印对象信息

class YYPeople{
    var age :Int = 18
    var name :String = "lisi"
    var height :Double = 175.0
}
//打印指针信息
//注意此处不能使用po p,po会增加p的引用计数
print(Unmanaged.passUnretained(p as AnyObject).toOpaque())
var p = YYPeople()
print("end")
输出信息.png

po会使p的引用计数+1,但是看po前后的变化0x0000000000000003->0x0000000200000003->0x0000000400000003,通过计算器查看三个数据,可以看出p的引用计数+1后,引用计数信息第33位变成了1;再+1后,引用计数信息第34位变成了1


计算器查看数据.png

可以看到的是引用计数的变化,并不是直接+1,而是refercount存储的信息发生变化。

2.2、通过源码探索

首先在HeapObject.cpp找到实例对象创建_swift_allocObject_方法

_swift_allocObject_.png

找到我们对象的结构体HeapObject

对象的结构体.png

找到refcount的定义

refcounts.png

InlineRefCounts.png

RefCounts.png

上一步调用RefCounts模板传入的是InlineRefCountBits

InlineRefCountBits.png

RefCountBitsT.png

RefCountBitsT是一个模板类,传入的是RefCountIsInline,这个模板类只有一个BitsType类型的参数bits。找到BitsType的定义typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type BitsType;,点击进去RefCountBitsInt

RefCountBitsInt.png

最终可以看到引用计数本质是一个uint64_t类型的信息。再回到第一步对象的结构体HeapObject中的RefCount类型

RefCount初始化.png

RefCounts初始化.png

从上面找源码的过程中,可以知道RefCountBits其实就是RefCountBitsT,找到初始化方法

RefCountBitsT初始化.png

strongExtraCount传入的是0
unownedCount传入的是1

计算StrongExtraRefCountShift

StrongExtraRefCountShift.png

StrongExtraRefCountShift = shiftAfterField(IsDeiniting) = IsDeinitingShift + IsDeinitingBitCount = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount
StrongExtraRefCountShift计算.png

所以StrongExtraRefCountShift = 0 + 1 + 31 + 1 = 33
PureSwiftDeallocShift = 0
UnownedRefCountShift = 1

0 << 33 = 0
1 << 0 = 1
1 << 1 = 2
所以最终的计算结果是(0|1|2) = 3
这就对应上了2.1当中打印结果


打印结果.png

2.3、对象赋值操作

下面来看看赋值操作,引用计数会有啥变化


赋值操作.png

从sil代码看看赋值的底层操作

赋值操作.png

通过sil文档查看copy_addr的作用
copy_addr.png

继续降级成IR代码
IR代码.png

可以看出来p赋值给p1就是调用swift_retain方法,从源码查看
swift_retain.png

incrementNonAtomic.png

incrementStrongExtraRefCount.png

所以赋值操作最终是引用计数增加1<<33 ,也就是RefCount的高33位+1
完美对应上了我们输出的RefCount变化

3、弱引用

3.1、作用

弱引用不会对其引用的对象保持强引用,因此不会阻止ARC释放被引用的实例对象,这个特性可以阻止循环引用的产生。声明的属性或者变量前面加上weak关键字表明是弱引用。
swift中弱引用必须是可选类型,因为引用的实例被释放后,ARC会自动将其置为nil。

3.2、源码探索

代码.png

3.2.1、汇编代码

汇编.png

调用了swift_weakInit方法

3.2.2、源码流程

swift_weakInit.png

nativeInit.png

formWeakReference.png.png

allocateSideTable.png

initRefCounts.png

常量值.png

计算

SideTableUnusedLowBits :3
UseSlowRCShift = shiftAfterField(StrongExtraRefCount) = StrongExtraRefCountShift + StrongExtraRefCountBitCount =
33 + 30 = 63
SideTableMarkShift = SideTableBitCount = 62
可以看出也是将side右移3位存储到64位的信息当中,并在63位和62位设置标记位置

HeapObjectSideTableEntry.png

SideTableRefCountBits.png

SideTable 是HeapObjectSideTableEntry类型,也有refCounts,内部是SideTableRefCountBits,就是在原来的uint64_t加上一个uint32_t

3.2.3、代码输出

弱引用.png

弱引用后的引用计数是0xc000000020c010da,我们将引用计数还原成HeapObjectSideTableEntry的side(上面的计算流程反过来:去62,63位去1,然后再左移3位)

计算器还原side.png

通过计算器计算得到sideTable的地址,然后再读取其中信息


代码验证side.png

3.2.4 引用计数总结

一个实例对象在首次初始化的时候,是没有sideTable的,当我们创建一个弱引用的时候,才会创建sideTable
对于HeapObject来说就会存在两种情况的引用计数的布局方式

//没有弱引用情况
  HeapObject {
    isa
    InlineRefCounts {
      atomic<InlineRefCountBits> {
        strong RC + unowned RC + flags
        OR
        HeapObjectSideTableEntry*
      }
    }
  }
//有弱引用情况
  HeapObjectSideTableEntry {
    SideTableRefCounts {
      object pointer
      atomic<SideTableRefCountBits> {
        strong RC + unowned RC + weak RC + flags
      }
    }   
  }

InlineRefCountsSideTableRefCounts公用模板类RefCounts<T>的实现
InlineRefCountBitsSideTableRefCountBits公用模板RefCountBitsT<bool>

4、无主引用

和弱引用类似,无主引用不会对实例强持有。不同于弱引用的是,无主引用是假定永远有值的。


崩溃.png

unowned是假定永远有值的,当前p是nil,所以会崩溃。在使用unowned的时候需要慎用

总结:

  • 如果两个对象的生命周期和对方完全没有关系(其中一方无论何时置为nil,都不会影响对象),请用weak
  • 如果确保一个对象销毁,另一个对象也会跟着销毁,此时就可以用unowned
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容