Swift内存管理初探

HeapObject

在Swift中,一个Class对象实际上就是一个HeapObject结构体指针。那么它的内存布局是怎样的呢? 首先我们先来看一下 HeapObject 的结构:

struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

  // Initialize a HeapObject header for an immortal object
  constexpr HeapObject(HeapMetadata const *newMetadata,
                       InlineRefCounts::Immortal_t immortal)
  : metadata(newMetadata)
  , refCounts(InlineRefCounts::Immortal)
  { }

};

从中我们可以看到:
第一个字段是一个 Metadata 对象,该对象有着与OC中 isa_t 类似的作用,用来描述对象类型的(等价于 type(of:) 取得的结果)! 但本文的重点不是在这,我们继续往下看!

这才是本文的主角哦: SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS (这是一个宏定义) !

我们点开这个宏来看看这是个啥?

RefCounts<InlineRefCountBits> refCounts;

这就是Swift中引用计数 refCounts了,引用计数、弱引用、unowned 引用都与它有关!

继续深挖定义refCounts的这个类 InlineRefCounts

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts

InlineRefCounts: 是用在常规对象以及无主引用中
SideTableRefCounts: 则是用在weak引用对象中

InlineRefCounts 被起了个别名为 RefCounts,我们看继续看下 RefCounts类结构

template <typename RefCountBits>    
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ....
}

这里省略了所有的方法和类型定义! 注意: 这是模版类,那么就要注意传进来的模版参数

首先我们来看看 InlineRefCountBits (常规对象)

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {

  friend class RefCountBitsT<RefCountIsInline>;
  friend class RefCountBitsT<RefCountNotInline>;

  static const RefCountInlinedness Inlinedness = refcountIsInline;

  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
    SignedBitsType;
  typedef RefCountBitOffsets<sizeof(BitsType)>
    Offsets;

  BitsType bits;

  // ...

};

通过模板替换之后,InlineRefCountBits 实际上就是一个 uint64_t! 位图如下:


占位@2x.png

接下来我们在看看 SideTableRefCounts (weak引用)

首先我们先来写一段代码,看看能从汇编中发现什么?

class People {
    var age = 10
}

let p = People()
weak var p2 = p
汇编@2x.png

咦,这个swift_weakInit是什么? 它又做了什么? 我们继续往里看

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

从上述代码可以看出该对象为WeakReference类! 那么 weak 变量, 编译器则会在声明的过程中就定义了WeakReference对象,该对象主要目的就是用来管理当前的弱引用对象!
该对象调用了其中的 nativeInit(value) 方法;传递了当前的HeapObject对象

继续看 nativeInit 具体操作

void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}

如果当前对象不为空则调用object->refCounts.formWeakReference()方法、我们查看下 formWeakReference方法的实现

template <> HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

创建SideTable、如果创建成功,则执行 incrementWeak();
查看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;
  }
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  auto newbits = InlineRefCountBits(side);
  ....
  return side;
}
  • 首先拿到原有的引用计数 refCounts.load(SWIFT_MEMORY_ORDER_CONSUME)
  • 然后通过 getHeapObject() 创建一个 HeapObjectSideTableEntry 实例对象 side
  • 再把创建出来的 side对象地址给 InlineRefCountBits 、而InlineRefCountBits 就是我们分析强引用计数的 RefCountBitsT!

顺便一起来看一下 HeapObjectSideTableEntry 的结构:

class HeapObjectSideTableEntry {
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
  public:
  HeapObjectSideTableEntry(HeapObject *newObject)
    : object(newObject), refCounts()
  { }
  ......
}

在HeapObjectSideTableEntry中保留了原对象的Metadata和refCound!
还有bits(强引用、无主引用的信息) 以及新增的 weakBits(弱引用信息)

小结

swift对象都是以HeapObject为模板创建!
RefCounts:
第一种是 InlineRefCounts ,用在 常规对象(无主) 中,它其实是一个 uint64_t!
第二种是SideTableRefCounts, 用在weak对象中,此时它是一个指向 SideTable(HeapObjectSideTableEntry) 的指针。

注意点: OC与Swift区别

OC:
弱引用计数是存放在全局维护的散列表中,isa中会记录是否使用了散列表。
在引用计数为0时,自动触发dealloc,会检查并清空当前对象的散列表计数。

Swift
弱引用计数也是存放在散列表中,这个散列表是局部的! 每个weak对象都有自己的散列表!
即便一个对象在使用了弱引用后,也不能保证相关内存全部被释放! 因为SideTable 的生命周期与对象是分离的,当强引用计数为 0 时,只有 HeapObject 被释放了,但不会触发SideTable的清空。而是在下次访问时发现当前对象为nil时,才会清空SideTable

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

推荐阅读更多精彩内容