iOS-block3-__block变量的内存管理、__forwarding、__block修饰的对象类型、循环引用

上文讲了一下__block的原理,但是关于__block还有一些其他东西,这篇文章就来慢慢讲述。

一. __block变量的内存管理

通过上文我们知道,block捕获对象类型的auto变量就会多出两个函数用于做内存管理操作(__main_block_copy_0和__main_block_dispose_0),如下:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

block也会使用这两个函数管理__block修饰的变量的内存,这也从侧面证明“编译器会将__block变量包装成一个对象”,这句话是对的,因为只有对象才需要内存管理。
下面我们就研究使用__block修饰的变量的内存管理:

  1. 当block在栈上时,并不会对__block变量产生强引用
  2. 当block被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)

如下图:

__block的内存管理-copy

解释:刚开始的时候,__block变量和block0、block1肯定都在栈区,假如他们同时使用__block变量。
当把block0复制到堆区,也会把__block变量复制到堆区,并且block0强引用__block变量。
再把block1复制到堆区,就不会再次复制__block变量了(因为已经拷贝过了),这时候block0和block1都会强引用着__block变量。

  1. 当block从堆中移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)

如下图:

__block的内存管理-dispose

解释:当没有block引用着__block变量,__block变量才会被释放。

为什么要这么管理呢?
因为__block变量是个对象,block内部使用了它,所以需要block来管理它的内存。

二. 对象类型的auto变量和__block变量的区别

(static修饰的变量和全局变量在全局区)

先把下面代码转成C++代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int no = 20;
        
        __block int age = 10;
        
        NSObject *object = [[NSObject alloc] init];
        __weak NSObject *weakObject = object;
        
        MJBlock block = ^{
            age = 20;
            
            NSLog(@"%d", no);
            NSLog(@"%d", age);
            NSLog(@"%p", weakObject);
        };
        
            block();
    }
    return 0;
}

转成的C++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no;  
  NSObject *__weak weakObject;  
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}

可以发现,结构体多了三个成员,分别对应捕获的三个auto变量,如下👇:

 int no;  
 __Block_byref_age_0 *age; // by ref
 NSObject *__weak weakObject;  

1. 对象类型的auto变量、__block变量

相同点:

  1. 当block在栈上时,对它们都不会产生强引用
  2. 当block拷贝到堆上时,都会通过copy函数来处理它们

对象类型的auto变量(假设变量名叫做p)

_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block变量(假设变量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 当block从堆上移除时,都会通过dispose函数来释放它们

对象类型的auto变量(假设变量名叫做p)

_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block变量(假设变量名叫做a)

_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

不同点:

  1. 对于对象类型的auto变量,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  2. 对于__block变量:_Block_object_assign函数只会产生强引用(retain)

2. 普通的的auto变量

例如:int a = 10,就是值引用,然后放到__main_block_impl_0结构体里面。

三. __forwarding指针的作用

我们知道__Block_byref_age_0结构体中的__forwarding指针存的是自己的地址,当我们想要访问age,需要先通过age结构体中的__forwarding获取自己,然后再获取age:

(age->__forwarding->age) = 20;

为什么设计这么奇怪呢?如下图:

__block的__forwarding指针

解释:如果栈上的block复制到堆上了,那么栈上堆上肯定都有一块内存。如果我们想把20赋值到堆上的block,如果不用__forwarding指针,访问栈上的block,就会把20赋值到栈上的block。如果使用__forwarding指针,不管访问的block在哪,最后赋值到的一定是堆上的block。

四. __block修饰的对象类型

以前我们是使用__block修饰基本数据类型,如果使用__block修饰对象类型会怎么样呢?

如下代码,内部会发生什么呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        //不能反过来(如:__weak __block),因为__weak只能修饰OC对象
        __block __weak MJPerson *weakPerson = person;
        
        MJBlock block = ^{
            NSLog(@"%p", weakPerson);
        };
        
        block();
    }
    return 0;
}

转成C++代码,如下:

struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指针,这里就是弱指针 (以前这里直接是int age;的)
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // 默认强指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

首先,发现__main_block_impl_0里面默认有一个强指针指向__Block_byref_weakPerson_0结构体。由于外面是用__weak修饰的,所以__Block_byref_weakPerson_0结构体里面有一个弱指针指向person对象,如下图:

__block修饰对象类型

上面的代码还可以看出__Block_byref_weakPerson_0结构体里面多了两个方法copy和dispose方法。(这两个方法在以前我们使用__block修饰基本数据类型的时候是没有的)

在__Block_byref_weakPerson_0结构体创建里面(就是__block __weak MJPerson *weakPerson = person;的底层实现),传入了copy和dispose两个函数地址,如下:

//copy block内存管理相关
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));

       //__block __weak MJPerson *weakPerson = person;的底层实现
       //就是创建__Block_byref_weakPerson_0结构体
__Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0),
            __Block_byref_id_object_copy_131, //传入copy方法的地址
            __Block_byref_id_object_dispose_131, //传入dispose方法的地址
            person};

        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指针,这里就是弱指针 (以前这里直接是int age;的)
};

//copy 对象内存管理相关
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//dispose 
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

观察上面的copy,发现它里面也有调用_Block_object_assign函数,(char*)dst就是结构体自己的地址值,但是它却+40 (8+8+4+4+8+8),从上上面我们分析可知:(char*)dst+40就是“MJPerson *__weak weakPerson;”的地址值。然后_Block_object_assign函数再根据传入的对象是强还是弱,再决定retain还是弱引用。所以上图的第二根指针的强弱,取决于外面修饰对象的的是强指针还是弱指针?

当block销毁的时候,block内部会调用__main_block_dispose_0函数,然后上图的第一根线就没了。第一根线就没了,__Block_byref_weakPerson_0也会销毁,它也会调用自己里面的__Block_byref_id_object_dispose_131函数,__Block_byref_id_object_dispose_131函数再把person引用计数器减一(或者销毁)。

总结:

对于block:

  1. 如果block是在栈上,将不会对__Block_byref_weakPerson_0产生强引用
  2. 如果栈上的block被拷贝到堆上
    _Block_object_assign函数会对__Block_byref_weakPerson_0产生强引用
  3. 如果堆上的block被移除
    _Block_object_dispose函数会对__Block_byref_weakPerson_0产生弱引用或者移除

对于__block修饰的对象类型:

  1. 当__block变量在栈上时,不会对指向的对象产生强引用
  2. 当__block变量被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用__block变量内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain,一直是弱的。这个特例只会在MRC并且是__block修饰对象类型才有
  3. 如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)

下面验证上面的“注意”:

将项目切换成MRC环境,使用__block修饰对象类型,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block MJPerson *person = [[MJPerson alloc] init];
        
        MJBlock block = [^{
            NSLog(@"%p", person);
        } copy];

        [person release];

        block();  //在此处打断点

        [block release];
    }
    return 0;
}

在上面代码断点处,可以发现打印:“[MJPerson dealloc]”,此时block还没释放,但是person却不在了,验证了我们上面说的“注意”。根据这一点,在MRC环境下,我们可以使用__block来解决循环引用,这个后面会说到。

解释如上代码,当block被拷贝到堆上的时候,__block修饰的person也会被拷贝到堆上,这时候会调用__block修饰的变量内部的copy函数,MRC环境下,copy函数内部只会对person对象产生弱引用。如下图:

__block修饰的对象.png

如果将上面代码的__block去掉,那么在断点处就不会打印:“[MJPerson dealloc]”,之后block释放之后person才会释放。这时候就是block直接捕获person对象了,如下图:

直接引用.png

五. block循环引用

1. 循环引用产生的原因

如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

首先“ MJPerson *person = [[MJPerson alloc] init];”执行完,会有个person强指针指向MJPerson。MJPerson里面有个_block,当执行完 person.block = ^{NSLog(@"age is %d", person.age);},MJPerson和block之间的循环引用就会产生,如下图:

循环引用.png

(block是封装了函数调用以及函数调用环境的OC对象)

同理,self和block之间的循环引用如下图:

block捕获self

因为self是局部变量,所以block也会捕获self,也会造成循环引用。block底层C++代码如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __strong self; //block内部有个self强指针,指向self对象
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2. ARC如何解决循环引用

① 用__weak解决

首先,我们想一下,如何解决循环引用,肯定是把其中一根线变成虚线就可以了,但是把哪根线变成虚线呢?我们想了下,person中的block属性不能变成虚的,因为person要拥有block,那么只能把block里面的person变成虚线了,怎么把block里面的person变成虚线呢?其实就是在上面的代码,把__strong变成__weak就好了,前面我们已经学过了,在外面用__weak修饰,里面就会变成__weak了,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;

        //__weak MJPerson *weakPerson = person; //这样写死了,不推荐
        __weak typeof(person) weakPerson = person;
        person.block = ^{
            NSLog(@"age is %d", weakPerson.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

这时候block底层C++代码如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __weak weakPerson;  //weakPerson弱指针
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

使用__weak后图示如下:

__weak.png

解释:当{}里面的代码执行完,MJPerson *person局部变量就会消失,person指向MJPerson的线就会消失,这时候没有实线指向MJPerson,MJPerson就会销毁,MJPerson销毁后,MJPerson指向block的线就会消失,这时候没有实线指向block,block也会销毁,循环引用解除。

② 用__unsafe_unretained解决

上面的代码,把__weak替换成__unsafe_unretained也能解决循环引用,代码就省略了。

它们有什么区别呢?直接看字面意思,__weak:不会产生强引用,__unsafe_unretained:不会产生强引用,不安全。

__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,这时候如果再去访问指针指向的地址就会报野指针错误

如下图,如果使用__weak,当weakPerson指向的对象销毁时,会把weakPerson置为nil,使用__unsafe_unretained就不会。

__weak会把指针置为nil

③ 用__block解决(必须要调用block)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //使用__block解决循环引用
        __block MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
            person = nil;
        };
        
        person.block();
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代码,即可解决循环引用。

首先我们分析这里的循环引用是如何产生的。
前面我们说过使用__block修饰对象,block内部会捕获__block变量,__block变量内部又有person对象,而且默认是强引用,对象又拥有block,所以block、__block变量、person对象之间会有循环引用,示意图如下:

循环引用.png

所以,如果想要解决__block的循环引用,我们可以调用block,并且在block里面把person对象置为nil,这样__block变量就不会持有person对象了,循环引用被打破,代码如上,示意图如下:(以前我们也验证过,__block变量里的person指针就是我们在外面拿到的person指针)

打破三者循环引用

总结:不用想也知道ARC环境下使用__weak解决循环引用最好。

3. MRC如何解决循环引用

我们知道MRC不支持使用__weak的,否则报错“Cannot create __weak reference in file using manual reference counting”
所以,相对于ARC,MRC就只有两种方式解决循环引用了。

① 使用__unsafe_unretained解决

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // MRC不支持__weak的否则报错:👇
        // Cannot create __weak reference in file using manual reference counting
        
        __unsafe_unretained MJPerson *person = [[MJPerson alloc] init];

        person.age = 10;
        person.block = [^{
            NSLog(@"age is %d", person.age);
        } copy];
        
        [person release];
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代码,使用__unsafe_unretained修饰person,当block引用person的时候,person的引用计数器不会增加,还是刚开始创建的时候的1,所以当“ [person release];”person就挂了。
如果不使用__unsafe_unretained修饰,person创建的时候引用计数器为1,因为person默认强指针,所以block引用person的时候又加1,所以当“ [person release];”person引用计数器还是1,不会挂。

② 使用__block解决

还记得上面验证的“注意”吗?MRC环境,使用__block修饰对象类型,对象不会被retain,所以也可以解决循环引用。

代码省略,可自行将上面的__unsafe_unretained替换成__block进行验证。

补充1:block属性的建议写法

MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
  1. 使用copy会将栈上的block拷贝到堆上,如果不使用copy,block就不会拷贝到堆上,因为MRC编译器不会自动copy,所以只能用copy。
  2. 以前我们说过,ARC环境,并且把block赋值给强指针,编译器会自动把block拷贝到堆上,所以ARC使用copy和strong都可以,没区别。
  3. 为了统一好记,我们统一对block使用copy。

为什么一定要copy到堆上呢?如果在栈上我们就无法控制block的生命周期,在堆上,什么时候让它生让它死都可以。

补充2:__strong typeof(weakSelf) myself = weakSelf;

为什么要这么写:

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) myself = weakSelf;
//报错:
//Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
    NSLog(@"age is %d", myself->_age);
};
  1. 原因一,如果在block内部通过weakSelf->_age会报错“弱指针不允许访问,因为有可能为null,让你使用强指针”,所以我们就用__strong强指针来访问self。
  2. 原因二,访问self的时候我们使用一个临时的强指针来访问self,这样在整个block执行期间,可以保证self对象不会被销毁,同时,block调用完后,临时的强指针被销毁,一切又回归原来的样子。这样既能保证整个block执行期间,self对象不会被销毁,又能保证不会产生循环引用。

面试题:

  1. block的原理是怎样的?本质是什么?
    block是封装了函数调用以及调用环境的OC对象,比如函数的调用地址、捕获的变量都封装到了里面。

  2. __block的作用是什么?有什么使用注意点?
    编译器会将__block变量包装成一个对象,就是__Block_byref_person_0这种结构体,可以解决block内部无法修改auto变量的问题(自己思考为什么不能修改)。
    使用注意:__block变量内部自己也会进行内存管理,而且MRC环境下,__block修饰对象,对象不会被retain的。

  3. 为什么使用__block修饰auto变量,在block内部就能修改此变量了?
    block内部有个指针指向__Block_byref_person_0结构体,通过访问结构体,再通过结构体访问变量进行修改的。

  4. block的属性修饰词为什么是copy?使用block有哪些使用注意?
    block一旦没有进行copy操作,就不会在堆上。使用注意:循环引用问题。

  5. block在修改NSMutableArray,需不需要添加__block?

NSMutableArray *array = [NSMutableArray array];
person.block = ^{
    [array addObject:@"123"];
};  

不需要。
如上代码,这个我们以前说过,给array数组添加成员只是操作array,除非要把array指针指向其他地方才要使用__block(比如:array = nil)。
一般如果没必要不要用__block修饰,因为还要包装一层。

Demo地址:block循环引用

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

推荐阅读更多精彩内容