【Swift底层】内存管理

原创
2021/07/23 08:29
阅读数 62

基础

  • Swift中用ARC机制来追踪和管理内存
  • 通过lldb探究
var cls = YKClass()
var cls2 = cls
var cls3 = cls

通过po指令获取变量地址并通过x/8g查看具体内容 可以看到此时的引用计数为3。

引用计数

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS InlineRefCounts refCounts;

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
// 可见该RefCounts是一个模版类,也就是说运行时实际取决于传进的模版参数`RefCountBits`
template <typename RefCountBits>
class RefCounts {
···
}

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

// RefCountIsInline
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };

// Basic encoding of refcount and flag data into the object's header.
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {}

RefCountBitsT

  • 在该类中,有一个成员变量BitsType bits,通过查看源码可以得知这就是一个64位整形
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
// 32-bit inline
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
  • 在RefCountsBitsT的初始化方法中,对传进的两个参数strongExtraCount``unownedCount执行了位移操作
 constexpr
  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
           (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
           (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
  { }

HeapObject

  • 在初始化方法中传进了两个参数。第一个为metaData,第二个则为refCounts。
  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  • enum Initialized_t { Initialized };可见其实为一个enum,再进一步查看可以发现
  // Refcount of a new object is 1.
  constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) { }

swift_retain

static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}

对引用计数的强引用计数执行+1的操作

实战探索

class YKClass {
    var name: String = "origin"
    var age: Int = 10
}

var cls = YKClass()
var cls1 = cls

将上述代码通过swiftc -emit-sil main.swift | xcrun swift-demangle > main.sil编译为sil文件查看赋值时究竟做了什么事。

  1. 创建全局变量
  2. 创建YKClass实例变量
  3. 可以看到将创建出来的实例变量存储到了全局变量cls中
  4. 又创建了一个全局变量
  5. 访问%3,将寄存器中的值放到全局变量cls1中 而这里的copy_addr实际上做了三件事:1. %new = load $*YKClass 2. strong_retain %new 3. store %new to %9

弱引用

  • weak声明
  • 弱引用声明的变量是一个可选类型,也就是在程序运行过程中是允许当前变量为nil的

底层探索

var cls = YKClass()
weak var cls2 = cls
print(CFGetRetainCount(cls as AnyObject))  // -> 2

lldb来查看该变量的内存地址中存放的值 根据打印可以看出,本质引用计数为2,没有变化。但是问题来了,本该存储引用计数的地址存放的这个0xc0000c000031ea28是什么东西?

  1. 打开Always Show Assembly可以看到调用底层的了swift_weakInit方法
  2. 源码搜索
// swift_weakInit
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return  ref;
}
  1. WeakReference用于管理弱引用,一个用weak修饰的变量,即有一个WeakReference值。
class WeakReference {
	···
    std::atomic<WeakReferenceBits> nativeValue;
	···
  }
  1. nativeInit判断object是否为空,若不为空则调用refCounts.formWeakReference()
void nativeInit(HeapObject *object) {
  auto side = object ? object->refCounts.formWeakReference() : nullptr;   nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
  1. formWeakReference,创建sideTableincrementWeak
// formWeakReference
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)
{
	// 1. 获取原有引用计数
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
	···
	// 2. 创建HeapObjectSideTableEntry对象并传给InlineRefCountBits初始化函数,我们查看过InlineRefCountBits其实是RefCountBitsT类型,在RefCountBitsT传入对应参数的初始化函数中可以看到对sideTable的地址做了一个偏移的操作,再放到内存中,上面我们看到的很奇怪的地址也就是偏移后的HeapObjectSideTableEntry对象地址
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  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;
}

循环引用【闭包】

  • 闭包一般默认捕获外部变量
  • 用weak弱引用
  • unowned无主引用:不允许被设置为nil,修饰的变量在程序运行期间都是假定有值的,使用不当容易出现野指针的情况
  • 捕获列表:定义在参数列表之前。捕获列表被写为用方括号括起来的表达式列表。如果使用捕获列表,则即使省略参数名称,参数类型和返回类型,也必须使用in关键字。对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量或变量,来初始化捕获列表中定义的常量。
展开阅读全文
c++
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部