Swift内存管理

OC一样,Swift中也是通过引用计数的方式来管理对象的内存的。在Swift类 结构探究中,分析过引用计数refCounts,它是RefCounts类型,class类型,占8字节大小。

一、强引用

class Animal {
    var age: Int = 10
    var name: String = "dog"
}
var t = Animal()
var t1 = t
var t2 = t

断点,查看t的的内存,refCounts0x0000000600000003

image.png

1.1

HeapObject开始分析引用计数的真正表示形态,源码 HeapObject -> InlineRefCounts

struct HeapObject {
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  ...
}

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

进入InlineRefCounts定义,是RefCounts类型的别名,而RefCounts是模板类,真正决定的是传入的类型InlineRefCountBits

typedef RefCounts InlineRefCounts;

template 
class RefCounts {
  std::atomic refCounts;
  ...
}

InlineRefCountBits,是RefCountBitsT类的别名:

typedef RefCountBitsT InlineRefCountBits;

RefCountBitsT,有bits属性:

template 
class RefCountBitsT {
    ...
      typedef typename RefCountBitsInt::Type
    BitsType;
    ...
    BitsType bits;
    ...
}

template <>
struct RefCountBitsInt {
  //类型
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

其中bits其实质是将RefCountBitsInt中的type属性取了一个别名,所以bits的真正类型是uint64_t,即64位整型数组

1.2 对象创建

然后,继续分析swift中对象创建的底层方法swift_allocObject,源码:

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
    ...
    new (object) HeapObject(metadata);
    ...
}


 constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

进入Initialized定义,是一个枚举,其对应的refCounts方法中,干事的是RefCountBits

  enum Initialized_t { Initialized };
  
  //对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}

进入RefCountBits定义,也是一个模板定义

template 
class RefCounts {
  std::atomic refCounts;
  ...
}

重点:最终,真正的初始化地方是下面这个,实际上是做了一个位域操作,根据的是Offsets

LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
       (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
       (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
{ }

referCounts的本质是RefCountBitsInt中的type属性,而RefCountBitsInt又是模板类RefCountBitsT的模板类型T,所以RefCountBits(0, 1)实质调用的是模板类RefCountBitsT构造方法strongExtraCount传值为0unownedCount传值为1
RefCountsBit的结构图,如下所示:

image.png

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

其中需要重点关注UnownedRefCountStrongExtraRefCount。至此,我们分析一下上面的引用计数值0x0000000600000003,用二进制展示:

image.png

如图,33位置开始的强引用计数StrongExtraRefCount0011,转换成十进制就是3
下面通过sil验证一下:
image.png

关于copy_addrsil文档有解释,其实现是对object的引用计数作+1操作。有兴趣的可以自己去查一下。
需要注意的是:var t = Animal()此时已经有了引用计数,即,OC中创建实例对象时为0;Swift中创建实例对象时默认为1
可以通过CFGetRetainCount获取引用计数:
image.png

二、弱引用

class Animal {
    var age: Int = 10
    var name: String = "cat"
    var dog: Dog?
}

class Dog {
    var age = 20
    var animal: Animal?
}

func test(){
    var t = Animal()
    weak var t1 = t
    
    print("end")
}

test()

查看 t的引用计数:

image.png

弱引用声明变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的。
t1处加断点,查看汇编,发现:swift_weakInit。查看源码,这个函数是由WeakReference来调用的,相当于weak字段编译器声明过程中就自定义了一个WeakReference的对象,其目的在于管理弱引用。

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

进入nativeInit

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

进入formWeakReference,创建sideTable

template <>
HeapObjectSideTableEntry* RefCounts::formWeakReference()
{
  //创建 sideTable
  auto side = allocateSideTable(true);
  if (side)
  // 如果创建成功,则增加弱引用
    return side->incrementWeak();
  else
    return nullptr;
}

进入allocateSideTable

template <>
HeapObjectSideTableEntry* RefCounts::allocateSideTable(bool failIfDeiniting)
{
  // 1、先拿到原本的引用计数
  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;
  }

  // Preflight passed. Allocate a side table.
  
  // FIXME: custom side table allocator
  //2、创建sideTable
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  // 3、将创建的地址给到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;
}

总结:

  1. 先拿到原本的引用计数;
  2. 创建sideTable
  3. 将创建的sideTable地址给InlineRefCountBits,并查看其初始化方法,根据sideTable地址作了偏移操作并存储到内存,相当于将sideTable直接存储到了64位的变量中。

通过上面的底层流程分析,我们可以get到关键的2点:
通过HeapObjectSideTableEntry初始化散列表

class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic object;
  SideTableRefCounts refCounts;
...
}

上述源码中可知,弱引用对象对应的引用计数refCountsSideTableRefCounts类型,而强引用对象的是InlineRefCounts类型。
接下来我们看看SideTableRefCounts

typedef RefCounts SideTableRefCounts;

继续搜索SideTableRefCountBits

image.png

里面包含了成员uint32_t weakBits,即一个32位域的信息。

通过InlineRefCountBits初始化散列表的数据

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }

这里继承的bits构造方法,而bits定义

BitsType bits;

typedef typename RefCountBitsInt::Type
    BitsType;

强引用一样,来到了RefCountBitsInt,这个之前分析过,就是uint64_t类型,存的是64位域信息。

综合12两点的论述可得出:64位 用于记录 原有引用计数32位 用于记录 弱引用计数

为何t的引用计数是:0xc0000000200e08ba

上述分析中我们知道,在 InlineRefCountBits初始化散列表的数据时,执行了(reinterpret_cast(side) >> Offsets::SideTableUnusedLowBits这句代码,而

static const size_t SideTableUnusedLowBits = 3;

side右移了3位,所以此时,将0xc0000000200e08ba 左移3位0x1007045D0,就是散列表的地址。再x/8g查看:

image.png

三、循环引用

var age = 10
let clourse = {
    age += 1
}
clourse()
print(age)

//打印结果
11

从输出结果中可以看出:闭包内部对变量的修改会改变外部原始变量的值,原因是闭包会捕获外部变量,这个与OC中的block一致的。

deinit
class Animal {
    var age = 10
    deinit {
        print("Animal deinit")
    }
}
func test(){
    var t = Animal()
    let clourse = {
        t.age += 10
    }
    clourse()
    print(t.age)
}
test()

//打印结果
//Animal deinit

可见,deinit是在当前实例对象即将被回收时触发。
接下来,我们把age放到类中,闭包中再去修改时会怎样:

class Animal {
    var age = 10
    deinit {
        print("Animal deinit")
    }
}

var t = Animal()
let clourse = {
    t.age += 10
}
clourse()
print(t.age)

//打印结果
//20
//Animal deinit

Animal类增加闭包属性

class Animal {
    var age = 10
    var completionBlock: (() ->())?
    deinit {
        print("Animal deinit")
    }
}
func test(){
    var t = Animal()
    t.completionBlock = {
        t.age += 10
    }
    print(t.age)
}
test()
//打印结果
//10

从运行结果发现,t.age还是1,并且没有执行deinit方法,这里存在循环引用

循环引用的处理

1 weak修饰闭包传入的参数。weak修饰后的变量是optional类型,所以t?.age += 1

func test(){
    var t = Animal()
    t.completionBlock = { [weak t] in
        t?.age += 10
    }
    print(t.age)
}

2 unowned修饰闭包参数

func test(){
    var t = Animal()
    t.completionBlock = { [unowned t] in
        t.age += 10
    }
    print(t.age)
}

[weak t][unowned t],称为捕获列表。定义在参数列表之前,如果使用捕获列表,那么即使省略参数名称、参数类型和返回类型,也必须使用in关键字

捕获列表的值
func test(){
    var age = 1
    var height = 1.0
    let clourse = {[age] in
        print(age)
        print(height)
    }
    age = 10
    height = 1.85
    clourse()
}
test()

//打印结果
//1
//1.85

如上,age值改变了,height未被捕获,值未变。

捕获列表特点: 捕获列表中的常量是值拷贝,而不是引用拷贝,因此,它是只读的,即不可修改

Runtime

Swift是一门静态语言,本身不具备动态性,不像OCRuntime运行时的机制(此处指OC提供运行时API供程序员操作)。但由于Swift兼容OC,所以可以转成OC类和函数,利用OC的运行时机制,来实现动态性

class Animal {
    var age: Int = 18
    func eat(){
        print("eat")
    }
}

let t = Animal()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(Animal.self, &methodCount)
    for i in 0..

尝试
1、以上代码,没有打印
2、给方法和属性添加@objc修饰,可以打印
3、类Animal继承NSObject,不用@objc修饰。只打印了初始化方法,因为在swift.h文件中暴露出来的只有init方法。
注意:如果要让OC调用,那么必须 继承NSObject + @objc修饰
4、去掉@objc修饰,改成dynamic修饰 + NSObject,同3。
5、@objc修饰 + dynamic修饰 + NSObject
关于方法调用,参考Swift方法调用

补充

AnyObject:代表任意类的instance,类的类型,类遵守的协议,但struct❌不行。
Any:代表任意类型,包括funcation类型或者Optional类型。
AnyClass:代表任意实例的类型。
T.self:如果T为实例对象,返回的就是它本身T是类,返回的是Metadata
type(of:):用于获取一个值的动态类型,编译期时,value的类型是Any类型运行期时,type(of:)获取的是真实类型

你可能感兴趣的:(Swift内存管理)