Swift -- 4.指针&内存管理

一.为什么说指针不安全

  • 比如我们在创建一个对象的时候,是需要在堆分配内存空间的。但是这个内存空间的声明周期是有限的,也就意味着如果我们使用指针指向这块内容空间,如果当前内存空间的生命周期到了(引用计数为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指向的内容

Symbol Table

3.通过拿到的0x58FCString Table拿到方法名称

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);

  ...
}
  • 此时的InlineRefCountsRefCounts<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))
  { }


位移结构体RefCountBitOffsetsshiftAfterField

// 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

Swift中RefCount的64位域信息
占位 名称 含义
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.代码验证位域信息

  1. 验证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() // 添加一个断点
swift_weakInit
  • 发现使用了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;
  }
};

关于源码对InlineRefCountSideTableRefCount的总结

  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位位域分布情况

SideTableRefCount

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

//加入断点

InlineRefCounts

2.获取HeapObjectSideTableEntry

  • 将62位(SideTableMark)和63位(UseSlowRC)置为0
  • 然后左移3位还原出HeapObjectSideTableEntry ,得出0x10075F790

3.查看HeapObjectSideTableEntry信息

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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容