一.为什么说指针不安全
比如我们在创建一个对象的时候,是需要在堆分配内存空间的。但是这个内存空间的声明周期是有限的,也就意味着如果我们使用指针指向这块内容空间,如果当前内存空间的生命周期到了(引用计数为0),那么我们当前的指针是不是就变成了未定义的行为了,也就是成了
野指针
。我们创建的内存空间是有边界的,比如我们创建一个大小为10的数组,这个时候我们通过指针访问到了
index = 11
的位置,这个时候是不是就越界了,访问了一个未知的内存空间。指针类型和内存的值不一致,也是不安全的。(指针的值是一个内存地址,也就是指针指向数据的首地址,根据这个地址只能得到指针指向的开始位置,并不知道是什么类型的,因此指针的类型决定了数据的类型。
比如此时的指针类型为Int8,而指向的数据为Int, 此时就会造成精度的缺失
)
二.typedPointer&rawPointer
Swift中的指针分为两类,typed pointer
指定数据类型指针,raw pointer
未指定数据类型的指针(原生指针)。
Swift | Object-C | 说明 |
---|---|---|
UnsafePointer<T> | const T * | 指针可变,所指向的内容都不可变 |
UnsafeMutablePointer<T> | T * | 指针及所指向的内容均可变 |
UnsafeRawPointer | const void * | 指针指向的内存区域未定 |
UnsafeMutableRawPointer | void * | 指针指向的内存区域未定 |
UnsafeBufferPointer<T> | ||
UnsafeMutableBufferPointer<T> | ||
UnsafeRawBufferPointer | ||
UnsafeMutableRawBufferPointer |
-
BufferPointer
表示申请连续的内存空间
1.存储4个整形的数据
使用UnsafeMutablePointer
//capacity:容量, 4代表4个8字节(Int)
let p = UnsafeMutablePointer<Int>.allocate(capacity: 4)
for i in 0 ..< 4 {
(p + i).initialize(to: i)
//其他方式
// p[i] = i
// p.advanced(by: i).initialize(to: i)
}
for i in 0 ..< 4 {
print((p + i).pointee) // 0,1,2,3
//其他方式
// print(p[i])
// print(p.advanced(by: i).pointee)
}
defer {
//将内存空间全部摸成0
p.deinitialize(count: 4)
//释放分配的内存空间
p.deallocate()
}
使用UnsafeMutableRawPointer
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0 ..< 4 {
p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
}
for i in 0 ..< 4 {
print(p.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)) // 0,1,2,3
}
defer {
//有allocate分配内存,就有deallocate将分配的内存释放
p.deallocate()
}
2.MemoryLayout
-
MemoryLayout<T>.size
表示T的实际大小 -
MemoryLayout<T>.stride
表示步长信息。也就是内存对齐后的大小 -
MemoryLayout<T>.alignment
表示内存对齐大小
struct LGTeacher {
var age = 18
var sex = true
}
print(MemoryLayout<LGTeacher>.size) //9
print(MemoryLayout<LGTeacher>.stride) //16
print(MemoryLayout<LGTeacher>.alignment) //8
3.泛型指针
- 泛型指针,其实就是
指定类型指针
修改age的值
var age = 10
//此时这里其实是将age = age + 10, 并不是改的指针指向的内存
age = withUnsafePointer(to: age) {
$0.pointee + 10
}
print(age) //20
//修改指针指向的内存
withUnsafeMutablePointer(to: &age) {
$0.pointee += 10
}
print(age) //30
4.withUnsafePointer和Unmanaged
获取实例的指针使用Unmanaged
//获取实例t的指针
Unmanaged.passUnretained(t).toOpaque()
获取内存地址withUnsafePointer
//探究withUnsafePointer中参数是否带&对返回值的影响
//1.如果为值类型
var age = 10
//此时对值取地址,表示age的内存地址
let agePtr = withUnsafePointer(to: &age){$0} // 0x0000000100008270。取内存0x100008270: 0x000000000000000a 0x0000000100008270
//已经将值从堆区拷贝到了栈区,然后返回的值在当前栈区的内存地址。第二个8字节为值原有的内存地址也就是agePtr(0x0000000100008270)
let ageStackPtr = withUnsafePointer(to: age){$0} // 0x00007ff7bfeff228。取内存0x7ff7bfeff228: 0x000000000000000a 0x0000000100008270
//2.如果值为实例类型,理解了值类型,指针类型也很好理解
var t = LGTeacher()
//此时的t为指针,实则是取指针的内存地址
let tPtr = withUnsafePointer(to: &t){$0} // 0x0000000100008258。0x100008258: 0x000000010b56b300 0x00007ff7bfeff280
//此时的t为指针,按照值类型的逻辑,将t指针拷贝到栈区,然后第二个8字节存放的是t指针原有的内存地址。返回的是t指针在栈区的内存地址。因此第二个8字节其实就是tPtr
let tStackPtr = withUnsafePointer(to: t){$0} // 0x00007ff7bfeff1f0。0x7ff7bfeff1f0: 0x000000010b56b300 0x0000000100008258
//此时的0x000000010b56b300就是指针t
三.使用指针读取Mach-o中类名、属性名称及vtable
class LGTeacher {
var age = 18
var name = "Kody"
func teach() {
print("teach")
}
func teach1() {
print("teach1")
}
func teach2() {
print("teach2")
}
}
//表示__swift_types里的字节大小
var size: UInt = 0
//1.获取__swift_types的内存地址
var typesPtr = getsectdata("__TEXT", "__swift5_types", &size)
//print(typesPtr)
//2.获取程序运行的基地址
//获取macho中header的地址,也就是程序的基地址
var machoHeaderPtr: UnsafePointer<mach_header>!
//在macOS Monterey版本下取3,在之前版本上取0就可以了
let count = _dyld_image_count()
for i in 0..<count {
if _dyld_get_image_header(i).pointee.filetype == MH_EXECUTE {
machoHeaderPtr = _dyld_get_image_header(i)
}
}
//print(machoHeaderPtr)
//3.通过__LINKEDIT拿到vmAddress(VM Address - File Offset) 或者通过__PAGEZERO拿到VM Size也就是VM Address
var vmAddress: UInt64 = 0
//方式1:通过__LINKEDIT
//var linkEditPtr = getsegbyname("__LINKEDIT")
//if let linkEdit = linkEditPtr?.pointee {
// vmAddress = linkEdit.vmaddr - linkEdit.fileoff
//}
//方式2:通过__PAGEZERO
var pageZeroPtr = getsegbyname("__PAGEZERO")
vmAddress = pageZeroPtr?.pointee.vmsize ?? 0
//4.获取__swift5_types的offset
var typesOffset: UInt64 = 0
//bitPattern按相同的二进制位,将内存地址转化为Int类型再转化为UInt64
typesOffset = UInt64(bitPattern: Int64(Int(bitPattern: typesPtr))) - vmAddress
//5.通过基地址获取__swift5_types中Data LO中4字节偏移信息
//将首地址转为UInt64
let machoHeaderPtr_Int = UInt64(bitPattern: Int64(Int(bitPattern: machoHeaderPtr)))
//拿到dataLo的地址
var dataLoAddress = machoHeaderPtr_Int + typesOffset
//将dataLo地址转为指针
let dataLoPtr = withUnsafePointer(to: &dataLoAddress){$0}
//获取dataLo的值, 4字节
let dataLoContent = UnsafePointer<UInt32>.init(bitPattern: UInt(dataLoAddress))?.pointee
//UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee
//6.获取TargetClassDescriptor的offSet
let tcdOffset = UInt64(dataLoContent!) + typesOffset - vmAddress
//7.获取TargetClassDescriptor在内存当中的地址
let targetClassDescriptor_Int = machoHeaderPtr_Int + tcdOffset
struct TargetClassDescriptor{
/*
关于原码、反码、补码
Int在计算机里存放的是补码也就是反码+1
比如一个Int8, 1和-1
1的原码:00000001(存的是补码)
1的反码:11111110
1的补码:11111111
1在内存:00000001
-1的原码:10000001
-1的反码:01111110
-1的补码:01111111
-1在内存:01111111(存的是补码)
*/
var flags: UInt32
var parent: UInt32
var name: UInt32
var accessFunctionPointer: UInt32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
//vTable
}
//8.将TargetClassDescriptor地址还原成TargetClassDescriptor指针
let targetclassDescriptorPtr = UnsafePointer<TargetClassDescriptor>.init(bitPattern: UInt(targetClassDescriptor_Int))
//9.解析类名称
if let name = targetclassDescriptorPtr?.pointee.name {
//获取nameOffset
let nameOffset = Int64(name) + Int64(tcdOffset) + 8 - Int64(vmAddress)
//name在程序中的真实地址
let nameAddress = UInt64(nameOffset) + machoHeaderPtr_Int
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(nameAddress)) {
print(String(cString: cChar))
}
}
//10.拿到FieldDescriptor的Offset
let fieldDescriptorOffset = tcdOffset + 16 + UInt64(targetclassDescriptorPtr!.pointee.fieldDescriptor)
//11.获取FieldDescriptor在内存中的真实地址
let fieldDescriptor_Int = fieldDescriptorOffset + machoHeaderPtr_Int
struct FieldDescriptor {
var mangledTypeName: Int32
var superclass: Int32
var Kind: UInt16
var fieldRecordSize: UInt16
var numFields: UInt32
// var fieldRecords: [FieldRecord]
}
struct FieldRecord{
var Flags: UInt32
var mangledTypeName: Int32
var fieldName: UInt32
}
//12.将FiledDescriptor真实内存地址绑定到结构体指针上
let fileDescriptorPtr = UnsafePointer<FieldDescriptor>.init(bitPattern: UInt(fieldDescriptor_Int))
//13.获取FieldRecords在内存中的真实地址并绑定到结构体上
let fileRecord_Int = fieldDescriptor_Int + 16
//14.便利打印属性名称
for i in 0..<fileDescriptorPtr!.pointee.numFields {
let fileRecordPtr = UnsafePointer<FieldRecord>.init(bitPattern: UInt(fileRecord_Int + UInt64(i) * 12))
let fileName_Int = UInt64(fileRecord_Int + UInt64(i) * 12) + 8 + UInt64(fileRecordPtr!.pointee.fieldName) - vmAddress
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fileName_Int)) {
print(String(cString: cChar))
}
}
//疑问1:通过FileOffsetVectorOffset获取属性的值,后续补充
//通过源码得出的vTable结构体
struct vTable {
var flags: TargetMethodDescriptor
var methodImpOffset: UInt32
}
struct TargetMethodDescriptor {
var flagType: UInt32
//从源码恢复的
enum KindType: UInt32 {
case Method,
Init,
Getter,
Setter,
ModifyCoroutine,
ReadCoroutine
}
enum FlagTypeMask: UInt32 {
case KindMask = 0x0F, // 16 kinds should be enough for anybody
IsInstanceMask = 0x10,
IsDynamicMask = 0x20,
IsAsyncMask = 0x40,
// ExtraDiscriminatorShift = 16, //这个16与0x10冲突了
ExtraDiscriminatorMask = 0xFFFF0000
}
func kindType() -> KindType {
return KindType(rawValue: flagType & FlagTypeMask.KindMask.rawValue)!
}
}
//15.获取方法(vtable)
//vtable
let vtable_Int = targetClassDescriptor_Int + 13 * 4
let vtableCount = targetclassDescriptorPtr!.pointee.size
//vtable偏移量
var vtableOffset = tcdOffset + 13 * 4
for i in 0 ..< vtableCount {
let vtablePtr = UnsafePointer<vTable>.init(bitPattern: UInt(vtable_Int + UInt64(i) * 8))
//排除添加属性生成的Getter、Setter、Modify方法,init方法
//这里只拿到teach相关方法
if vtablePtr!.pointee.flags.kindType() == .Method {
let methodImp_Int = vtable_Int + UInt64(i) * 8 + UInt64(vtablePtr!.pointee.methodImpOffset) + 4 - vmAddress
let methodImp = UnsafePointer<UInt64>.init(bitPattern: UInt(methodImp_Int))
//将Imp指针转化为方法
typealias Function = @convention(c) () -> Void
//unsafeBitCast,将一个指针指向的内存强制按位转化为目标的类型。也就是将Imp强制转化为Swift中的Function,从而完成调用。否则在Swift里是不能操作Imp的
let function = unsafeBitCast(methodImp, to: Function.self)
function()
}
}
疑问这里的vtable怎么没有方法的名称
/*
方法的名称称之为符号。
方法的名称是放在Mach-o中的String Table中的。通过Symbol Table去查找String Table
关于Symbol Table:1.String Table Index:存放了符号在字符串表的偏移量。2.Value:存放了当前函数的地址(也就是函数的Imp地址)
根据偏移量去String Table就可以找到函数的方法名称
在上架的过程中是会将Symbol Table给抹除掉。
原因也是因为程序的安全性,在我们程序运行的过程中会把符号表给剔除,mach-o会保留动态符号表(Dynamic Symbol Table)。将剔除的符号表放在了.dSYM文件下,也就是使用dSYM还原符号表的由来。
因为我们程序编译完成后,函数的地址就确定了,保留符号是没有必要的,如果我们保留了符号表,别人可以通过符号表通过符号很轻易的拿到函数地址信息,从而对这个函数进行hock,显然这是不安全的。
*/
验证猜想及结论
1.从上面的代码拿到一个方法teach
的Imp0x0000000100003de0
2.Symbol Table
找到该Imp
指向的内容
3.通过拿到的0x58FC
去String Table
拿到方法名称
此时的函数名称应该存放在0x180A8 + 0x58FC = 0x1D9A4
4.查看0x1D9A4
此时可以发现这个字符串信息其实就是在第二步图中的_$s9swiftTest9LGTeacherC5teachyyF
,此时信息是混淆后的
5.通过终端还原混淆代码
命令xcrun swift-demangle xxx
- 至此,成功还原出
teach符号化信息
四.内存绑定
Swift提供了三种不同的Api来绑定/重新绑定指针
assumingMemoryBound(to:)
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0]) // 10
print(p[1]) // 20
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) {
testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
}
/*
assumingMemoryBound使用场景
比如一个方法需要一个类型指针,但是现有的类型指针或原生指针与之不匹配,并且现有类型指针或原生指针指向的数据类型与需要的类型指针一致,
因此可以使用该方法assumingMemoryBound(UnsafeRaPointer)告诉编译器当前指针指向的数据的类型是Int类型,不需要再检查我了,也就是绕过编译器,并没有发生实际的类型转换
*/
bindMemory
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0]) // 10
print(p[1]) // 20
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) {
// testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
testPointer(UnsafeRawPointer($0).bindMemory(to: Int.self, capacity: 1)) // UnsafePointer<T>
}
/*
bindMemory
发生了实际类型的转化,把当前的原生指针转换成了UnsafePointer<T>,改定当前内存绑定的类型
*/
withMemoryRebound
func testPointer(_ p: UnsafePointer<Int>) {
print(p[0]) // 10
print(p[1]) // 20
}
let tuple = (10, 20)
withUnsafePointer(to: tuple) {
// testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
// testPointer(UnsafeRawPointer($0).bindMemory(to: Int.self, capacity: 1)) // UnsafePointer<T>
$0.withMemoryRebound(to: Int.self, capacity: 1) {
testPointer($0)
}
}
/*
withMemoryRebound
用于临时更改当前指针绑定的内存,出了闭包作用域就失效了
*/
查看源码通过内存绑定
还原实例
//查看源码总结出的MetaData及HeapObject结构
struct MetaData {
var kind: Int
var superClass: Any.Type
var cacheData: (Int,Int)
var data: Int
var flags: UInt32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var description: UInt32
var iVarDestroyer: UInt32
}
struct HeapObject {
var metadata: UnsafePointer<MetaData>
var refCount: UInt
}
class LGTeacher {
var age = 18
var name = "Kody"
}
//此时的heapObject就是还原后的数据
let heapObject = Unmanaged.passUnretained(t).toOpaque().bindMemory(to: HeapObject.self, capacity: 1)
五.内存管理中的引用计数(RefCount)
Swift中使用自动引用计数(ARC)
class LGTeacher{
var age: Int = 18
var name: String = "Kody"
}
var t = LGTeacher()
//获取内存指针
print(Unmanaged.passUnretained(t).toOpaque())
断点下在print
0x000000010b50d2f0
(lldb) x/8g 0x000000010b50d2f0
0x10b50d2f0: 0x00000001000081a8 0x0000000000000003
0x10b50d300: 0x0000000000000012 0x0000000079646f4b
0x10b50d310: 0xe400000000000000 0x0000000000000000
0x10b50d320: 0x0000000000000006 0x00007ff850b6d198
(lldb)
-
0x0000000000000003
这个就是refCount
1.源码分析refCount
1.进入HeapObject.h
找到RefCount
// 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;
...
}
2.进入InlineRefCounts
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
// Out-of-line slow paths.
SWIFT_NOINLINE
void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);
SWIFT_NOINLINE
void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);
SWIFT_NOINLINE
bool tryIncrementSlow(RefCountBits oldbits);
SWIFT_NOINLINE
bool tryIncrementNonAtomicSlow(RefCountBits oldbits);
SWIFT_NOINLINE
void incrementUnownedSlow(uint32_t inc);
...
}
此时的
InlineRefCounts
是RefCounts<InlineRefCountBits>
的模板类。有一个泛型参数InlineRefCountBits
在RefCounts类中我们可以发现其实都是在操作传进来的泛型参数
InlineRefCountBits
因此
RefCounts
其实是对引用计数的一个包装,引用计数的具体类型取决于传入进来的具体参数。
3.探究泛型参数InlineRefCountBits
//InlineRefCountBits是RefCountBitsT的模板函数
// RefCountIsInline泛型参数true或false
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
// RefCountIsInline: refcount stored in an object
// RefCountNotInline: refcount stored in an object's side table entry
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
//找到RedCountBitsT
class RefCountBitsT {
friend class RefCountBitsT<RefCountIsInline>;
friend class RefCountBitsT<RefCountNotInline>;
static const RefCountInlinedness Inlinedness = refcountIsInline;
//BitsType是RefCountBitsInt的模板函数
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
SignedBitsType;
typedef RefCountBitOffsets<sizeof(BitsType)>
Offsets;
//只有一个属性bits
BitsType bits;
...
}
//找到BitsType的模板函数RefCountBitsInt
//发现本质就是一个64位的位域信息
// 64-bit inline
// 64-bit out of line
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
实际上Swift中的
RefCounts
其实也是存放的64位 的位域信息
,存放了运行声明周期相关的引用计数为什么一个简单的UInt64需要绕这么大一圈来存储?
因为抽象出来的模板函数后续还要兼容弱引用,提高代码复用率
4.创建一个实例对象,当前的引用计数是多少?
//之前我们在探究Metadata时,在_swift_allocObject中有实例的初始化方法
new (object) HeapObject(metadata);
//HeapObject的初始化代码
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
//传入的RedCount是Initialized
//找到Initialzed的定义,在Class RefCount中
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
public:
//Initialized其实是一个枚举Initialized_t
enum Initialized_t { Initialized };
enum Immortal_t { Immortal };
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
//其实就是传入0,1
//RefCountBits也就是RefCountBitsT这个类
: refCounts(RefCountBits(0, 1)) {}
...
}
/*
RefCountBits(0, 1)
strongExtraCount - 0
unownedCount - 1
根据RefCountBitOffsets,得出
StrongExtraRefCountShift = 33
PureSwiftDeallocShift = 0
UnownedRefCountShift = 1
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
# define shiftAfterField(name) (name##Shift + name##BitCount)
把强引用计数和无主引用计数通过位移的方法存放在64位位域信息当中
强引用左偏移33位,0
是否是纯Swift析构函数的标志位,左偏移0位,也就是低1位1。因为这里是由swift_allocObject执行过来的,肯定是Swift的析构
无主引用左偏移1位,低2位上是1。也就是0x2
*/
SWIFT_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
位移结构体RefCountBitOffsets
及shiftAfterField
// Layout of refcount bits.
// field value = (bits & mask) >> shift
// FIXME: redo this abstraction more cleanly
# define maskForField(name) (((uint64_t(1)<<name##BitCount)-1) << name##Shift)
# define shiftAfterField(name) (name##Shift + name##BitCount)
template <size_t sizeofPointer>
struct RefCountBitOffsets;
// 64-bit inline
// 64-bit out of line
// 32-bit out of line
template <>
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);
};
2.总结出当没有SideTable时的位域信息
源码版本Swift5.5.2
占位 | 名称 | 含义 |
---|---|---|
0 | PureSwiftDealloc | 是否使用Swift执行Dealloc析构函数,false表示使用objc-Runtime执行析构函数 |
1~31 | UnownedRefCount | 无主引用计数 |
32 | isDeiniting | 是否正在析构函数,也可以称为是否正在释放 |
33~62 | StrongExtraRefCount | 强引用计数 |
63 | UseSlowRC | 是否使用缓慢RC |
0~32 | isImmortal | 是否是不朽对象。由之前的0变为0~32 |
- 对于之前的
isImmortal
`overlaps PureSwiftDealloc and UnownedRefCount`
根据,这段是覆盖在`PureSwiftDealloc`和`UnOwnedRefCount`上的
---Immortal case---
All bits set, the object does not deallocate or have a refcount
如果将所有bits设置后,这个对象不会释放或者有一个引用计数
3.代码验证位域信息
- 验证
isDeiniting
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
}
var t: LGTeacher? = LGTeacher()
//获取内存指针
//为什么没有用po t,而是使用代码获取内存地址。是因为po t会影响当前的引用计数
print(Unmanaged.passUnretained(t!).toOpaque())
//breakpoint 0x10b10f6b0: 0x00000001000081a8 0x0000000000000003
t = nil
//breakpoint 0x10b10f6b0: 0x00000001000081a8 0x0000000100000003
- 当执行完
t = nil
后,refCount
的32位变为了1,表示实例对象正在执行析构函数
2.源码查看强引用的添加_swift_retain_
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;
}
//进入object->refCounts.increment(1)
// Increment the reference count.
SWIFT_ALWAYS_INLINE
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));
}
//增加强引用计数newbits.incrementStrongExtraRefCount(inc)
// 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) {
// This deliberately overflows into the UseSlowRC field.
//这里的inc为1,相当于bits += 1<< 33
//StrongExtraRefCountShift = 33
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
- 调用
swift_retain
相当于执行bits += inc << 33
,也印证了初始化的逻辑
3.代码验证强引用计数
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
}
var t = LGTeacher()
//获取内存指针
//为什么没有用po t,而是使用代码获取内存地址。是因为po t会影响当前的引用计数
print(Unmanaged.passUnretained(t).toOpaque())
var t1 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000000000003(源码分析0位为1,1位为1存放无主引用,刚好为0x3)
var t2 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000200000003(源码分析,1偏移33位存放强引用,加上之前的无主引用0x3,为0x200000003)
var t3 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000400000003(源码分析,2偏移33位存放强引用,加上之前的无主引用0x3,为0x400000003)
var t4 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000600000003(源码分析,3偏移33位存放强引用,加上之前的无主引用0x3,为0x600000003)
六.内存管理中的弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止ARC
释放被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或变量时,在前面加上weak
关键字表明这是一个弱引用。
由于弱引用不会强保持对实例的引用,所以说实例被释放了弱引用仍旧引用着这个实例也是有可能的。因此,ARC
会在被引用的实例被释放时自动地设置弱引用为nil
.由于弱引用需要允许它们的值为nil
,它们一定得是可选类型。
简而言之就是 weak var t = LGTeacher()
,这里t因为是可变的(释放时会变为nil),所以必须是由var
修饰
1.通过汇编探究使用weak后,程序是怎么执行的
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
}
weak var t = LGTeacher() // 添加一个断点
- 发现使用了
weak
后会调用swift_weakInit
函数
2.通过源码分析swift_weakInit
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
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
//创建了一个SideTable
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
- 声明一个
var
变量相当于重新定义了WeakReference
对象 - 实际上就是创建了一个散列表
SideTable
3.SideTable相关分析
进入到allocateSideTable
// Return an object's side table, allocating it if necessary.
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
//取出原先的RefCount
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
//判断原先的refCount是否有SideTable,如果有直接返回SideTable
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.
// FIXME: custom side table allocator
//通过HeapObjectSideTableEntry创建一个SideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//通过side初始化RefCount
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存入之前的RefCount的信息
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
进入HeapObjectSideTableEntry
//这里的InlineRefCounts是我们在分析,执行swift_allocObject时,在HeapObject中的refCount类型就是InlineRefCounts。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
//这里的SideTableRefCounts,与InlineRefCounts共用一个模板函数RefCounts。
//并且这里的泛型参数SideTableRefCountBits也是共用一个父类RefCountBitsT
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
//存储了当前的实例对象
std::atomic<HeapObject*> object;
//存储了当前的refCount
SideTableRefCounts refCounts;
...
}
关于SideTableRefCountBits
//与InlineRefCountBits共同继承自RefCountBits
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
//多了一个32字节存储weakBits
uint32_t weakBits;
public:
SWIFT_ALWAYS_INLINE
SideTableRefCountBits() = default;
SWIFT_ALWAYS_INLINE
constexpr SideTableRefCountBits(uint32_t strongExtraCount,
uint32_t unownedCount)
: RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
// weak refcount starts at 1 on behalf of the unowned count
,
weakBits(1) {}
SWIFT_ALWAYS_INLINE
SideTableRefCountBits(HeapObjectSideTableEntry *side) = delete;
SWIFT_ALWAYS_INLINE
SideTableRefCountBits(InlineRefCountBits newbits)
: RefCountBitsT<RefCountNotInline>(&newbits), weakBits(1) {}
SWIFT_ALWAYS_INLINE
void incrementWeakRefCount() { weakBits++; }
SWIFT_ALWAYS_INLINE
bool decrementWeakRefCount() {
assert(weakBits > 0);
weakBits--;
return weakBits == 0;
}
SWIFT_ALWAYS_INLINE
uint32_t getWeakRefCount() {
return weakBits;
}
// Side table ref count never has a side table of its own.
SWIFT_ALWAYS_INLINE
bool hasSideTable() {
return false;
}
};
关于源码对InlineRefCount
和SideTableRefCount
的总结
Storage layout:
HeapObject {
isa
InlineRefCounts {
atomic<InlineRefCountBits> {
strong RC + unowned RC + flags
OR
HeapObjectSideTableEntry*
}
}
}
HeapObjectSideTableEntry {
SideTableRefCounts {
object pointer
atomic<SideTableRefCountBits> {
strong RC + unowned RC + weak RC + flags
}
}
}
Swift中存在两种引用计数
不存在弱引用,InlineRefCounts
存在弱引用,HeapObjectSideTableEntry
在源码中找到关于SideTableRefCountBits
的初始化方法
SWIFT_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
//通过RefCountOffsets中可知SideTableUnusedLowBits = 3,也就是将HeapObjectSideTableEntry右移3位存入
//(BitsType(1) << Offsets::UseSlowRCShift),1右移63位,还是标识是否慢速RC
//(BitsType(1) << Offsets::SideTableMarkShift)),1右移62位,散列表标记
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
当存在SideTable
时,也就是有弱引用的时候,64位位域分布情况
4.代码验证
1.获取refCount
相关数据
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
}
var t = LGTeacher()
//获取内存指针
//为什么没有用po t,而是使用代码获取内存地址。是因为po t会影响当前的引用计数
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())
//加入断点
weak var t1 = t
//加入断点
2.获取HeapObjectSideTableEntry
- 将62位(
SideTableMark
)和63位(UseSlowRC
)置为0 - 然后左移3位还原出
HeapObjectSideTableEntry
,得出0x10075F790
3.查看HeapObjectSideTableEntry
信息
4.为什么添加一个弱引用是2?
这时在代码里再添加一个weak
引用当前t,发现此时的弱引用计数为0x3
。猜想大概率是因为在SideTableRefBits
创建时给了一个初始值1。
通过源码分析
//1.找到添加弱引用时执行的代码,使用incrementWeak增加弱引用
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
//2.HeapObejectSideTableEntry中的incrementWeak()
// WEAK
SWIFT_NODISCARD
HeapObjectSideTableEntry* incrementWeak() {
// incrementWeak need not be atomic w.r.t. concurrent deinit initiation.
// The client can't actually get a reference to the object without
// going through tryRetain(). tryRetain is the one that needs to be
// atomic w.r.t. concurrent deinit initiation.
// The check here is merely an optimization.
if (refCounts.isDeiniting())
return nullptr;
refCounts.incrementWeak();
return this;
}
//3.找到RefCount中的incrementWeak()
// Increment the weak reference count.
void incrementWeak() {
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));
}
//4.找到incrementWeakRefCount()
SWIFT_ALWAYS_INLINE
void incrementWeakRefCount() { weakBits++; }
//5.weakBits就是存放弱引用的uint32_t类型。 因此就解释了多一个引用计数就变为了3
//6.根据 HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());,
//找到HeapObjectSideTableEntry的初始化方法
//函数内创建了refCounts,这里的refCounts是SideTableRefCount,也就是RefCount<SideTableRefCountBits>.
public:
HeapObjectSideTableEntry(HeapObject *newObject)
: object(newObject), refCounts()
{ }
//7.找到SideTableRefCountBits的初始化方法
//在SideTableRefCountBits创建的时候将weakBits设置为1
SWIFT_ALWAYS_INLINE
constexpr SideTableRefCountBits(uint32_t strongExtraCount,
uint32_t unownedCount)
: RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
// weak refcount starts at 1 on behalf of the unowned count
,
weakBits(1) {}
5.图解总结InlineRefCounts和HeapObjectSideTableEntry
六.内存管理中的无主引用
关键字unowned
。和弱引用类似,无主引用不会牢牢保持住引用的实例。但是不像弱引用,总之引用假定是永远有值的。因此无主引用并不是类型安全的
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
}
var t: LGTeacher? = LGTeacher()
unowned let t2 = t
t = nil
print(t2)
//Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated2022-01-13 17:46:36.434502+0800 swiftTest[2358:153642] Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated
//Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated
根据苹果官方文档的建议。
当我们知道两个对象的生命周期并不相关,那么我们必须使用weak
。例如delegate
反正,非强引用对象拥有和强引用对象拥有同样的声明周期的话,我们应该使用unowned
unowned
的性能更好,不需要创建SideTable
七.内存管理中的循环引用&捕获列表
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
deinit {
print("LGTeacher deinit")
}
}
let t = LGTeacher()
let closure = {
print(t.age)
}
closure()
//18
//并没有执行deinit,此时不执行的原因并不是因为循环引用,而是因为t是一个全局变量
循环引用例子
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
var closure: (() -> Void)?
deinit {
print("LGTeacher deinit")
}
}
func test() {
let t = LGTeacher()
//这里就形成了循环引用
t.closure = {
print(t.age)
}
t.closure?()
}
test()
//18
解决方案
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
var closure: (() -> Void)?
deinit {
print("LGTeacher deinit")
}
}
func test() {
let t = LGTeacher()
//这里就形成了循环引用
weak var t1 = t //使用弱引用解决循环引用
unowned var t2 = t //使用无主引用解决循环引用
t.closure = {
print(t1?.age)
}
t.closure?()
}
test()
//Optional(18)
//LGTeacher deinit
在Swift中还可以使用捕获列表
来解决循环引用
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
var closure: (() -> Void)?
deinit {
print("LGTeacher deinit")
}
}
func test() {
let t = LGTeacher()
//这里就形成了循环引用
t.closure = { [weak t] in
//也可以使用[unowned t]
print(t?.age)
}
t.closure?()
}
test()
//Optional(18)
//LGTeacher deinit
关于捕获列表
var age = 0
var height = 1.85
/*
对编译器来说,当执行闭包时(捕获列表发生在闭包调用的时候),
如果有捕获列表,就会去函数上下文找到与之对应的值,复制到捕获列表当中,
因此无论后面怎么修改,在闭包内部访问的值都不会改变.
*/
let closure = { [age] in
print(age) //0
print(height) //1.85
}
age = 10
height = 1.85
closure()
在Switf闭包内使用strong dance
class LGTeacher {
var age: Int = 18
var name: String = "Kody"
var closure: (() -> Void)?
deinit {
print("LGTeacher deinit")
}
}
func test() {
let t = LGTeacher()
t.closure = { [weak t] in
//使用strong dance,在Swift里就是一个解包操作
if let strongT = t {
print(strongT.age)
}
//延迟实例变量的生命周期
withExtendedLifetime(t) {
}
}
t.closure?()
}
test()