Key-Value Observing (键值监测)

简介

KVO是一套当目标对象的属性值改变时观察者对象能够接受到通知的机制。必须先理解KVC才能更好的理解KVO,前者是后者的实现基础。
这样的通信机制在MVC设计模式很是常见


14728034947627.png

实现过程简单来说分为3步:
1、添加观察这和监测对象
2、监测对象改变
3、收到值改变通知,处理后续逻辑
举个生活中的例子就是给银行卡开通短信通知的业务,总体也是分3步“
1、去银行办理短信业务
2、账号财产变动
3、收到短信通知
KVO是框架级别的服务,无需自己发送通知,使用方便,基本不需要添加额外代码即可使用。

详情

为了使用KVO,必须满足以下3步

1、目标对象的属性,必须支持KVO

2、注册观察者与被观察者addObserver:forKeyPath:options:context:

3、观察者必须实现observeValueForKeyPath:ofObject:change:context:方法

第一步、确保目标支持KVO

被监测的目标对象的属性支持KVO必须满足以下条件:

1、目标对象的属性必须支持KVC,对于1对1属性简单来说就是实现set和get方法。详情和1对多请阅读官方说明。系统已有类及子类自动支持,放心使用。

2、自动和手动属性通知
目标对象必须能发出属性变化通知。系统默认支持,也可自定义。
系统默认支持,且支持的很好,一般无需自定义。

//如果需要自定义,需要重新此方法,默认返回YES
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;

//在set方法中手动调用,变化类型只是针对NSKeyValueChangeSetting
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

例如

//假设有属性
@property (nonatomic,copy)NSString *name;


+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    
    BOOL automatic = NO;
    /* 只自定义指定属性,其它仍然自动发送通知 */
    if ([theKey isEqualToString:@"name"])
    {
        //在set方法中手动调用相关方法
        automatic = NO;
    }
    else
    {
        //此方法默认返回YES
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

- (void)setName:(NSString *)name
{
    //即将变化
    [self willChangeValueForKey:@"name"];
    
    _name = name;
    
    //已经变化
    [self didChangeValueForKey:@"name"];
}

//如果说只有值不相等时才发送通知,提升性能
- (void)setName:(NSString *)name
{
    if (![name isEqualToString:_name])
    {
    
        [self willChangeValueForKey:@"name"];
        
        _name = name;
        
        [self didChangeValueForKey:@"name"];
    }
}

如果涉及1对多的容器类,需要自己实现 NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement三种操作对应的方法,例如

//Keys为属性名称
- (void)removeKeysAtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"keys"];
 
    // Remove the transaction objects at the specified indexes.
 
    [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"keys"];
}

3、属性依赖
如果目标对象属性存在依赖关系,注册合适的依赖Keys。核心方法为

第一种、
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key NS_AVAILABLE(10_5, 2_0);
说明:
1、返回目标属性依赖属性的KeyPath的Set。当对象注册后,KVO自动监测该对象所有的KeyPaths。
2、其默认实现从对象所属类的方法列表中匹配方法:+keyPathsForValuesAffecting<Key>(<Key>为属性名,比如Name),如果存在执行并返回结果;如果不存在向底层寻找已经废弃的方法+setKeys:triggerChangeNotificationsForDependentKey:
3、可以用来替换手动调用-willChangeValueForKey:/-didChangeValueForKey:来实现属性依赖的解决方案
4、不能在已有类的Category中使用,在Category禁止重写此方法,可以使用+keyPathsForValuesAffecting<Key>来实现。

第二种、
或者重写此格式+keyPathsForValuesAffecting<Key>(<Key>为属性名,比如Name)的方法名

比如说,姓名=姓+名;当二者任一变动时更新姓名

@property (nonatomic,copy)NSString *name;//姓名
@property (nonatomic,copy)NSString *firstName;//姓
@property (nonatomic,copy)NSString *lastName;//名

//重新get方法,表明字段组成
-(NSString *)name
{
    return [NSString stringWithFormat:@"%@%@",_firstName,_lastName];
}

//通过此方法
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key 
 {
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    
    if ([key isEqualToString:@"name"])
    {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
或者
+ (NSSet *)keyPathsForValuesAffectingName
{
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

//改变值
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setValue:@"张" forKey:@"firstName"];
    [self setValue:@"三" forKey:@"lastName"];

    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
    //名称改变,刷新姓名
    [self setValue:@"龙" forKey:@"lastName"];


}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"NSKeyValueChangeOldKey:%@",change[NSKeyValueChangeOldKey]);
    NSLog(@"NSKeyValueChangeNewKey:%@",change[NSKeyValueChangeNewKey]);
}

//输出结果
2016-09-06 17:05:01.904 KVC[4192:307124] NSKeyValueChangeOldKey:张三
2016-09-06 17:05:01.904 KVC[4192:307124] NSKeyValueChangeNewKey:张龙

注意:以上关于属性依赖的处理方法不支持一对多的关系。比如说ViewController对象有一个totalNumber表示总数和属性datas数组,数组中Data的对象,对象含有number属性。

/* Data对象 */
@interface Data : NSObject

@property (nonatomic,assign)NSInteger number;

@end


/* ViewController对象 */
@interface ViewController ()

@property (nonatomic,assign)NSInteger totalNumber;

@property (nonatomic,strong)NSArray *datas;

@end

可以采用以下方法解决
1、注册每个Data对象的年龄属性为监测属性,ViewController对象为观察者,data.number变化时,使用集合运算符求和,然后设置ViewController的totalNumber属性值

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /* 创建Data对象 */
    Data * data1 = [[Data alloc] init];
    data1.number = 0;
    
    Data *data2 = [[Data alloc] init];
    data2.number = 0;
    
    Data *data3 = [[Data alloc] init];
    data3.number = 0;
    
    /* self.datas属性赋值 */
    [self setValue:@[data1,data2,data3] forKey:@"datas"];

    /* 监测self.datas中每个data对象的number属性 */
    //(0, 3) 中0表示index从0开始,0表示长度3,也就是index(0、1、2);但是不能使得self.datas数组越界
    NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 3)];
    
    [self.datas addObserver:self toObjectsAtIndexes:set forKeyPath:@"number" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    
    /* 监测totalNumber属性 */
    [self addObserver:self forKeyPath:@"totalNumber" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

    /* 改变值,查看输出结果 */
    [data1 setValue:@"1" forKey:@"number"];
    [data2 setValue:@"1" forKey:@"number"];
    [data3 setValue:@"1" forKey:@"number"];

}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"number"])
    {
        [self updateTotalNumber];
    }
    else if ([keyPath isEqualToString:@"totalNumber"])
    {
        NSLog(@"%@--%@",keyPath,change);
    }
}

//更新总数
- (void)updateTotalNumber
{
    
    NSNumber *total = [self valueForKeyPath:@"datas.@sum.number"];
    
    [self setValue:total forKey:@"totalNumber"];
}

//输出结果
2016-09-07 14:10:10.694 KVC[3034:165515] totalNumber--{
    kind = 1;
    new = 1;
    old = 0;
}
2016-09-07 14:10:10.695 KVC[3034:165515] totalNumber--{
    kind = 1;
    new = 2;
    old = 1;
}
2016-09-07 14:10:10.695 KVC[3034:165515] totalNumber--{
    kind = 1;
    new = 3;
    old = 2;
}

以上代码中不涉及移除,根据需要添加代码,对象delloc之前,必须移除。
2、Core Data,自动实现类似的功能。

第二步、注册

1、注册通知
为了能够获取目标属性值改变的通知,需要注册观察者和观察对象属性

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
参数说明:
observer:观察者对象,就是想收到变动通知的对象
keyPath:监测的目标属性的路径
options:决定了通知中内容和发送时间
context:C指针或者对象,传递参数,一般不用传NULL

例如在新建的工程的ViewController中

@property (nonatomic,copy)NSString *name;

- (void)registerAsObserver
{

    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

}

注意:此方法不持有观察者对象、被观察对象、context,管理好其生命周期。

2、接受通知
当监测的目标对象的属性变化时,观察者将调用observeValueForKeyPath:ofObject:change:context: message,所有的观察者都必须实现此方法

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
keyPath:监测的目标属性的路径
object:观察者对象
change:变化内容
context:C指针或者对象,传递参数,一般不用传NULL

3、移除通知
当不再使用时,需要通过以下方法移除通知。

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

keyPath:监测的目标属性的路径
observer:观察者对象
context:C指针或者对象,传递参数,一般不用传NULL
以上两个方法,根据需要选择使用。

特别注意:NSArray、NSOrderedSet、NSSet不支持以上三个方法,调用会抛出异常。

第三步、属性变化

使用KVC方法,或者能够触发KVC方法使得监测的目标对象属性变化。

第四步、接收变化

当监测的目标对象的属性变化时,观察者将调用observeValueForKeyPath:ofObject:change:context: message,所有的观察者都必须实现此方法。在此方法中处理变化

以上第二、三、四步组成一次完整的KVO使用过程,下边关于一些参数的用法说明

参数说明

关于NSKeyValueObservingOptions

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
options:决定了通知中内容和发送时间

NSKeyValueObservingOptions是一个枚举类型

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {

    /*通知dic中是否包含新值*/
    NSKeyValueObservingOptionNew = 0x01,
    
    /*通知dic中是否包含新值*/
    NSKeyValueObservingOptionOld = 0x02,

    /*添加此操作,通知dic中是否包含注册通知前的初始值;如果目标属性是容器类,每个元素都会触发通知发送*/
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,

    /*添加此操作,每次值变化,将触发两次:1、变化前(dic中包含NSKeyValueChangeNotificationIsPriorKey,值为1,NSNumber类型) 
    2、变化后
    */
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};

关于Change Dictionary

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
change:变化内容

其包含以下几种内容,可以使用以下字段取值

//值变化类型
FOUNDATION_EXPORT NSString *const NSKeyValueChangeKindKey;

//新值
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNewKey;

//旧值
FOUNDATION_EXPORT NSString *const NSKeyValueChangeOldKey;

//容器类中,变化值所在位置,NSIndexSet类型
FOUNDATION_EXPORT NSString *const NSKeyValueChangeIndexesKey;

//是否值变化前,NSNumber类型
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNotificationIsPriorKey NS_AVAILABLE(10_5, 2_0);

其中NSKeyValueChangeKindKey有以下几种类型

typedef NS_ENUM(NSUInteger, NSKeyValueChange) 
{
    NSKeyValueChangeSetting = 1,//值变化
    NSKeyValueChangeInsertion = 2,//插入
    NSKeyValueChangeRemoval = 3,//移除
    NSKeyValueChangeReplacement = 4,//替换
};

上边的NSKeyValueChangeKindKey2、3、4分别对应着有序集合比如NSArray中增、删、改操作。

参数说明-代码示例

1、NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld

@property (nonatomic,copy)NSString *name;

//注册
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setValue:@"zwq" forKey:@"name"];

    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

   [self setValue:@"zwq2" forKey:@"name"];
    
}

//接收
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"NSKeyValueChangeKindKey:%@",change[NSKeyValueChangeKindKey]);
    NSLog(@"NSKeyValueChangeOldKey:%@",change[NSKeyValueChangeOldKey]);
    NSLog(@"NSKeyValueChangeNewKey:%@",change[NSKeyValueChangeNewKey]);
    NSLog(@"change dic:%@",change);
}

//输出结果
2016-09-06 14:58:55.349 KVC[3614:231751] NSKeyValueChangeKindKey:1
2016-09-06 14:58:55.351 KVC[3614:231751] NSKeyValueChangeOldKey:zwq
2016-09-06 14:58:55.351 KVC[3614:231751] NSKeyValueChangeNewKey:zwq2
2016-09-06 14:58:55.352 KVC[3614:231751] change dic:{
    kind = 1;
    new = zwq2;
    old = zwq;
}

从以上代码可以看出change中的key如何取值,以及其中内容。

2、NSKeyValueObservingOptionInitial

@property (nonatomic,copy)NSString *name;

//注册
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setValue:@"zwq" forKey:@"name"];

    //为了明显观察值变化,多添加两个key
    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

    
    [self setValue:@"zwq2" forKey:@"name"];

}

//接收
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"NSKeyValueChangeKindKey:%@",change[NSKeyValueChangeKindKey]);
    NSLog(@"NSKeyValueChangeOldKey:%@",change[NSKeyValueChangeOldKey]);
    NSLog(@"NSKeyValueChangeNewKey:%@",change[NSKeyValueChangeNewKey]);
    NSLog(@"change dic:%@",change);
}

//输出结果
2016-09-06 15:06:49.963 KVC[3654:237675] NSKeyValueChangeKindKey:1
2016-09-06 15:06:49.964 KVC[3654:237675] NSKeyValueChangeOldKey:(null)
2016-09-06 15:06:49.964 KVC[3654:237675] NSKeyValueChangeNewKey:zwq
2016-09-06 15:06:49.964 KVC[3654:237675] change dic:{
    kind = 1;
    new = zwq;
}

2016-09-06 15:06:49.964 KVC[3654:237675] NSKeyValueChangeKindKey:1
2016-09-06 15:06:49.965 KVC[3654:237675] NSKeyValueChangeOldKey:zwq
2016-09-06 15:06:49.965 KVC[3654:237675] NSKeyValueChangeNewKey:zwq2
2016-09-06 15:06:49.965 KVC[3654:237675] change dic:{
    kind = 1;
    new = zwq2;
    old = zwq;
}

对比1、2的输出结果,可以看出2中可以获得初始值,后续值变化接收到的通知dic内容同1中一样(可以多改变几次赋值,观察结果)

3、NSKeyValueObservingOptionPrior

@property (nonatomic,copy)NSString *name;

//注册
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setValue:@"zwq" forKey:@"name"];

    //为了明显观察值变化,多添加两个key
    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionPrior|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];

    
    [self setValue:@"zwq2" forKey:@"name"];
    [self setValue:@"zwq3" forKey:@"name"];

}

//接收
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"NSKeyValueChangeKindKey:%@",change[NSKeyValueChangeKindKey]);
    NSLog(@"NSKeyValueChangeOldKey:%@",change[NSKeyValueChangeOldKey]);
    NSLog(@"NSKeyValueChangeNewKey:%@",change[NSKeyValueChangeNewKey]);
    NSLog(@"NSKeyValueChangeNotificationIsPriorKey:%@",change[NSKeyValueChangeNotificationIsPriorKey]);
    NSLog(@"change dic:%@",change);
}

//输出结果
2016-09-06 15:17:16.325 KVC[3730:246941] NSKeyValueChangeKindKey:1
2016-09-06 15:17:16.326 KVC[3730:246941] NSKeyValueChangeOldKey:zwq
2016-09-06 15:17:16.326 KVC[3730:246941] NSKeyValueChangeNewKey:(null)
2016-09-06 15:17:16.326 KVC[3730:246941] NSKeyValueChangeNotificationIsPriorKey:1
2016-09-06 15:17:16.327 KVC[3730:246941] change dic:{
    kind = 1;
    notificationIsPrior = 1;
    old = zwq;
}
2016-09-06 15:17:16.327 KVC[3730:246941] NSKeyValueChangeKindKey:1
2016-09-06 15:17:16.327 KVC[3730:246941] NSKeyValueChangeOldKey:zwq
2016-09-06 15:17:16.327 KVC[3730:246941] NSKeyValueChangeNewKey:zwq2
2016-09-06 15:17:16.327 KVC[3730:246941] NSKeyValueChangeNotificationIsPriorKey:(null)
2016-09-06 15:17:16.328 KVC[3730:246941] change dic:{
    kind = 1;
    new = zwq2;
    old = zwq;
}


2016-09-06 15:17:16.328 KVC[3730:246941] NSKeyValueChangeKindKey:1
2016-09-06 15:17:16.328 KVC[3730:246941] NSKeyValueChangeOldKey:zwq2
2016-09-06 15:17:16.328 KVC[3730:246941] NSKeyValueChangeNewKey:(null)
2016-09-06 15:17:16.328 KVC[3730:246941] NSKeyValueChangeNotificationIsPriorKey:1
2016-09-06 15:17:16.328 KVC[3730:246941] change dic:{
    kind = 1;
    notificationIsPrior = 1;
    old = zwq2;
}
2016-09-06 15:17:16.328 KVC[3730:246941] NSKeyValueChangeKindKey:1
2016-09-06 15:17:16.329 KVC[3730:246941] NSKeyValueChangeOldKey:zwq2
2016-09-06 15:17:16.329 KVC[3730:246941] NSKeyValueChangeNewKey:zwq3
2016-09-06 15:17:16.329 KVC[3730:246941] NSKeyValueChangeNotificationIsPriorKey:(null)
2016-09-06 15:17:16.329 KVC[3730:246941] change dic:{
    kind = 1;
    new = zwq3;
    old = zwq2;
}

从以上代码输出结果不难看出:a、通知分开发送:变化前和变化后 b、变化前NSKeyValueChangeNewKey为空,但是NSKeyValueChangeNotificationIsPriorKey值为1;变化后反之。

结合以上3段代码结论:4种各有所用,可以单独使用,也可以组合使用,根据需要选择合适。作用简单概括:1.新、旧值 2.初始值 3.值变化前后。

4、NSKeyValueChangeKindKey

@interface ViewController ()

@property (nonatomic,strong)NSArray *datas;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
      
    /* 创建Data对象 */
    Data * data1 = [[Data alloc] init];
    Data *data2 = [[Data alloc] init];
    Data *data3 = [[Data alloc] init];
    Data *data4 = [[Data alloc] init];

    
    /* self.datas属性赋值 */
    [self setValue:@[data1,data2,data3] forKey:@"datas"];
    
    /* 监测self.datas属性 */
    [self addObserver:self forKeyPath:@"datas" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    

    //修改datas数组:增删改
    NSMutableArray *mutable_subdatas = [self mutableArrayValueForKeyPath:@"datas"];
    [mutable_subdatas addObject:data4];
    [mutable_subdatas removeObject:data4];
    [mutable_subdatas replaceObjectAtIndex:2 withObject:data4];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"%@--%@",keyPath,change);
}

//输出结果
2016-09-07 14:46:50.366 KVC[3231:186568] datas--{
    indexes = "<_NSCachedIndexSet: 0x7f8591428c60>[number of indexes: 1 (in 1 ranges), indexes: (3)]";
    kind = 2;
    new =     (
        "<Data: 0x7f8591427b00>"
    );
}


2016-09-07 14:46:50.367 KVC[3231:186568] datas--{
    indexes = "<_NSCachedIndexSet: 0x7f8591428c60>[number of indexes: 1 (in 1 ranges), indexes: (3)]";
    kind = 3;
    old =     (
        "<Data: 0x7f8591427b00>"
    );
}


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

推荐阅读更多精彩内容