iOS 开发每天分享优质文章专注iOS开发的小渣渣

swift底层探索 07 -内存管理(refCount&weak

2020-12-28  本文已影响0人  Henry________

提到内存管理在iOS开发中,就不得不提ARC(自动引用技术)。本文主要讨论的就是ARC在swift中是如何存储、计算,以及循环引用是如何解决的。
[toc]

一, refCount引用计数(强引用 + 无主引用)

先看一段简单的代码

class classModel{
    var age : Int = 18
}
func test() {
    let c = classModel()
    var c1 = c
    var c2 = c
}
test()

通过LLDB添加断点查看当前c对象的内存情况

图一

1. cfGetRetainCount - sil解析

class classModel{
    var age : Int = 18
}
let temp = classModel()
CFGetRetainCount(temp)

编译后的Sil文件:


图二

2. refCount - 类型的源码

swift底层探索 01 - 类初始化&类结构一文中有对swift类的源码进行过简单的解释。

相信你一定会有疑惑:0x0000000600000002是什么?它为什么被叫做refCount,探索方法依旧是翻开源码!

(1) 该方法是swift对象初始化方法
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
(2) InlineRefCounts类型
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
(3) RefCounts类
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
  //省略方法
}
(4) InlineRefCountBits类型
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
(5) RefCountIsInline枚举
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
(6) RefCountBitsT 核心类
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
    //内部变量
    BitsType bits;
    //内部变量类型
    typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;

    ...
    //省略无关代码
}
(7) RefCountBitsInt 结构体
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
(8) 【总结】

3. refCount - 初始化的源码

现在再看0x0000000600000002知道它是一个uint64_t的值,可是内部存储了哪些值还需要查看初始化方法,观察初始化方法做了什么?

(1) 该方法是swift对象初始化方法
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
(2) RefCounts初始化方法
template <typename RefCountBits>
class RefCounts {
    std::atomic<RefCountBits> refCounts;
    
    enum Initialized_t { Initialized };
    
 constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}
    ...
    //省略无关代码
}
(3) RefCountBitsT初始化方法
  constexpr
  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
           (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
           (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
  { }
(4)Offsets对应关系
Offsets的关系图: 简书-月月
(5)【总结】
0x0000000600000002 >> 33 // 引用计数 = 3
补充1:
补充2:
class PersonModel{
    var age : Int = 18
}
func test() {
    let c = PersonModel()
    var c1 = c
    var c2 = c
    var c3 = c
    //增加了一个无主引用
    unowned var c4 = c
}
test()
图三-输出结果

4. 引用计数增加、减少

知道了引用计数的数据结构初始化值,现在就需要知道引用计数是如何增加减少,本文中以增加为例;

通过打开汇编,查看调用堆栈:


图三
swift_retain源码
//入口函数
HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}

static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    //引用计数在该函数进行+1操作
    object->refCounts.increment(1);
  return object;
}
increment
图四

通过可执行源码进行调试可执行源码

具体计算的方法
图五

二, refCount 循环引用

class PersonModel{
    var teach : TeachModel?
}
class TeachModel{
    var person : PersonModel?
}

面对这样的相互包含的两个类,使用时一定会出现相互引用(循环引用)

图六

1. weak关键字

通过OC的经验,可以将其中一个值改为weak,就可以打破循环引用.

class PersonModel{
    weak var teach : TeachModel?
}
class TeachModel{
    weak var person : PersonModel?
}
图六

2. weak 实现源码

weak var weakP = PersonModel()

依旧是打开汇编断点.

图七
swift_weak源码
//weak入口函数
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

void nativeInit(HeapObject *object) {
//做一个非空判断
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
通过formWeakReference创建HeapObjectSideTableEntry
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
调用allocateSideTable进行创建
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
    //获取当前对象的原本的引用计数(uInt64_t)
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  ...
  
  // FIXME: custom side table allocator
  
  //创建HeapObjectSideTableEntry对象
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
    //RefCountBitsT对象进行初始化
  auto newbits = InlineRefCountBits(side);
  
  do {
    if (oldbits.hasSideTable()) {
      auto result = oldbits.getSideTable();
      delete side;
      return result;
    }
    else if (failIfDeiniting && oldbits.getIsDeiniting()) {
      return nullptr;
    }
    side->initRefCounts(oldbits);
    //通过地址交换完成赋值
  } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
  return side;
}
HeapObjectSideTableEntry对象
class HeapObjectSideTableEntry {
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
    ...
}

class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
    //weak_count
  uint32_t weakBits;
}

class RefCountBitsT {
    //Uint64_t就是strong_count | unowned_count
    BitsType bits;
}

通过源码分析得出HeapObjectSideTableEntry对象的内存分布

RefCountBitsT初始化

最终保存到实例对象的refcount字段的内容(RefCountBitsT)创建

    //Offsets::SideTableUnusedLowBits = 3
    //SideTableMarkShift 高位 62位
    //UseSlowRCShift 高位 63位
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }

lldb验证

现在知道了refcount字段获取规律,以及sideTable对象的内部结构,现在通过lldb验证一下。

图八 图九 图十

weak_count 增加

weakcount是从第二位开始计算的。
formWeakReference函数中出现了side->incrementWeak();sideTable对象创建完成后调用了该函数.

  HeapObjectSideTableEntry* incrementWeak() {
    if (refCounts.isDeiniting())
      return nullptr;
      //没有销毁就调用
    refCounts.incrementWeak();
    return this;
  }
  
  void incrementWeak() {
    //获取当前的sideTable对象
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    RefCountBits newbits;
    do {
      newbits = oldbits;
      assert(newbits.getWeakRefCount() != 0);
      //调用核心自增函数
      newbits.incrementWeakRefCount();
      
      if (newbits.getWeakRefCount() < oldbits.getWeakRefCount())
        swift_abortWeakRetainOverflow();
        //通过值交换完成赋值
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }
  
  void incrementWeakRefCount() {
  //就是一个简单++
    weakBits++;
  }
  1. 在声明weak后,调用了incrementWeak自增方法;
  2. incrementWeak方法中获取了sideTable对象;
  3. incrementWeakRefCount完成了weakBits的自增;

注:在weak引用之后,在进行strong强引用后,refCount该如何计算呢?篇幅问题就不展开了,各位可以自己试试。

三, 捕获列表

本次换个例子。

class TeachModel{
    var age = 18
    var closure : (() -> Void)?
    deinit {
        print("deinit")
    }
}
func test() {
    let b = TeachModel()
    b.closure = {
        b.age += 1
    }
    print("end")
}

作用1-解决循环引用

func test() {
    let b = TeachModel()
    b.closure = {[weak b] in
        b?.age += 1
    }
    print("end")
}

func test() {
    let b = TeachModel()
    b.closure = {[unowned b] in
        b?.age += 1
    }
    print("end")
}

执行效果,都可以解决循环引用:


作用2-捕获外部变量

例如这样的代码:

func test() {
    var age = 18
    var height = 1.8
    var name = "Henry"
    
    height = 2.0
    //age,height被闭包进行了捕获
    let closure = {[age, height] in
        print(age)
        print(height)
        print(name)
    }
    
    age = 20
    height = 1.85
    name = "Wan"
    
    //猜猜会输出什么?    
    closure()
}
闭包捕获之后值发生了什么?

通过打开汇编调试,并查看寄存器堆栈信息.


几种基本汇编指令详解
上一篇 下一篇

猜你喜欢

热点阅读