iOS-面试题1-NSObject本质

目录:

  1. NSObject本质
  2. OC对象的分类
  3. isa指针和superclass指针+窥探Class
  4. KVO和KVC
  5. Category分类
  6. load和initialize方法
  7. 关联对象
  8. block原理

一. NSObject本质

  1. Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
    Objective-C的对象、类主要是基于C\C++的结构体实现的,其内部只有一个isa指针,点进入Class,发现isa其实就是一个指向结构体的指针,既然是指针,指针在64位系统占8个字节,在32位系统占4字节。

  2. 两个获取内存大小的函数
    ① size_t class_getInstanceSize(Class _Nullable cls)
    获取实例对象的成员变量所占用内存大小(内存对齐后的)-> 其实就是实例对象至少占用的内存大小。
    sizeof同上,返回的是传入类型至少占用的内存大小,sizeof是个运算符。
    ② size_t malloc_size(const void *ptr)
    获取指针所指向内存的大小 -> 其实就是实例对象实际占用的内存大小。

  3. 一个NSObject对象占用多少内存?
    首先我们要知道指针占用8字节,int类型数据占用4字节。
    系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
    但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)。

  4. 对于一个NSObject对象,为什么我们需要8字节,系统却给我们分配16字节呢?
    OC对象至少占用16个字节,可查看源码验证,这是系统的硬性规定。
    分析对象内存的时候不要忘记结构体内存对齐(结构体的大小必须是最大成员大小的倍数,一般是8)和iOS内存对齐(对象内存大小必须是16的倍数)

博客地址:iOS-底层-NSObject本质

二. OC对象的分类

  1. OC对象分为几种?
    Objective-C中的对象,简称OC对象,主要可以分为3种:
    instance对象(实例对象)、class对象(类对象)、meta-class对象(元类对象)

  2. 关于instance对象
    instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象,分别占据着两块不同的内存,instance对象在内存中存储的信息包括:

isa指针和其他成员变量。

  1. 关于class对象
    每个类在内存中有且只有一个class对象,获取class对象的三种方式:

Class objectClass1 = [object1 class]; //对象的class方法
Class objectClass2 = [NSObject class]; //类的class方法
Class objectClass3 = object_getClass(object1); //传入对象

class对象在内存中存储的信息主要包括:

isa指针
superclass指针
类的属性信息(@property)
类的成员变量信息(ivar)
类的协议信息(protocol)

类的对象方法信息(instance method)
......

  1. 关于meta-class对象
    每个类在内存中有且只有一个meta-class对象,获取meta-class对象只有一种方法:
Class objectMetaClass = object_getClass([NSObject class]);//传入一个class对象

将类对象当做参数传入object_getClass,获得元类对象。类对象和元类对象都是Class类型的。

meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:

isa指针
superclass指针
类的类方法信息(class method)
......

  1. object_getClass的底层实现
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

可以发现,返回的是isa,所以:
如果传进来的是instance对象,返回class类对象
如果传进来的是class类对象,返回metra-class元类对象
如果传进来的是metra-class对象,返回NSObject(基类)的metra-class对象

  1. - (Class)class、+ (Class)class的底层实现
 - (Class) {
     return self->isa;
 }
//如果是对象方法,返回isa就返回了当前的类对象

 + (Class) {
     return self;
 }
//如果是类方法,返回自己就是类对象

综上,这两个class方法返回的就是类对象。

博客地址:iOS-底层-OC对象的分类

三. isa指针和superclass指针+窥探Class

  1. isa指针和superclass指针的作用
isa和superclass.png

关于isa指针:
① instance的isa指向class
② class的isa指向meta-class
③ meta-class的isa指向基类(NSObject)的meta-class
④ 基类(NSObject)的meta-class的isa指向它自己

关于superclass指针:
① class的superclass指向父类的class
② 如果没有父类,superclass指针为nil,(最后一直找不到方法会报错:unrecognized selector sent to instance/class)
③ meta-class的superclass指向父类的meta-class
④ 基类的meta-class的superclass指向基类的class

  1. 方法调用轨迹

① instance实例对象调用对象方法的轨迹:实例对象的isa找到class,方法不存在,就通过superclass找父类。
② class类对象调用类方法的轨迹:
类对象的isa找meta-class,方法不存在,就通过superclass找父类。

可以发现,不管是类对象还是实例对象,调用流程都是:
isa -> superclass -> suerpclass -> superclass -> .... superclass -> nil

  1. 窥探Class结构
窥探struct objc_class的结构.png

现在我们明白了,无论是类对象还是元类对象,他们在内存中的结构都是objc_class这种结构体类型的,但是他们存储的信息不一样,比如对于元类对象,只有isa、superclass、类方法有值,其他的都是空。

  1. OC的类信息存放在哪里?
    ① 属性、成员变量、协议、对象方法,存放在class对象中
    ② 类方法,存放在meta-class对象中
    ③ 成员变量的具体值,存放在instance对象中

博客地址:iOS-底层-isa指针和superclass指针+窥探Class

四. KVO和KVC

  1. 给person添加监听
使用KVO.png

① 使用KVO,系统会使用Runtime动态创建的一个NSKVONotifying_MJPerson类,这个类是MJPerson的子类。
② 添加监听的属性的值改变的时候,会调用NSKVONotifying_MJPerson类的setAge方法,setAge方法里面会调用_NSSetIntValueAndNotify方法,_NSSetIntValueAndNotify里面走如下步骤:
willChangeValueForKey 将要改变
setAge(原来的set方法) 真的去改变
didChangeValueForKey 已经改变
observeValueForKeyPath:ofObject:change:context: 监听到MJPerson的age属性改变了

  • 如果你自己写了这个类,就会报动态生成失败。
  • KVO效率没代理高,因为代理是直接调用,KVO还要动态生成一个类。
  1. NSKVONotifying_MJPerson类对象的isa指向哪里?
    NSKVONotifying_MJPerson是MJPerson的子类,所以NSKVONotifying_MJPerson类对象的isa也是指向它自己的元类对象。

  2. 为什么重写class、dealloc、isKVO方法?
    在上图中我们可以看出,创建NSKVONotifying_MJPerson之后会重写setAge、class、dealloc、isKVO 这四个方法,setAge方法我们知道为什么重写,但是为什么要重写后面三个方法呢?
    因为NSKVONotifying_MJPerson是内部创建的,不想让用户看到,所以用户调用class方法要把NSKVONotifying_MJPerson转成MJPerson,所以系统才重写了class方法。使用object_getClass函数(RuntimeAPI)获取的就是真实的,不会被转成MJPerson。

  3. 如何手动触发KVO?(就算没有人修改age值,也想触发监听方法observeValueForKeyPath)
    手动调用willChangeValueForKey:和didChangeValueForKey:方法。

  4. 直接修改成员变量会触发KVO吗?
    不会触发KVO,因为没调用重写后的set方法。

  5. 通过KVC给属性赋值会触发KVO吗?为什么?
    会,其实KVC内部调用了下面方法才会触发KVO的:

[person willChangeValueForKey:@"age"];
person->_age = 10;
[person didChangeValueForKey:@"age"];
  1. KVC设值原理
设值原理.png
  1. KVC取值原理
取值原理.png

博客地址:iOS-底层-KVO和KVC

五. Category分类

  1. Category的使用场合是什么?
    主要是给一些类添加新的方法,或者拆分类。

  2. 分类的底层结构是什么?
    分类的底层结构是_category_t结构体:

struct _category_t {
    const char *name; //名称
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; //对象方法
    const struct _method_list_t *class_methods; //类方法
    const struct _protocol_list_t *protocols; //协议
    const struct _prop_list_t *properties; //属性
};

所以分类的功能我们也知道了,分类可以添加属性、协议、对象方法、类方法。

  1. Category的实现原理是什么?
    ① 编译完成之后,分类里的所有东西都在category_t结构体中,暂时和类是分开的
    ② 运行的时候,通过Runtime加载某个类的所有Category数据
    ③ 把所有Category的属性、协议、方法数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面
    ④ 将合并后的分类数据(属性、协议、方法),插入到类原来数据的前面

  2. Category和类扩展有什么区别呢?
    类扩展就是在类的.m里面添加一些属性,成员变量,方法声明。其实类扩展就是相当于将原来写在.h文件里面的东西剪掉放到.m里面,把原来公开的属性,成员变量,方法声明私有化。所以,类扩展里面的东西在编译的时候就已经存在类对象里面了,这点和分类不同。

博客地址:Category分类

六. load和initialize方法

  1. +load方法
    调用时机:+load方法会在Runtime加载类、分类时调用。
    调用顺序:先调用父类的+load,后调用子类的+load,再调用分类的+load,并且先编译的先调用。
    调用方式:根据函数地址直接调用。
    调用次数:每个类、分类的+load方法,在程序运行过程中只调用一次。
  • +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用(通过isa和superclass找方法),所以不会存在方法覆盖的问题。
  • 上面我们都没有主动调用过+load方法,都是让系统自动调用,系统会根据+load方法地址,直接调用。如果我们主动调用了+load方法,那走的就是objc_msgSend函数调用(通过isa和superclass找方法)这一套了。
  1. +initialize方法
    调用时机:+initialize方法会在类第一次接收到消息时调用(走的也是objc_msgSend这一套机制)。
    调用顺序:先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类)。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)。如果分类实现了+initialize,就会覆盖类本身的+initialize调用。
    调用方式:通过objc_msgSend调用。
    调用次数:每个类只会初始化一次。

博客地址:+load和+initialize方法

七. 关联对象

  1. 分类可以添加属性吗?成员变量呢?
    分类可以添加属性,这从分类的底层结构也可以看出来,但是在分类中添加属性只会生成set、get方法的声明,不会生成_开头的成员变量,也不会生成set、get方法的实现。
    分类中不能直接添加成员变量,否则报错,其实从分类的底层结构也能看出来,分类并没有存放成员变量的地方。

  2. 如何给分类添加成员变量?
    可以使用关联对象间接实现Category有成员变量的效果。

  3. 使用关联对象给分类添加成员变量

//name
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, @selector(name));
}
  • 理论上给什么对象添加关联都可以,上面我们是给实例对象添加关联对象,因为每个实例对象都不一样。如果给类对象添加关联对象,也是可以的,但是由于类对象只有一个,添加的关联对象也是唯一的,这样做没什么意义(MJExtension中有给MJProperty类对象添加关联对象)。

  • _cmd是当前方法的@selector,就是@selector(当前方法名)。
    其实每个方法都有两个隐式参数:self和_cmd,比如上面的name方法也可以写成:

 - (NSString *)name:(id)self _cmd:(SEL)_cmd
 {
 // 隐式参数
 // _cmd == @selector(name)
 return objc_getAssociatedObject(self, _cmd);
 }
  1. 关联对象的原理
    ① 关联对象不是存储在原来实例对象和类对象里面,关联对象是通过Runtime实现的。
    ② 关联对象存储在全局的统一的一个AssociationsManager中。
  • 设置某个关联对象为nil,就相当于是移除某个关联对象
  • 移除所有的关联对象用objc_removeAssociatedObjects

博客地址:关联对象

八. block原理

block1-底层结构、变量捕获、类型

  1. 什么是block?
    block是封装了函数调用以及函数调用环境的OC对象

  2. 验证上面的结论

block的本质.png

如上图所示,block底层就是一个__main_block_impl_0结构体,它由三个部分组成:
① 第一部分是impl,它是个结构体,里面有isa指针和FuncPtr指针,FuncPtr指针指向__main_block_func_0函数,这个函数里面封装了block需要执行的代码。
② 第二部分是desc,它是个指针,指向__main_block_desc_0结构体,它里面有一个Block_size用来保存block的大小。
③ 第三部分是age,它把外面访问的成员变量age封装到自己里面了。

  1. block如何进行变量捕获?
    ① 如果是被auto修饰的局部变量,会被捕获,是值传递
    ② 如果是被static修饰的局部变量,会被捕获,是指针传递
    ③ 如果是全局变量,不会被捕获,因为可以直接访问
  • auto自动变量,离开作用域就销毁,默认省略auto。比如我们常见的 int age = 10,其实就是默认省略了auto,本来应该是auto int age = 10。
  1. block中会捕获self吗?
    block中会捕获self,并且是指针捕获。既然被捕获,就说明self是局部变量,为什么self是局部变量呢?其实每个方法都两个隐式参数,一个是self一个是_cmd,self是方法调用者,_cmd是方法名,既然self被当做参数了,那self肯定是局部变量了。self都被捕获了,肯定可以获取到self中的其他信息了。

接下来我们讲的都是在MRC环境下

  1. block有三种类型,最终都是继承于NSBlock类型
    __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    __NSMallocBlock__ ( _NSConcreteMallocBlock )
    __NSStackBlock__ ( _NSConcreteStackBlock )

  2. 三种block在内存中的分布

iOS程序的内存布局:
iOS程序的内存布局

三种block在内存中的分布:
三种block内存分配.png

现在我们知道了三种block在内存中的分布:

__NSGlobalBlock__是放在数据段的,也就是和全局变量放在一起。
__NSMallocBlock__是放在堆区的,和一般的OC对象一样的,需要我们自己管理内存。
__NSStackBlock__是放在栈区的,和局部变量是一样的,系统自动管理内存。

  1. 什么样的block才是这三种block的某一种类型呢?
block类型 环境
__NSGlobalBlock__ 没有访问auto变量
__NSMallocBlock__ __NSStackBlock__调用了copy
__NSStackBlock__ 访问了auto变量
  • GlobalBlock没有访问auto变量,这种类型的block都可用方法代替,不常用。
  • StackBlock访问了auto变量,放在栈区,这时候block捕获了auto变量的值,然后存储在block结构体内部,栈区是系统自动管理的,所以在代码块结束之后,block内存会被销毁,这时候block结构体内部的值就是乱七八糟的了,block就会有问题,如何解决这个问题呢?
    可以把block从栈放到堆里面,每一种类型的block调用copy后的结果如下所示:
block类型 副本源的配置存储域 复制效果
__NSGlobalBlock__ 程序的数据区段 什么也不做
__NSMallocBlock__ 引用计数器增加
__NSStackBlock__ 从栈复制到堆

将上面的NSStackBlock加个copy变成NSMallocBlock,block就能正常使用了。

博客地址:底层结构、变量捕获、类型

block2-copy操作、对象类型的auto变量、__block

  1. 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
    ① block作为函数返回值时
    ② 将block赋值给__strong指针时
    ③ block作为Cocoa API中方法名含有usingBlock的方法参数时
    ④ block作为GCD API的方法参数时

  2. block如何捕获对象类型的auto变量?如何进行内存管理的?

无论MRC、ARC,当block内部访问了对象类型的auto变量时(这时就是__NSStackBlock__,放在栈区):

① 如果block是在栈上,将不会对auto变量产生强引用
② 如果栈上的block被拷贝到堆上(自己拷贝的或者ARC下系统自动拷贝的)
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
③ 如果堆上的block被移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(或者release)

  1. block内部如何修改auto变量值?
    ① 使用static修饰
    ② 使用全局变量
    ③ 使用__block修饰:
    __block可以用于解决block内部无法修改auto变量值的问题,编译器会将__block变量包装成一个对象。
    __block不能修饰全局变量、静态变量(static)(因为__block的作用就是上句👆)。

  2. 为什么__block修饰的auto变量可以修改变量值?
    使用__block修饰age,会将age包装成__Block_byref_age_0结构体(对象),对象里面存着isa,对象的地址,对象的大小,age的值,然后通过对象里面的__forwarding指针拿到自己,再拿到自己的age值,进行修改。如果没修改外面的变量就不要加__block,因为又包装了一层对象,等用到的时候再加。

  3. 执行下面代码会报错吗?

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

回答:不会。因为“ [arr addObject:@"123"];”是使用arr而不是修改它的值(例如:arr = nil)。

  1. 使用__block修饰变量的确可以达到修改变量的值的目的,如果要再次访问变量,到底访问的是__Block_byref_age_0结构体还是结构体里面的“int age”呢?
    使用__block修饰变量,再次访问,访问的是__Block_byref_age_0里面的int age变量。

现在想一下,为什么苹果要设计成访问变量(使用了__block修饰)直接访问的就是__Block_byref_age_0里面的变量呢?
因为对于一般的开发者来说,可能不知道被__block修饰后还会包装一次,就像KVO苹果重写class方法一样,是不让开发者知道有这么个操作,所以你访问变量,就把里面那个变量的地址给你了。

  1. __forwarding指针的作用

我们知道__Block_byref_age_0结构体中的__forwarding指针存的是自己的地址,当我们想要访问age,需要先通过age结构体中的__forwarding获取自己,然后再获取age,为什么设计这么奇怪呢?

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

博客地址:block2-copy操作、对象类型的auto变量、__block

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

  1. 对象类型的auto变量和__block变量内存管理的区别?

有如下三个auto变量:

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

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

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

相同点
① 当block在栈上时,对它们都不会产生强引用
② 当block拷贝到堆上时,都会通过copy函数来处理它们
③ 当block从堆上移除时,都会通过dispose函数来释放它们

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

  1. __block修饰的对象类型的内存管理

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

  1. ARC如何解决循环引用?
    用__weak解决
    用__unsafe_unretained解决

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

  1. MRC如何解决循环引用?
    使用__unsafe_unretained解决

  2. block属性的建议写法?

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

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

① 使用copy会将栈上的block拷贝到堆上,如果不使用copy,block就不会拷贝到堆上,因为MRC编译器不会自动copy,所以只能用copy。
② 以前我们说过,ARC环境,并且把block赋值给强指针,编译器会自动把block拷贝到堆上,所以ARC使用copy和strong都可以,没区别。
③ 为了统一好记,我们统一对block使用copy

  1. __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);
};

① 原因一,如果在block内部通过weakSelf->_age会报错“弱指针不允许访问,因为有可能为null,让你使用强指针”,所以我们就用__strong强指针来访问self。
② 原因二,访问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操作,就不会在堆上。使用注意:循环引用问题。

博客地址:block3-__block变量的内存管理、__forwarding、__block修饰的对象类型、循环引用

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