Swift指针、内存管理

784 阅读7分钟

Swift指针

  • Swift中的指针分为两类,typed pointer指定数据类型指针,raw pointer未指定数据类型指针(原生指针)
  • raw pointerSwift中的表示是UnsafeRawPointer
  • tyepd pointerSwift中的表示是UnsafePointer<T>
  • Swift指针对比oc指针 | Swift | oc | 说明 |
    | --- | --- | --- | | UnsafePointer | const T * | 指针及所指向的内容都不可变 |
    | UnsafeMutablePointer | T * | 指针及所指向的内容都可变 |
    | UnsafeRawPointer | const void * | 指针及所指向的内容都不可变且指针指向未知类型 |
    | UnsafeMutableRawPointer | void * | 指针及所指向的内容可变且指针指向未知类型 |

原生指针

  • 我们想在内存中存储连续4个整型数据,通过原生指针raw pointer如何实现
// 1,分配32字节内存大小
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

// 2、advanced代表当前p前进的步长,对于rawPointer来说我们需要移动的是当前存储值的内存大小即MemoryLayout.stride
// storeBytes:这里就是存储我们的数据,这里需要指定数据的类型
for i in 0..<4 {
    p.advanced(by: i * 8).storeBytes(of: (i + 1), as: Int.self)
}
// 3、load: 加载 fromByteOffset:是相对于我们当前p的首地址的偏移
for i in 0..<4 {
    let value = p.load(fromByteOffset: i * 8, as: Int.self)
    print("index:\(i)--value:\(value)")
}

p.deallocate()

指定数据类型指针

  • 获取一个变量的地址
// 获取age变量的地址
var age = 10

// 通过Swift提供的简写API,这里注意当前尾随闭包的写法
let p = withUnsafePointer(to: &age, {$0})
//    withUnsafePointer(to: age, {$0})
print(p.pointee)
// withUnsafePointer的返回值是unsafePointer,意味着我们不能直接修改值,此时b的值=22
var b = withUnsafePointer(to: &age, { ptr in
    ptr.pointee + 12
})
print(b)

// 如果我们想要修改Pointer.pointee的值,使用withUnsafeMutablePointer
withUnsafeMutablePointer(to: &age, { ptr in
    ptr.pointee += 12
    print(ptr.pointee)
})
  • 另一种创建Type Pointer的方式
struct XQTeacher {
    var age = 18
    var height = 1.88
}

// capacity:容量大小,当前的大小为2*MemoryLayout<XQTeacher>.stride
let p = UnsafeMutablePointer<XQTeacher>.allocate(capacity: 2)

// 初始化当前的UnsafeMutablePointer<XQTeacher>指针
p.initialize(to: XQTeacher())
p.advanced(by: 1).initialize(to: XQTeacher.init(age: 20, height: 1.90))

p.deinitialize(count: 2)

print(p[0])
print(p[1])

p.deallocate()

通过案例来理解指针

  • 1,通过指针绑定到swift_class结构体
struct HeapObject {
    var kind : UnsafeRawPointer
    var strongRef : UInt32
    var unownedRef : UInt32
}

struct xq_swift_class {
    var kind : UnsafeRawPointer
    var superClass : UnsafeRawPointer
    var cachedata1 : UnsafeRawPointer
    var cachedata2 : UnsafeRawPointer
    var data : UnsafeRawPointer
    var flags : UInt32
    var instanceAddressOffset : UInt32
    var instanceSize : UInt32
    var instanceAlignMask : UInt16
    var reserved : UInt16

    var classSize : UInt32
    var classAddressOffset : UInt32
    var description : UnsafeRawPointer
};

class XQTeacher {
    var age = 18
}

//实例变量的内存地址
var t = XQTeacher()
//Unmanagedpass.Unretained(t as AnyObject).toOpaque()
//OC 和 CF 交互的方式, __brige ,所有权的转换
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()

let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)

let metaPtr = heapObject.pointee.kind.bindMemory(to: xq_swift_class.self, capacity: 1)

print(metaPtr.pointee)
//  打印结果
//  xq_swift_class(kind: 0x0000000100008140, superClass: 0x00007fff889728f8, cachedata1: 0x00007fff201f0af0, cachedata2: 0x0000802000000000, data: 0x0000000100606fe2, flags: 2, instanceAddressOffset: 0, instanceSize: 24, instanceAlignMask: 7, reserved: 0, classSize: 136, classAddressOffset: 16, description: 0x0000000100003c3c)
  • 2,元组指针类型转换
var tul = (10,20)

withUnsafePointer(to: &tul, { (ptr : UnsafePointer<(Int,Int)>) in
    testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
})

func testPointer(_ p : UnsafePointer<Int>) {
    print(p)
    print("end")
}
  • 3,获取结构体的属性的指针
struct HeapObject {
    var strongRef = 10
    var unownedRef = 20
}

var h = HeapObject()

withUnsafePointer(to: &h, { (ptr : UnsafePointer<HeapObject>) in
	// 通过原生指针 + 偏移量
    let strongRefPtr = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of:\HeapObject.strongRef)!
    testPointer(strongRefPtr.assumingMemoryBound(to: Int.self))
})

func testPointer(_ p : UnsafePointer<Int>) {
    print(p)
    print("end")
}
  • withMemoryRebound:临时更改内存绑定类型,使用场景:函数调用的参数需要传递一个UnsafePointer<UInt64>类型参数,我们传入的age其实是一个UnsafePointer<Int>且不想修改它的类型就可以使用临时更改绑定类型,只在闭包的作用空间里面有效
var age = 10

withUnsafePointer(to: &age, { (ptr : UnsafePointer<Int>) in
    ptr.withMemoryRebound(to: UInt64.self, capacity: 1, { ( ptr : UnsafePointer<UInt64> ) in
        testPointer(ptr)
    })
})

func testPointer(_ p : UnsafePointer<UInt64>) {
    print(p.pointee)
    print("end")
}
  • bindMemory(to: capacity:):更改内存绑定类型,如果之前没有绑定,那么就是首次绑定;如果绑定过了,会被重新绑定为该类型。
  • assumingMemoryBound:假定内存绑定,这里是告诉编译器:我就是这类型,不用再检查我了。

Swift内存管理

  • Swift中使用自动引用计数(ARC)机制管理内存,我们创建一个类来观察它的引用引用计数
class XQTeacher {
    var age = 18
    var name = "xq"
}

var t = XQTeacher()
var t1 = t
var t2 = t
  • 通过lldb调试打印它的引用计数

源码分析引用计数

  • 我们通过直接阅读源码的方式来分析refCounted,我们知道HeapObject里面有两个属性HeapMetadata const *metadata;InlineRefCounts refCounts
  • refCounts就是引用计数相关的属性,InlineRefCounts是一个别名
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
  • RefCounts是一个模板类,它的结构由传入的参数决定
  • InlineRefCountBits又是一个别名
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • RefCountIsInline是一个enum
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
  • RefCountBitsT的结构,里面有一个参数BitsType bitsBitsTypeRefCountBitsInt<refcountIsInline, sizeof(void*)>::Type的别名
  • RefCountBitsInt的结构,所以bits其实就是一个uint64_t的类型的数据
struct RefCountBitsInt<RefCountNotInline, 4> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
  • 回到类初始化的时候,enum Initialized_t { Initialized };的初始化
  • Offsets的定义
struct RefCountBitOffsets<8> {
  /*
   The bottom 32 bits (on 64 bit architectures, fewer on 32 bit) of the refcount
   field are effectively a union of two different configurations:
   
   ---Normal case---
   Bit 0: Does this object need to call out to the ObjC runtime for deallocation
   Bits 1-31: Unowned refcount
   
   ---Immortal case---
   All bits set, the object does not deallocate or have a refcount
   */
  static const size_t PureSwiftDeallocShift = 0;
  static const size_t PureSwiftDeallocBitCount = 1;
  static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);

  static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
  static const size_t UnownedRefCountBitCount = 31;
  static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);

  static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
  static const size_t IsImmortalBitCount = 32;
  static const uint64_t IsImmortalMask = maskForField(IsImmortal);

  static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
  static const size_t IsDeinitingBitCount = 1;
  static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);

  static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;
  static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
  
  static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
  static const size_t UseSlowRCBitCount = 1;
  static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);

  static const size_t SideTableShift = 0;
  static const size_t SideTableBitCount = 62;
  static const uint64_t SideTableMask = maskForField(SideTable);
  static const size_t SideTableUnusedLowBits = 3;

  static const size_t SideTableMarkShift = SideTableBitCount;
  static const size_t SideTableMarkBitCount = 1;
  static const uint64_t SideTableMarkMask = maskForField(SideTableMark);
};
  • 最终我们可以得到下面结论

swift_retain

  • 在源码中搜索swift_retain,找到swift_retain的实现,可以看到它是执行了object->refCounts.increment(1);函数

  • increment的实现,调用了incrementStrongExtraRefCount函数

void increment(uint32_t inc = 1) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    
    // constant propagation will remove this in swift_retain, it should only
    // be present in swift_retain_n
    if (inc != 1 && oldbits.isImmortal(true)) {
      return;
    }
    
    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));
  }
  • 函数incrementStrongExtraRefCount的实现,将传入的参数强转成BitsType,通过上文的研究我们知道其实就是uint64_t类型,再进行位移操作StrongExtraRefCountShift
// 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).
  LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
  bool incrementStrongExtraRefCount(uint32_t inc) {
    // This deliberately overflows into the UseSlowRC field.
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
  }

弱引用

  • 弱引用声明一个变量weak var t = XQTeacher(),可以看到它是一个可选值,因为程序运行过程中是允许将当前变量设置为nil的。
  • 可以看到如果不使用weak修饰,是不允许设置为nil的
  • 使用了weak修饰之后,我们再来观察下refcount,可以看到refcount变了。
  • 我们来研究下weak做了什么,在weak处设置断点,运行进入断点
  • 在上文我们已经研究了swift_retainswift_releaseswift_retain是对应的,我们重点研究swift_weakInit
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);
}
👇
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
👇
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;
  }

  // Preflight passed. Allocate a side table.
  
  // FIXME: custom side table allocator
  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;
}
  • HeapObjectSideTableEntry类里面有两个属性objectrefCounts
  • SideTableRefCounts的定义
  • SideTableRefCountBits的结构
  • auto newbits = InlineRefCountBits(side);函数调用了下面的初始化函数。
LLVM_ATTRIBUTE_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)// 右移3位
           | (BitsType(1) << Offsets::UseSlowRCShift) //最高位处理
           | (BitsType(1) << Offsets::SideTableMarkShift))// 第62位处理
  {
    assert(refcountIsInline);
  }
  • 通过这个函数的实现,我们可以对weak第二个地址进行处理
  • 对地址0xc000000020b86cde处理,先将最高两位抹0,再将它左移3位,得到地址0x105C366F0lldb查看内存数据
  • 所以被weak修饰的类的最终结构会变成下面这样
HeapObject{
	InlineRefCountBits{ strong count + unowned count} 
	HeapObjectSideTableEntry{
	    HeapObjec *object
        xxx
        strong count + unonwned count (uint64_t) 
        weak count (uint32_t)
    }
}

weak修饰的对象再进行retain

  • _swift_retain_方法往下执行
  • incrementSlow函数里面也是对side的操作
template <typename RefCountBits>
void RefCounts<RefCountBits>::incrementSlow(RefCountBits oldbits,
                                            uint32_t n) {
  if (oldbits.isImmortal(false)) {
    return;
  }
  else if (oldbits.hasSideTable()) {
    // Out-of-line slow path.
    auto side = oldbits.getSideTable();
    side->incrementStrong(n);
  }
  else {
    // Retain count overflow.
    swift::swift_abortRetainOverflow();
  }
}

循环引用

  • 反初始化器deinit,类似于oc的dealloc
  • 解决循环引用问题使用weak
  • 使用unowned
  • weakunowned的区别,unowned修饰的变量在程序运行期间都是假定有值的,类似于oc中的unsafe_unretained,是不安全的可能会出现野指针的情况,在能确定变量的生命周期是可控,像上文中闭包中的使用

捕获列表

  • 定义在参数列表之前,捕获列表被写为用逗号连接起来的表达式列表,并用方括号括起来。如果使用捕获列表,则即使省略参数名称,参数类型和返回类型,也必须使用in关键字
  • 通过一个例子来理解捕获列表
  • 对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量或变量,来初始化捕获列表中定义的常量。