iOS底层原理(下)

8、UI事件处理

iOS事件分类:触摸事件、定时器事件、传感器事件、远程控制事件

UIWindow:是一种特殊的View,可以作为试图容器协调VC,还可以处理事件分发

1、通常在一个程序中只会有一个UIWindow为keyWindow,但可以手动创建多个UIWindow

2、触摸事件传给当前window,其他事件只交给keyWindow

3、alert和键盘都是单独的window,每一个UIWindow之间是独立的

4、WindowLevel控制显示等级,默认值是 0,值越高就展示在越前面

触摸事件产生:

IOKit库捕获到事件,交给桌面app,再通过进程通讯交给当前app的runloop的source1任务,然后转成source0,封装成uievent添加到UIApplication事件队列,再交给当前UIWindow分发处理

UIWindow沿着视图树不断逆序遍历subviews,调用子视图的hitTest:withEvent:和pintInside:找到第一响应者,形成响应者链,将事件传递给第一响应者进行处理

触摸事件的传递

继承了UIResponder的UI对象能接受并处理事件,UIResponder内部提供了一系列的touch方法处理事件,提供nextResponder属性可以向上传递事件

hitTest:方法根据试图是否允许交互、是否隐藏、是否透明、触摸点是否在当前视图范围内pointInside方法等查找响应者



事件的响应

1、事件传递给第一响应者后开始处理响应,有三种事件响应方式:UIResponder的touches方法,UIGestureRecognzier和UIButton的UIControlEvent事件

2、UIWindow将事件优先交给手势处理,如果有手势处理直接调用响应者的touchcancel方法,事件不再传递处理结束

3、如无手势,系统会调用第一响应者的touches方法处理事件,第一响应者不能处理事件,会顺着响应链向上传递,直到UIApplication也不能处理将其丢弃

4、UIButton重写了UIResponder的touchs方法不调用super的touchs使事件不再往上传递

父试图加了手势,子button即时没添加target+action,手势也不响应,除非设置userEnabled=NO,UIControl没有重写touch方法,没有添加事件,父试图的手势也会响应

同时响应子按钮click和父试图的tap,设置tap的cancelsTouchesInView=no,参考:https://www.jianshu.com/p/617577ff4be1

响应链应用场景:

1、事件拦截和事件转发:有两种方案实现

(1)重写touchesBegan等系列方法进行拦截

(2)重写hitTest方法添加逻辑返回自己想要的响应者

2、扩大按钮点击范围:创建button分类重写pointInside方法使的系统调用hitTest时判断范围更大


3、回溯响应链生成埋点控件唯一id

4、基于UI响应链的事件传递方式,实现步骤:

(1)添加一个UIResponder的分类方法实现中调用nextResponder的该方法

- (void)routerEventWithSelectorName:(NSString *)selectorName object:(id)object userInfo:(NSDictionary *)userInfo {

 [[self nextResponder] routerEventWithSelectorName:selectorName object:object userInfo:userInfo];

}

(2)当第一响应者发生事件后,调用该方法就会沿着响应链向上传递

(3)响应链上的控件可以实现该方法获取事件进行处理,或者添加内容让事件继续向上传递

手势和UI事件响应:

通过hittest找到第一响应者后,可能会有两条路线,手势识别和touchBegan系列方法同时执行,手势识别优先级高如果识别成功会调用touchCancel方法取消响应链上所有的touch方法调用


父View加了tap手势,子view也会相应手势,但是点击子button不会响应手势,不管button有没有添加事件

原因是UIButton,UISwitch,UISegmentedControl,UIStepper和UIPageControl等控件的是否接受触摸事件方法(gestureRecognizerShouldBegin:)返回值默认是NO,这样点击按钮时手势识别会失败,只处理button的touch事件和lick事件,button默认重写的touch事件默认不向上调用touch方法

如果想同时响应tap和button事件,设置设置tap的cancelsTouchesInView为NO,这样不再调用button的是否接受触摸事件方法,两个事件同时响应

aview上增加button,button添加点击事件,再添加一个子bview,点击bview时,bview和button都走了touchbegan和end系列方法,但是button事件没有执行,猜测只有button是第一响应者时才执行

如果aview加了tap手势,点击bview也会触发tap方法

父View加了tap手势,点击子视图uicollectionview的cell,发现cell没有被响应,响应的是tap手势事件

原因是手势识别时可以延时响应touch事件,只有手势识别完成才调用touch相关事件,cell的响应机制就是这样,第一响应者是cell,但是手势识别优先处理手势并没有处理touch事件

如果想要响应cell事件不响应tap,在tap的应接受触摸代理方法shouldReceiveTouch:判断点击的是tableview就返回NO

嵌套滚动试图冲突,如一个UICollectionView嵌套了一个UICollectionView,希望嵌套的UICollectionView在父视图达到一定高度时,父视图不再滚动,而是子视图滚动,有三种方案:

a、设置只有底层响应滚动上层不响应,底层scrollview可滚,上层tableview不可滚动,tableview.height = tableview.contentSize.height

b、创建底层滚动试图的子类,实现手势代理方法使两个滚动手势同时响应,监听两个viewdidscroll方法,在同一方法里分情况设置两个视图的滚动偏移量,如爱钱进产品列表页

c、上下试图同时响应滚动手势,在手势冲突的代理方法里根据偏移量判断响应哪个手势,https://www.jianshu.com/p/adc8d45f0fef

手势:

参考:https://www.jianshu.com/p/617577ff4be1

常见的手势冲突处理:https://www.jianshu.com/p/adc8d45f0fef

我们可以通过配置手势的属性来改变它的表现,下面介绍三个常用的属性:

cancelsTouchesInView:该属性默认是 true。顾名思义,如果设置成 false,当手势识别成功时,将不会发送 touchesCancelled 给目标视图,从而也不会打断视图本身方法的触发,最后的结果是手势和本身方法同时触发。有的时候我们不希望手势覆盖掉视图本身的方法,就可以更改这个属性来达到效果。

delaysTouchesBegan:该属性默认是 false。在上个例子中我们得知,在手指触摸屏幕之后,手势处于 .possible 状态时,视图的 touches 方法已经开始触发了,当手势识别成功之后,才会取消视图的 touches 方法。当该属性时 true 时,视图的 touches 方法会被延迟到手势识别成功或者失败之后才开始。也就是说,假如设置该属性为 true ,在整个过程中识别手势又是成功的话,视图的 touches 系列方法将不会被触发。

delaysTouchesEnded:该属性默认是 true。与上个属性类似,该属性为 true 时,视图的 touchesEnded 将会延迟大约 0.15s 触发。该属性常用于连击,比如我们需要触发一个双击手势,当我们手指离开屏幕时应当触发 touchesEnded,如果这时该属性为 false,那就不会延迟视图的 touchesEnded 方法,将会立马触发 ,那我们的双击就会被识别为两次单击。当该属性是 true 时,会延迟 touchesEnded 的触发,将两次单击连在一起,来正常识别这种双击手势。

9、分类Category

分类:

Category编译之后的底层结构是category_t,里面存储着分类的对象方法、类方法、属性声明、协议信息

在程序运行的时候,runtime会将Category的数据,合并到类信息class_rw_t的方法表头中(类对象、元类对象中)

rw_t->method是一个二维数据结构,原始类的只有一个指针指向类的方法list,runtime在运行时将数组扩容,将分类方法list插入二维数据前面,后编译的在前面


分类的属性:

Category可以添加属性,但是只有属性声明,不会自动生成set/get方法的实现,更不会添加成员变量

因为category_t结构体中并不存在成员变量,且成员变量列表class_ro_t是只读的,不可以在运行时添加,可以自己实现set/get方法通过关联对象间接实现

分类和延展的区别:

延展Class Extension在编译的时候包含在类信息中,Category是在运行时合并过去

如何做到分类方法不覆盖原方法:

可以在调用时runtime遍历方法list找到最后的方法调用

load、initialize方法

1、+load是启动时runtime加载类和分类的过程中根据函数地址调用,只调用一次;

runtime在调用+load方法之前,准备了两个数组分别存储类和分类列表,类数组是先根据编译先加入数组,加入子类时会先把父类加入;

分类只按编译顺序加入数据,调用时先遍历类数组调用+load,所有类都调用完再调用分类数组

2、+initialize方法会在类第一次接收到消息时调用,通过objc_msgSend进行调用

runtime的objc_msgSend内部实现,先判断类是否初始化,如未初始化会先调用父类的+initialize,再调用子类的+initialize,每个类只会初始化1次


3、因为initialize是通过objc_msgSend调用,存在两个问题:

分类实现了+initialize,就覆盖类本身的+initialize调用,如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)所以initialize实现代码要配合GCD ONCE函数

10、关联对象AssociatedObject:

关联对象:

一个对象可以通过key关联多个对象,使用key添加修改删除关联对象

对象走dealloc时会从全局HashMap中查找并删除他的关联对象

关联对象的key要保证全局唯一,有很多种方案实现,一般使用全局静态唯一地址值,建议使用@selecor(name)


存储结构:切套哈希表

外层HashMap以对象地址为key值为内层哈希表,内层哈希表以属性地址为key值为存储的value和内存管理变量

关联对象不会对传入的对象obj进行引用,底层实现支持根据obj生成一个唯一key,对应存储的value进行持有


关联对象如何实现weak修饰

关联对象默认修饰符只有retain, assgin copy没有weak

可以对象对象赋值给一个__weak变量,创建一个返回值为__weak变量的block,将block存储为关联对象值,取值时执行block

11、通知

注册通知有两种方式:selector和block

selecer需要传入接收者observer,selecter,name,和发送者anObject

Block不需要传入接收者,可以传指定队列中执行block,可以指定到主线程执行

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

anObject参数:

anObject:发送通知的对象,如果传nil表示不关心是谁发送的通知,观察者那边传入nil表示可以接受任何发送者的通知。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test:) name:@"test" object:@"1"];

[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:@"2"];

object相同才会接收到通知方法调用,如果我有一个需求,用户点击某个按钮十次才会触发某个事件,那么我就可以把这个参数设置为 @10,每次点击将发送的 object 加1,这样当点击的数量等于10的时候就可以自动触发回调事件而不需要写

通知的存储结构:哈希表+链表(name和anObject是key,observer和seleter是链表节点值)

注册的通知存储在通知中心单利的NCTbl结构体中,以有无name和object分别储存在三个表结构中

1、有name和无论有无object,使用二维哈希表+链表Obs,外层key是name地址值为内层哈希表,内层哈希表key是object值时存储observer和select的链表


2、无name有anObject的,一维表+链表Obs,key为object值为链表

3、全无的,直接存链表Obs

通知的发送:

1、一条通知对象是由三个元素组成:通知名name,发送者object,附加参数userInfo,发送消息时选择性传入

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

2、发送通知时根据name和object查找到所有的链表节点obs对象(保存了observer和sel),放到数组中,依次调用performSelector同步发送,block形式注册的通知放到队列中执行

3、添加通知队列NSNotificationQueue的通知,可以根据添加的策略结合runloop运行时机将通知交给通知中心发送,并提供通知合并管理功能

从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop的时机来触发的

通知与多线程:

1、通知是线程安全的,通过runtime执行selecter,通知的发送和接收在同一线程且多个接收者顺序执行

2、想要接受时在指定线程执行可以在selecter方法内部切换线程,或使用block注册时指定队列,或使用NSMachPort实现线程通讯参考:https://juejin.cn/post/6844904147691503624

通知移除:

iOS9之后对接收者的持有由unsafe_unretained 变成了weak,不移除也安全

但是通过 addobserverForName :object: queue:usingBlock 方法注册的观察者是强引用的需要手动释放

12、KVO

KVO:键值监听,用于监听某个对象属性值的改变

使用NSObject分类实现监听的注册和移除方法,- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

一个KVO包含的数据有5个:调用add的对象为注册KVO的对象(被观察者)、实现监听代理的Obsever(观察者)、属性keyPath、新老值option,环境信息context(不同对象相同属性key时用于区分环境)

触发KVO的方式点语言self.age和KVC,直接访问成员变量不行self->age

KVC可以触发KVO,不管KVC是通过set赋值还是直接查找成员变量赋值,也就是KVC内部实现有调用KVO方法的逻辑,不是通过调用KVO派生子类的set方法实现的,应该是在setValue:forKey前后调用了willchangevalue和didchangevalue

KVO的原理:

1、注册KVO的类在运行时自动生成一个子类即派生类,派生会被缓存下来,将实例对象的isa指向派生子类,并重写set/class/delloc,添加isKVO方法

set方法调用fundation框架的_NSSetXXXValueAndNotify函数,函数内部调用willChangeValueForKey、父类原来的setter、didChange…方法,向监听者发消息调用代理方法

2、存储:这些数据存储在全局的嵌套hashMap+数组中,外层以被观察者为key,内层以被观察属性keyPath为key,数组里的元素存储观察者obsver和option等信息

3、KVO是线程同步的,回调函数执行与KVO发生在同一线程

https://www.jianshu.com/p/56baca325824


手动触发KVO:

automaticallyNotifiesObserversForName函数默认返回YES自动触发KVO,可以重写该函数返回NO,手动触发KVO

手动触发需要成对调用willChangeValueForKey和didChangeValueForKey

KVO 监听数组变化

1、对可变数组属性添加KVO默认数组内容变化不会触发KVO,可以通过mutableArrayValueForKey:获取可变数组再添加元素可以实现监听,如:[[self.selectedsArr mutableArrayValueForKey:@"selecteds"] addObject:]];

2、原理猜测是通过mutableArrayValueForKey函数获取一个重写了add和remove方法的数组,添加了willChange和didChange

KVO异常防护:

KVO异常原因:KVO的注册和移除必须成对出现,否则会出现崩溃,观察者已销毁未移除KVO、多次移除、未注册就移除都会出现崩溃、KVO使用期间keypath销毁、为实现代理方法等等

防护原理:

1、先建立NSObject分类,拦截add、remode、和delloc方法

2、创建一个KVODeleage类充当观察者,将原始的被观察者、观察者、keypath、options、context等信息全部存储在一个字典中,存储结构嵌套哈希表+数组,key是被观察者地址+属性keypath,值为观察者等

3、添加和移除观察者时可以结合字典排重

4、拦截dealloc后可以进行移除kvo避免漏移

https://www.imgeek.org/article/825358174

13、KVC:

键值编码:通过key和keypath存储访问实例变量的方式

KVC的属性赋值是以NSObject分类方式NSObject(NSKeyValueCoding)实现的

分类中方法+ (BOOL)accessInstanceVariablesDirectly;默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

KVC赋值流程:取值流程类似


KVC对容器类也实现了分类,添加了setValue: forKey:方法

如果是字典的话,则修改key对应的value,如果是数组或者集合,则会向每个对象发送此消息,去修改元素的key对应的property

对于字典value为nil时不会crash,对于字典相当于删除key-value对,相当于调用 -removeObjectForKey,调用字典的setObject: forKey:方法会crash

KVC可以触发KVO,不管KVC是通过set赋值还是直接查找成员变量赋值,也就是KVC内部实现有调用KVO方法的逻辑,不是通过调用KVO派生子类的set方法实现的,应该是在setValue:forKey前后调用了willchangevalue和didchangevalue方法

KVC引用场景:

1、解析数据时字典转模型,路由参数解析赋值

2、可视化无痕埋点时附加字段抓取

14、数组

数组:是一种顺序存储的线性表,所有元素的内存地址是连续的

OC数组本质:

NSArray和NSMutableArray底层使用的是隐藏类__NSArrayI、__NSArrayM和__NSSingleObjectArrayI

数据存储在id *list数组指针中,可变数组的list是循环数组,_offset标识起始插入位置

可变数组在超限时自动创建大小为原来二倍的新数组并复制过去

数组遍历方式:

OC中数组有4类:for、for in、枚举器enumerate、dispatch_apply函数等

for in性能最好,基于快速枚举,直接从c数组中取值

枚举器enumerate包含一种子线程遍历,耗时较大的遍历可用

为什么数组遍历删除元素crash

for in删除会Crash,因为底层遍历时会做检查,原数组发生改变就crash

for删除不会crash,因为底层调用n遍objectAtIndex:,for()中做了越界保护

数组遍历删除方案:

遍历数组取出待删除元素,调用removeObjectsInArray:

直接调用for删除会发生元素跳位,删除元素时可配合使用i--

15、NSDictionary和哈希表

NSDictionary底层实现:

1、NSDictionary是对NSMapTable的封装,底层采用哈希表存储,开放定制法解决哈希冲突

2、哈希表本质是一个数组,字典在存值时,key和value都不能为空,会根据key的哈希值经过位运算(取余%)计算得到存储的index

3、key可以是字符串或NSobject类型,但是必须遵守copying协议实现copyWithZone方法,继承NSObject作为key需要重载hash:和isEqual:方法,如不重载默认NSObject的hash方法返回内存地址,isEqual:只是判断内存地址是否相等==

4、NSMapTable是可变的,可以设置对key和value的weak引用,当key和value被释放时自动清除实体,也可以设置添加value时复制,一般用于实现弱引用表如SD内存缓存表

5、KVC通过字典分类添加setValue:forKey:方法进行字典赋值,key必须是字符串类型不能是对象,key不能为nil,value可以为空,为nil时删除key元素

6、NSSet底层封装NSHashTable,NSHashTable同样可以更灵活的定制规则

关于==、isEqual、hash

==基础类型比较值是否相等,对象类型判断两个对象的内存地址是否相等

isEqual:方法NSObject的默认实现是==,即比较内存地址,子类可以重载,可实现更高级别的比较,如地址不同但两个对象属性相同可认为是同一个对象

hash:方法NSObject默认返回对象地址,字典存值时根据哈希值计算位置,为了避免存值的哈希冲突在使用非字符串作为key是尽量重写该方法减少不同哈希冲突,NSString类型的hash函数经过重载已经不是简单的内存地址,不容易出现哈希冲突建议使用字符串作为key。https://juejin.cn/post/6844903717578211341


哈希表(hash table,也叫散列表)

1、哈希表根据键key直接访问Value的数据结构,哈希表的key和value封装成节点,实际存储在链表节点(拉链法)或数组中(开发定址法)

2、哈希函数:字符串key经过hash方法得到一个整数值x,根据除留余数法,将x值余哈希表大小M能得到最初的index,由于不同的key会得到相同的index所以需要处理哈希冲突

3、哈希表扩容2倍?(数组扩容一般1.5倍)

哈希表存值时根据key的哈希值对数组进行取余XXX%9,可转换成进行&运算XXX&(9-1),例如原始长度是16,扩容后的长度是32,这样做是为了&运算的结果尽量保持一致,数组扩容是2的n次幂时就可以尽量避免hash冲突的发生。https://blog.csdn.net/pk_sir/article/details/107858439


哈希冲突:

1、拉链法:数组 + 链表,最终的数据存储在链表节点中

字典的key通过哈希函数得到数组index,数组的每一个元素指向一个链表,链表的节点存储字典的key和value,出现哈希冲突时使用链表连接所有节点

链表过长时会转成红黑树存储


2、开放定址线性探测法:使用多个数组完成,最终的数据存储在数组中

数组keys存储所有key,values存储所有值,得到index即可访问数据

创建一个处理哈希冲突的数组存index,当哈希函数得到相同的index时直接将下标索引加一


哈希冲突方案对比:

拉链法链表动态申请,适合节点不固定情况,链表方便删除节点,但拉链法的内存使用较多,拉链法处理冲突简单不会产生堆积问题,适用于数据量大的

开发定制法删除节点时只能标记删除,不能真删,容易产生堆积问题,适用于数据量小或临时缓存表等如__weak表

iOS中常用哈希表的地方


16、SDWebimage

SD通过OperationQueue子线程下载任务、内存+磁盘双重缓存、子线程解码

下载并发数是6,超时15秒,缓存过期默认一周,50M

SD的缓存:

1、SD请求图片前先根据url的md5分别请求内存缓存和磁盘缓存,都没有再去下载

2、内存缓存:使用NSCache的子类存uiimage+弱引用表NSMapTable管理UIImage

使用若引用表获取图片的好处是当cache被清除时image还可能被页面强持有,仍可以通过内存访问,避免不必要的磁盘访问

3、磁盘缓存:使用url的md5拼接路径存储编码后的data

4、缓存清理:内存警告时内存缓存全部清除,App进入后台和退出时先清理磁盘过期图片,然后判断缓存是否超限如仍超限折半清除磁盘

NSCache做缓存比NSDictionary区别:

1、资源耗尽时自动删减,2、收到低内存告警时先删除最久未使用内存,3、多线程安全,4、可设置大小,5、默认不拷贝键key

url未变,但图片资源变化如何及时更新:

1、SD设置Option忽略缓存直接下载

2、设置HTTP1.1的If-Modified判断客户端缓存与服务器资源是否更新,如缓存是最新的HTTP会返回304,否则正常下载返回200

子线程解码:

JPEG/PNG不是位图,是经过编码压缩后的数据,需要将其解码成位图才能渲染到屏幕上,磁盘缓存存的是编码压缩的PNG图片

通常使用的imageNamed:默认会在主线程解码,消耗CPU,大量使用会卡顿,

imageNamed:有内存缓存,加载大图时可直接使用imageWithContentsOfFile:

SD的子线程解码步骤:在子线程创建绘图上下文,绘图,成图

17、CocoaPods原理:

CocoaPods实现原理:

1、使用Ruby编写的iOS库管理工具,包含若干个gems包,命令解析器、脚本解析器、下载器、工程集成器、pods插件管理器等

2、提供依赖库版本管理,下载,pod工程创建,workspace集成,预编译等功能

3、有三个主要目录:索引库、缓存库、每个工程的pods代码库

pod install和pod update区别:

1、install只对podfile中不符合.lock中条件的pod库更新,update会忽略.lock直接更新

2、这两个命令默认都会自动更新索引库repo,可以使用--no-repo-update忽略更新

pod install执行流程:

1、命令解析器解析pod命令,2、检查更新索引库,3、脚本解析器分析Podfile和.lock生成待下载列表,4、下载器下载pod库,5、集成器生成Pods工程集成进workspace,6、保存依赖到.lock,7、执行pod插件

Pod插件编译优化:

利用pod插件在pod install过程将库编译成二进制静态库,Xcode编译时只需要链接静态即可

18、Git原理:

Git是一个分布式版本控制系统,每个开发者本地保存一个完整的文件版本镜像,可以离线版本管理,然后再同步到中心版本库

git分4个区:本地工作区,本地暂存区,本地仓库,远程仓库

.git文件用于版本跟踪,存储本地仓库和暂存区,包含各文件版本的快照、分支、log等

Git底层实现:git是一套内容寻址的文件系统

git管理的内容经过加密算法生成三种对象

1、blob对象只跟文本文件的内容;2、tree对象记录文本文件内容和名称、目录等信息;3、commit对象记录本次提交的所有信息,包括提交人、提交时间,本次提交包含的tree及blob

代码回滚:如A之后提交了B

1、B未push到远端:git reset A

2、push到远端:1、反转提交Bgit revert B,2、git reset —hard A 然后git reset B 然后commit加push,3、切分支处理

合并代码:

1、git merge 合并分支,把一个分支合并进当前的分支,产生合并提交

2、git rebase 打补丁提交,先保存提交代码,拉取新分支内容,然后提交代码补丁,不会产生合并提交

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

推荐阅读更多精彩内容