swift的内存管理逻辑你是不是还不知道

背景

前面swift类对象和示例对象探究文章,我们知道它们的底层逻辑以及内存布局,这篇文章我们再探究下swift的内存管理

值类型内存

js 复制代码
var var1 = StructMemoryTest()
print("var1:")
print(withUnsafePointer(to: &var1) {ptr in print(ptr)})
var var2 = var1
print("var2: ")
print(withUnsafePointer(to: &var2) {ptr in print(ptr)})
var var3 = var2
print("var3:")
print(withUnsafePointer(to: &var3) {ptr in print(ptr)})

运行: 可以看到对于结构体值类型的赋值是深拷贝

再深入探究下底层逻辑

js 复制代码
import Foundation

struct Student {
    var name:String
    var age:Int
}

var stu = Student(name: "小李", age: 28)

我们发现可以直接调用Student的初始化方法,但是我们并没有写Student的初始化方法 编译为sil,发现实际底层给添加了初始化方法

再通过一个例子我们修复test2的age,test1的age也不会受影响

js 复制代码
struct StructMemoryTest {
    var name:String?
    var age:Int?
}

var test1 = StructMemoryTest(name: "小李", age: 28)
var test2 = test1
test2.age = 30

print("test1.age:\(test1.age) test2.age:\(test2.age)")

总结值类型

  • 值类型存储在栈空间
  • 值类型的赋值都是值传递,深拷贝

引用类型内存

引用类型分强弱引用,下面我们都分别看下实现原理

强引用

js 复制代码
class ClassMemoryTest {
    var name:String?
    var age:Int?
}

var t = ClassMemoryTest()
var t1 = t

运行看t赋值给t1,它们的内存地址是不一样的

再看下类的内存布局 前面章节我们知道swift类的前16位分别是元类和引用计数相关数据

说明:

  • 直接将对象赋值给一个变量,就是强引用
  • 可以查看此时的refCounts的值

1.refCounts的认识

通过在底层源码中查找HeapObject来查看对象引用的计数。 引用计数refCounts属性其本质是RefCountsInt类型,包含unit32_t Type和int32_t SignedType

👇1.1 HeapObject

js 复制代码
// HeapObject头文件中不被标准的Objective-C实例共享的成员
// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts
  
/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  /// 总是一个指向元类的指针
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;

  // 引用计数相关
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

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

说明:

  • 对象在底层是HeapObject
  • 所以在源码中查看HeapObject,发现引用是SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
  • 通过SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS的定义可以看到是refCounts的类型是InlineRefCounts
  • HeapObject的构造函数执行插传入引用计数位InlineRefCounts::Initialized

👇1.2 InlineRefCounts

js 复制代码
typedef RefCounts<InlineRefCountBits> InlineRefCounts;

...

template <typename RefCountBits>
class RefCounts {
    std::atomic<RefCountBits> refCounts;
...
}
  • 查看InlineRefCounts,发现它是RefCounts的别名
  • 而RefCounts的成员是refCounts,它是由RefCountBits决定的
  • 因此是InlineRefCounts通过InlineRefCountBits来决定里面的值

👇1.2.1 InlineRefCountBits

js 复制代码
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • InlineRefCountBits是RefCountBitsT的别名,传入RefCountIsInline

👇1.2.2 RefCountBitsT

js 复制代码
// Basic encoding of refcount and flag data into the object's header.
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;
 ...  
}

struct RefCountBitsInt<RefCountNotInline, 4> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
  • 可以看到有一个bits属性,这个属性就是Bits
  • bits其实质是将RefCountBitsInt中的type属性取了一个别名
  • 所以bits的真正类型是uint64_t即64位整型的数组,包含Type和SignedType两个成员

总结:

到这里有个重要消息是refCounts中其实包含有Bits成员,而这个成员是64位整型的数组。

2. refCounts的赋值探索

前面文章提高对象创建过程,swift源码是从swift_allocObject开始的

js 复制代码
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  ::new (object) HeapObject(metadata);

  ...

  return object;
}

// HeapObject构造函数
constexpr HeapObject(HeapMetadata const *newMetadata) 
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }  
  • 通过swift_allocObject方法来构造对象,因此在这里会进行对象中引用属性的赋值
  • 可以看到是通过refCounts来给引用计数赋值的,传入的参数是Initialized

👇2.1 Initialized

js 复制代码
enum Initialized_t { Initialized };
  • 进入Initialized定义,是一个枚举
  • 其对应的refCounts方法中,看出真正干事的是RefCountBits

👇 2.2 RefCountBits

js 复制代码
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}
  • 进入RefCountBits定义,也是一个模板定义

👇 2.2 RefCountBitsT

js 复制代码
SWIFT_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
	   (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
	   (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
{ }
  • 这里就是真正的初始化地方
  • 实际上是做了一个位域操作

👇2.3 RefCountsBit结构 经过上文的查找,可以得到RefCountsBit结构

  • isImmortal(0)
  • UnownedRefCount(1-31):unowned的引用计数
  • isDeinitingMask(32):是否进行释放操作
  • StrongExtraRefCount(33-62):强引用计数
  • UseSlowRC(63)

总结设置引用计数

设置引用计数实际是对底层的RefCountsBit数据结构进行位域操作

👇2.4 创建对象查看引用计数

js 复制代码
class Teacher {
    func teach() {
        print("教语文课")
    }
}

var teacher = Teacher()
var t1= teacher

2.4.1 第一次创爱对象

  • 先创建一个全局的实例变量
  • 之后创建对象
  • 将对象赋值给全局变量

2.4.2 第二次创建对象

  • 创建t1
  • 把t1赋值给%9
  • 把teacher %3 赋值为 %10
  • copy_addr 将 %10 赋值为 %9
    • %new = load $*LGTeacher(拿到这个对象)
    • strong_retain %new(给这个对象引用计数加一)
    • store %new to %9(将这个对象赋给t1) copy_addr最主要的就是对这个对象做了一下strong_ratain。之后再进行赋值操作

2.4.3 swift_retain的认识

strong_retain对应的就是 swift_retain,其内部是一个宏定义,内部是_swift_retain_,其实现是对object的引用计数作+1操作。

js 复制代码
SWIFT_ALWAYS_INLINE
static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}

...


// Increment the reference count.
SWIFT_ALWAYS_INLINE
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);

// 如果需要新增计数不等于1 并且 旧的bits是永生的 ->不处理后续逻辑
// 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));
}
  • 通过refCounts.increment(1);进行引用计数+1
  • 在increment方法中是通过incrementStrongExtraRefCount来实现计数的

2.4.5 incrementStrongExtraRefCount

js 复制代码
// Returns true if the increment is a fast-path result.
// Returns false if the increment should fall back to some slow path
// (for example, because UseSlowRC is set or because the refcount overflowed).
SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
incrementStrongExtraRefCount(uint32_t inc) {
// 对inc做强制类型转换为 BitsType // 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000 //这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
  • 通过位域进行偏移
  • 我们知道refCounts中的62-33位是strongCount,这里的位域目的就是将引用计数加在这里
  • 每次的增加为1左移33位
    • 也就是每次增加0x200000000
    • 也就是BitsType(inc) << Offsets::StrongExtraRefCountShift

总结:

  • swift中创建实例对象时默认为1
  • 强引用计数存储在refCounts中的33-62位,因此每次引用计数+1,都要加上0x200000000

弱引用

主要认识弱引用的使用、以及散列表的存储结构。散列表不仅存储了弱引用,还有强引用和无主引用,如果有弱引用,在refCounts中直接使用散列表的地址

弱引用的使用

js 复制代码
class Student {
    var name:String = ""
    var age:Int = 0
}

var stu = Student()
weak var weakStu = stu
  • 给变量t赋值给弱引用t1
  • 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的

debug调试看下汇编代码

  • 前面我们知道当把对象赋值另外一个变量时,底层会执行swift_allocObject析建变量承接数据,这里当把对象赋值为一个weak修饰的变量时,底层走swift_weakInit

swift_weakInit

js 复制代码
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

参数

参数 含义
ref 弱引用指针
value 弱引用指针指向的宿主对象
  • 查看swift_weakInit,可以看到系统本身提供了一个WeakReference类,用来进行引用计数的创建
  • 这个ref对象调用nativeInit,传入对象,就可以创建弱引用了

若引用对象的nativeInit函数

js 复制代码
void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
  • 调用宿主对象的refCounts的formWeakReference完成弱引用映射

宿主对象的refCounts的formWeakReference

js 复制代码
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
  • 创建散列表
  • 通过incrementWeak()给散列表增加弱引用

allocateSideTable创建散列表

js 复制代码
// 返回一个对象的side table散列表,如果有必要再创建
// Return an object's side table, allocating it if necessary.
// 如果对象在执行析构函数返回一个null出去
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  //1、先拿到原本的引用计数  
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  //2. 预检查操作
  //在创建新的side table之前判断旧的引用计数是否已经使用散列表side table 如果存在返回已经存在的side table
  // Preflight failures before allocating a new side table.
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  // 如果宿主对象正在执行析构函数直接返回null
  else if (failIfDeiniting && oldbits.getIsDeiniting()) {
    // Already past the start of deinit. Do nothing.
    return nullptr;
  }

  // Preflight passed. Allocate a side table.
  
  //3. 创建sidetable
  // 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;
}
  • 先拿到原本的引用计数
    • 散列表也需要存储强引用的引用计数
    • 如果这个bits本来就有散列表了,就直接使用原有的散列表
  • 创建散列表(通过allocateSideTable构建散列表)
    • 这个散列表只是针对当前对象的
  • 通过InlineRefCountBits将散列表的地址赋给值newBits
    • 因此如果有弱引用,那么引用计数中存储的就是散列表地址

InlineRefCountBits

js 复制代码
SWIFT_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
//将散列表通过地址偏移操作存入RefCountBits中
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
	   | (BitsType(1) << Offsets::UseSlowRCShift)
	   | (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
  • 传入的参数是HeapObjectSideTableEntry的方法,就是用来构建散列表的Bits
  • 将散列表地址通过偏移操作存储到RefCountBits内存中
  • 在这里可以看到,除去63、62位,剩下的地方用来存储了散列表地址

HeapObjectSideTableEntry

js 复制代码
class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic<HeapObject*> object; //对象
  SideTableRefCounts refCounts; //散列表计数
  ...
}
  • 查看散列表的结构
  • 包括object和refCounts
  • 其实可以看做分别将对象和引用计数作为键和值
  • 这里的引用计数表就是散列表

SideTableRefCountBits

  • 继承自RefCountBitsT
  • 只有一个属性weakBits,是弱引用属性
  • 在RefCountBitsT中还有一个属性,64位用来记录原有的强引用计数。

验证

计算过程

  • 散列表地址 = refCounts 清除63、62位 + 左移三位
  • 上面我们拿到了弱引用后的地址0xc000000020261de6
  • 将62、63清零,变成0x20261DE6
  • 然后左移3位变成0x10130EF30,这个就是散列表地址
  • 查看散列表内容
    • 包含两个内容,一个是对象,所以会打印对象的地址
    • 一个是引用表,引用表包含强引用计数和弱引用计数

总结弱引用:

  • 当给一个对象增加弱引用后,会创建一个散列表用来存储弱引用和强引用
  • 并且将散列表的地址存储到bits中

整体总结

  • 值类型都是神拷贝操作
  • HeapObject有两种refCounts方式
    • 没有弱引用
      • 没有弱引用,只有强引用和无主引用
      • strongCount + unonwnedCount
    • 有弱引用
      • 如果有弱引用,那么存储的是一个散列表地址
      • 而散列表中存储的是object和散列表信息
      • 散列表信息又包含了weakBits
      • 同时又因为继承RefCountBitsT,所以依然会包含强引用和无主引用。

到这里就完毕了,感谢您的阅读,欢迎阅读我的其他文章!

相关推荐
键盘敲没电5 小时前
【iOS】KVC
ios·objective-c·xcode
吾吾伊伊,野鸭惊啼5 小时前
2024最新!!!iOS高级面试题,全!(二)
ios
吾吾伊伊,野鸭惊啼5 小时前
2024最新!!!iOS高级面试题,全!(一)
ios
不会敲代码的VanGogh6 小时前
【iOS】——应用启动流程
macos·ios·objective-c·cocoa
Swift社区9 小时前
Apple 新品发布会亮点有哪些 | Swift 周报 issue 61
ios·swiftui·swift
逻辑克10 小时前
使用 MultipeerConnectivity 在 iOS 中实现近场无线数据传输
ios
dnekmihfbnmv14 小时前
好用的电容笔有哪些推荐一下?年度最值得推荐五款电容笔分享!
ios·电脑·ipad·平板
Magnetic_h1 天前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...1 天前
「iOS」——单例模式
ios·单例模式·cocoa
yanling20232 天前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24