Swift进阶04:内存管理&Runtime

第四节课:内存管理&Runtime

内存管理 - 强引用

在swift中也是使用ARC来追踪和管理内存的,下面我们先简单看一段代码来进行分析

class HZMTeacher {
    var age: Int = 18
    var name: String = "HZM"
}
var t = HZMTeacher()
var t1 = t
var t2 = t
内存管理01.png
  • 第一位为metadata但是后面的0x0000000600000002跟我们之前的refCounts并不一样

我们分析下源码 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<InlineRefCountBits> InlineRefCounts;
👇
template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
}
  • 分析InlineRefCountBits,是RefCountBitsT类的别名
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • 分析RefCountBitsT,有bits属性
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
    ...
      typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
    ...
    BitsType bits;
    ...
}
👇
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
  //类型
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

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

然后来继续分析swift中对象创建的底层方法swift_allocObject

分析初始化源码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方法中
  enum Initialized_t { Initialized };
  
  //对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
  • 从这里看出真正干事的是RefCountBits

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

template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> 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))
{ }

分析RefCountsBit的结构,如下图所示

内存管理02.png

  • isImmortal(0)

  • UnownedRefCount(1-31): unowned的引用计数

  • isDeinitingMask(32):是否进行释放操作

  • StrongExtraRefCount(33-62): 强引用计数

  • UseSlowRC(63)

重点关注UnownedRefCountStrongExtraRefCount

内存管理3.png

trefCounts用二进制展示,我们发现:
1~31的UnownedRefCount 为1
33~62的StrongExtraRefCount 为2

当只有t实例变量时


内存管理04.png

当有t + t1时,查看是否有 strong_retain操作

//SIL中的main
alloc_global @main.t1 : main.HZMTeacher       // id: %8
%9 = global_addr @main.t1 : main.HZMTeacher : $*HZMTeacher // user: %11
%10 = begin_access [read] [dynamic] %3 : $*HZMTeacher // users: %12, %11
copy_addr %10 to [initialization] %9 : $*HZMTeacher // id: %11

//其中copy_addr等价于
- %new = load s*HZMTeacher
- strong_retain %new
- store %new to %9
内存管理05.png
//内部是一个宏定义
HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}
👇
//本质调用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}
👇
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;
    }
    //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));
  }
  • 回退到HeapObject,从InlineRefCounts进入,其中是c++中的模板定义,是为了更好的抽象,在其中查找bits(即decrementStrongExtraRefCount方法)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 对inc做强制类型转换为 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000
//这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}

例如以trefCounts为例(其中62-33位是strongCount,每次增加强引用计数增加都是在33-62位上增加的,固定的增量为1左移33位,即0x200000000

  • 只有t时的refCounts是 0x0000000200000002

  • t + t1时的refCounts是 0x0000000400000002 = 0x0000000200000002 + 0x200000000

  • t + t1 + t2 时的refCounts是 0x0000000600000002 = 0x0000000400000002 + 0x200000000

针对上面的例子,可以通过CFGetRetainCOunt获取引用计数,发现依次是 2、3、4,默认多了一个1

内存管理06.png

  • 如果将t、t1、t2放入函数中,还会再次retain一次
    内存管理07.png

为什么是0x200000000
因为1左移33位,其中4位为一组,计算成16进制,剩余的33-32位0x10,转换为10进制为2。其实际增加引用计数就是1

swift与OC强引用计数对比

OC中创建实例对象时为0

swift中创建实例对象时默认为1

内存管理 - 弱引用

我们先看下面的代码

class HZMTeacher{
    var age: Int = 20
    var name: String = "HZM"
    var stu : HZMStudent?
}

class HZMStudent {
    var age: Int = 18
    var name: String = "HZM2"
    var teacher: HZMTeacher?
}

func testCount() {
    var t = HZMTeacher()
    weak var t1 = t
    print("end")
}

查看t的引用计数变化

弱引用01.png

  • 本质上 t1 = t 并没有增加引用计数,但是t的地址存放的内容却发生了变化
  • 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil

t1处加断点,查看汇编

弱引用02.png

查看 源码中的 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<InlineRefCountBits>::formWeakReference()
{
  //创建 sideTable
  auto side = allocateSideTable(true);
  if (side)
  // 如果创建成功,则增加弱引用
    return side->incrementWeak();
  else
    return nullptr;
}

进入allocateSideTable

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
  //第二步、创建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;
}

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

弱引用03.png

所以上面的0xc0000000200abb32HeapObjectSideTableEntry实例对象的内存地址,即散列表的地址(除去63、62位)

第一反应为啥要存它?我们继续看
查看HeapObjectSideTableEntry定义,其中有object对象、refCounts

弱引用04.png

继续往里进
进入SideTableRefCounts,同InlineRefCounts类似,实际做事的是SideTableRefCountBits,继承自RefCountBitsT(存的是uint64_t类型的64位的信息),还有一个uint32_tweakBits,即32位的位域信息

弱引用05.png

0xc0000000200a45c4为例,将62、63位清零,变成0x200A45C4,然后左移3位(即InlineRefCountBits初始化方法),变成0x100522E20HeapObjectSideTableEntry对象地址,即散列表地址,然后通过x/8g读取(直接用编程计算器敲的)

弱引用06.png

发现最终结果的首地址就是我们当前的实例对象
并且第二行是我们的强引用计数弱引用计数

总结一下

对于HeapObject来说,其refCounts有两种:

  • 无弱引用:strongCount + unownedCount
  • 有弱引用:object + xxx + (strongCount + unownedCount) + weakCount
HeapObject {
    InlineRefCountBit {strong count + unowned count }
    
    HeapObjectSideTableEntry{
        HeapObject *object
        xxx
        strong Count + unowned Count(uint64_t)//64位
        weak count(uint32_t)//32位
    }
}

循环引用

func test(){
    var age = 10
    let closure = {
        age += 1
    }
    closure()
    print(age)
}

test()

<!--打印结果--!>
11

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

来看下面代码

class HZMTeacher {
    var age = 18
    //反初始化器(当前实例对象即将被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}
func test(){
    var t = HZMTeacher()
}
test()

<!--打印结果--!>
HZMTeacher deinit

修改例子,通过闭包修改其属性值

class HZMTeacher {
    var age = 18
    //反初始化器(当前实例对象即将被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}
var t = HZMTeacher()
let clourse = {
    t.age += 1
}
clourse()

<!--打印结果--!>
19

再次修改

class HZMTeacher {
    var age = 18
    //反初始化器(当前实例对象即将被回收)
    deinit {
        print("HZMTeacher deinit")
    }
}


func test(){
    var t = HZMTeacher()

    let clourse = {
        t.age += 1
    }
    clourse()
}
<!--运行结果-->
HZMTeacher deinit

根据运行结果我们发现,闭包对 t 并没有强引用

继续修改

class HZMTeacher {
    var age = 18
    
    var completionBlock: (() ->())?
    
    deinit {
        print("HZMTeacher deinit")
    }
}

func test(){
    var t = HZMTeacher()
    t.completionBlock = {
        t.age += 1
    }
}
test()

从运行结果发现,没有执行deinit方法,即没有打印HZMTeacher deinit,所以这里有循环引用

循环引用的解决方法

Swift中有两种解决方式
1.weak
使用weak修饰闭包传入的参数,其中参数的类型是optional

func test(){
    var t = HZMTeacher()
    t.completionBlock = { [weak t] in
        t?.age += 1
    } 
}

2.unowned(无主引用)
使用unowned修饰闭包参数,与weak的区别在于unowned不允许被设置为nil,即总是假定有值的,容易出现野指针情况。

func test(){
    var t = HZMTeacher()
    t.completionBlock = { [unowned t] in
        t.age += 1
    } 
}

捕获列表

[weak t] / [unowned t] 在swift中被称为捕获列表
1.定义在参数列表之前
2.[书写方式]捕获列表被写成用逗号括起来的表达式列表,并用方括号括起来
3.如果使用捕获列表,则即使省略参数名称、参数类型和返回类型,也必须使用in关键字
4.[weak t] 就是取t的弱引用对象 类似weakself

我们看下面代码

func test(){
    var age = 0
    var height = 0.0
    //将变量age用来初始化捕获列表中的常量age,即将0给了闭包中的age(值拷贝)
    let clourse = {[age] in
        print(age)
        print(height)
    }
    age = 10
    height = 1.85
    clourse()
}

<!--打印结果--!>
0
1.85

所以从结果中可以得出:对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量/变量,来初始化捕获列表中定义的常量。有以下几点说明:

  • 捕获列表中的常量是值拷贝,而不是引用

  • 捕获列表中的常量的相当于复制了变量age的值

  • 捕获列表中的常量是只读的,即不可修改

swift中Runtime探索

class HZMTeacher {
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

let t = HZMTeacher()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(HZMTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodList?[I]{
            let methodName = method_getName(method)
            print("方法列表:\(methodName)")
        }else{
            print("not found method")
        }
    }
    
    var count: UInt32 = 0
    let proList = class_copyPropertyList(HZMTeacher.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[I]{
            let propertyName = property_getName(property)
            print("属性成员属性:\(property)")
        }else{
            print("没有找到你要的属性")
        }
    }
    print("test run")
}
test()

<!--打印结果--!>
test run

结果发现并没有打印方法和属性

  • 给属性 和 方法 都加上 @objc,可以打印吗?
    Runtime01.png

从运行结果看,是可以打印,但是由于类并没有暴露给OC,所以OC是无法使用的,这样做是没有意义的

  • 如果swift的类继承NSObject,没有@objc修饰属性和方法,是否可以打印全部属性+方法?
    Runtime02.png

从结果发现获取的只有init方法,主要是因为在 swift.h文件中暴露出来的只有init方法

如果想让OC能使用,必须类继承NSObject + @objc修饰属性、方法

Runtime03.png

  • 如果去掉@objc修饰属性,将方法改成dynamic修饰,是否可以打印方法?
    Runtime04.png

从结果可以看出,依旧不能被OC获取到,需要修改为@objc dynamic修饰

结论:

对于纯swift类来说,没有动态特性dynamic(因为swift是静态语言),方法和属性不加任何修饰符的情况下,已经不具备runtime特性,此时的方法调度,依旧是函数表调度即V_Table调度

对于纯swift类,方法和属性添加@objc标识的情况下,可以通过runtime API获取到,但是在OC中是无法进行调度的,原因是因为swift.h文件中没有swift类的声明

对于继承自NSObject类来说,如果想要动态的获取当前属性+方法,必须在其声明前添加 @objc关键字,如果想要使用方法交换,还必须在属性+方法前添加dynamic关键字,否则当前属性+方法只是暴露给OC使用,而不具备任何动态特性

objc源码验证(由于xcode12.2暂时无法运行objc源码,下列验证图片仅供参考)

  • 进入class_copyMethodList源码,断住,查看此时的cls,其中data()存储类的信息
Runtime06.png

进入data,打印bits、superclass

Runtime07.png

从这里可以得出swift中有默认基类,即_SwiftObject

打印methods

Runtime08.png

swift源码中搜索_SwiftObject,继承自NSObject,在内存结构上与OC基本类似

#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
 @private
  Class isa;
  //refCounts
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

在之前的文章中有提到,其中TargetAnyClassMetadata继承自TargetHeapMetaData,其中只有一个属性kindTargetAnyClassMetadata有四个属性:isa、superclass、cacheData、data即bits

Runtime09.png

所以swift为了保留和OC交互,其在底层存储的数据结构上和OC是一致的

objc源码中搜索swift_class_t,继承自objc_class,保留了OC模板类的4个属性,其次才是自己的属性

struct swift_class_t : objc_class {
    uint32_t flags;
    uint32_t instanceAddressOffset;
    uint32_t instanceSize;
    uint16_t instanceAlignMask;
    uint16_t reserved;

    uint32_t classSize;
    uint32_t classAddressOffset;
    void *description;
    // ...

    void *baseAddress() {
        return (void *)((uint8_t *)this - classAddressOffset);
    }
};

为什么继承NSObject?
必须通过NSObject声明,来帮助编译器判断,当前类是一个和OC交互的类

元类型、AnyClass、Self

AnyObject

AnyObject:代表任意类的instance、类的类型、仅类遵守的协议

class HZMTeacher: NSObject {
    var age: Int = 18
}

var t = HZMTeacher()

//此时代表的就是当前HZMTeacher的实例对象
var t1: AnyObject = t

//此时代表的是HZMTeacher这个类的类型
var t2: AnyObject = HZMTeacher

//继承自AnyObject,表示JSONMap协议只有类才可以遵守
protocol JSONMap: AnyObject { }

如果是结构体遵守协议,会报错,表示JSONMap协议只有类才可以遵守


Anyobject01.png

Any

Any:代表任意类型,包括 function类型 或者Optional类型,可以理解为AnyObject是Any的子集

//如果使用AnyObject会报错,而Any不会
    var array: [Any] = [1, "HZM", "", true]

AnyClass

AnyClass:代表任意类的实例的类型 ,类型是AnyObject.Type

查看定义,是public typealias AnyClass = AnyObject.Type

T.self & T.Type

T.self:如果T是实例对象,返回的就是它本身,如果T是类,那么返回的是MetaData
T.Type:一种类型
T.selfT.Type类型

简单看一个例子

//此时的self类型是  HZMTeacher.Type
var t = HZMTeacher.self

再看下下面的例子:

var t = HZMTeacher()
//实例对象地址:实例对象.self 返回实例对象本身
var t1 = t.self
//存储metadata元类型
var t2 = HZMTeacher.self
Type01.png

type(of:)

type(of:):用来获取一个值的动态类型

var age = 10 as NSNumber
print(type(of: age))

<!--打印结果--!>
__NSCFNumber


//value - static type 静态类型:编译时期确定好的
//type(of:) - dynamic type:Int
var age = 10
//value的静态类型就是Any
func test(_ value: Any){
    
    print(type(of: value))
}

test(age)

<!--打印结果--!>
Int

实践一下下

class HZMTeacher{
    var age = 18
    var double = 1.85
    func teach(){
        print("HZMTeacher teach")
    }
}
class HZMPartTimeTeacher: HZMTeacher {
    override func teach() {
        print("HZMPartTimeTeacher teach")
    }
}

func test(_ value: HZMTeacher){
    let valueType = type(of: value)
    value.teach()
    print(value)
}
var t = HZMPartTimeTeacher()
test(t)

<!--打印结果--!>
HZMPartTimeTeacher teach
HZMTest.HZMPartTimeTeacher

运行时value的类型还是HZMPartTimeTeacher,只是传参的时候告诉编译器是HZMTeacher类型

protocol TestProtocol {
    
}
class HZMTeacher: TestProtocol{
    var age = 18
    var double = 1.85
    func teach(){
        print("HZMTeacher teach")
    }
}

func test(_ value: TestProtocol){
    let valueType = type(of: value)
    print(valueType)
}
var t = HZMTeacher()
let t1: TestProtocol = HZMTeacher()
test(t)
test(t1)

<!--打印结果--!>
HZMTeacher
HZMTeacher

3.如果将test中参数的类型修改为泛型,此时的打印是什么?

func test<T>(_ value: T){
    let valueType = type(of: value)
    print(valueType)
}

<!--打印结果--!>
HZMTeacher
TestProtocol

从结果中发现,打印并不一致,原因是因为当有协议、泛型时,当前的编译器并不能推断出准确的类型,需要将value转换为Any,修改后的代码如下:

func test<T>(_ value: T){
    let valueType = type(of: value as Any)
    print(valueType)
}

总结

当无弱引用时,HeapObject中的refCounts等于 strongCount + unownedCount

当有弱引用时,HeapObject中的refCounts等于 object + xxx + (strongCount + unownedCount) + weakCount

循环应用可以通过weak / unowned修饰参数来解决

swift中闭包的捕获列表值拷贝,即深拷贝,是一个只读的常量

swift由于是静态语言,所以属性、方法在不加任何修饰符的情况下时是不具备动态性即Runtime特性的,此时的方法调度是V-Table函数表调度

如果想要OC使用swift类中的方法、属性,需要class继承NSObject,并使用@objc修饰

如果想要使用方法交换,除了继承NSObject+@objc修饰,还必须使用dynamic修饰

Any:任意类型,包括function类型、optional类型

AnyObject:任意类的instance、类的类型、仅类遵守的协议,可以看作是Any的子类

AnyClass:任意实例类型,类型是AnyObject.Type

T.self:如果T是实例对象,则表示它本身,如果是类,则表示metadata.T.self的类型是T.Type

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

推荐阅读更多精彩内容