1、关于ReactiveObjC使用简介

本文是对ReactiveObjC部分使用介绍,原理及流程简介,见文章结尾

目录:
1、简单使用
2、UIKit (基于UIView控件)
3、Foundation (Foundation对象)
4、KVO (关于监听)
5、事件信号
6、结合网络请求使用

一、简单使用

  • RACSignal 信号相当于一个电视塔 ,只要将电视机调到跟电视塔的赫兹相同的频道,就可以收到信息。
  • subscribeNext 相当于订阅频道。当RACSignal信号发出sendNext消息时,subscribeNext就可以接收到信息。
//1、创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    //任何时候,都可以发送信号,可以异步
    [subscriber sendNext:@"发送信号"];
    //数据传递完,最好调用sendCompleted,这时命令才执行完毕。
    [subscriber sendCompleted];
    return nil;
}];
    
//2、订阅信号
RACDisposable *disposable = [signal subscribeNext:^(id  _Nullable x) {
     //收到信号时
     NSLog(@"信号内容:%@", x);
}];
    
//取消订阅
[disposable dispose];

1、调用 createSignal 创建一个信号

二、UIKit (基于UIView控件)

1、rac_textSignal 文本监听信号,可以减少对代理方法的依赖

//UITextField创建了一个 `textSignal`的信号,并订阅了该信号
//当UITextField的内容发生改变时,就会回调subscribeNext
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
     NSLog(@"text changed = %@", x);
}];

2、filter 对订阅的信号进行筛选

//当UITextField内输入的内容长度大于5时,才会回调subscribeNext
[[[self.textField rac_textSignal] filter:^BOOL(NSString * _Nullable value) {
      return value.length > 5;
}] subscribeNext:^(NSString * _Nullable x) {
      NSLog(@"filter result = %@",  x);
}];

3、ignore 对订阅的信号进行过滤

[[[self.textField rac_textSignal] ignore:@"666"] subscribeNext:^(NSString * _Nullable x) {
    //当输入的内容 equalTo @"666" 时,这里不执行
    //其他内容,均会执行subscribeNext
    NSLog(@"ignore = %@", x);
}];

4、rac_signalForControlEvents 创建事件监听信号

//当UIButton点击时,会调用subscribeNext
[[self.button rac_signalForControlEvents:(UIControlEventTouchUpInside)] subscribeNext:^(__kindof UIControl * _Nullable x) {
    NSLog(@"button clicked");
}];

三、Foundation (Foundation对象)

1、NSNotificationCenter 通知

//@property (nonatomic, strong) RACDisposable *keyboardDisposable;
self.keyboardDisposable = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil]  subscribeNext:^(NSNotification * _Nullable x) {
    NSLog(@"%@ 键盘弹起", x); // x 是通知对象
}];

注意:rac_addObserverForName同样需要移除监听。RAC通知监听会返回一个RACDisposable清洁工的对象,在dealloc中销毁信号,信号销毁时,RAC在销毁的block中移除了监听

- (void)dealloc {
    [_keyboardDisposable dispose];
}

2、 interval定时器 (程序进入后台,再重新进入前台时,仍然有效,内部是用GCD实现的)

//创建一个定时器,间隔1s,在主线程中运行
RACSignal *timerSignal = [RACSignal interval:1.0f onScheduler:[RACScheduler mainThreadScheduler]];
//定时器总时间3秒
timerSignal = [timerSignal take:3];
//定义一个倒计时的NSInteger变量
self.counter = 3;
@weakify(self)
[timerSignal subscribeNext:^(id  _Nullable x) {
    @strongify(self)
    self.counter--;
    NSLog(@"count = %ld", (long)self.counter);
} completed:^{
    //计时完成
    NSLog(@"Timer completed");
}];

3、delay延迟

//创建一个信号,2秒后订阅者收到消息
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    return nil;
}] delay:2] subscribeNext:^(id  _Nullable x) {
    NSLog(@"delay : %@", x);
}];

4、NSArray 数组遍历

NSArray *array = @[@"1", @"2", @"3", @"4", @"5"];
[array.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"数组内容:%@", x);
}];

5、NSDictionary字典遍历

NSDictionary *dictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"};
[dictionary.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
    // x 是一个元祖,这个宏能够将 key 和 value 拆开   乱序
    RACTupleUnpack(NSString *key, NSString *value) = x;
    NSLog(@"字典内容:%@ : %@", key, value);
}];

6、RACSubject代理
定义一个DelegateView视图,并且声明一个RACSubject的信号属性,在touchesBegan方法中,给信号发送消息

@interface DelegateView : UIView
//定义了一个RACSubject信号
@property (nonatomic, strong) RACSubject *delegateSignal;
@end

@implementation DelegateView

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 判断代理信号是否有值
    if (self.delegateSignal) {
        // 有值,给信号发送消息
        [self.delegateSignal sendNext:@666];
    }
}
@end

在UIViewController中声明DelegateView作为属性

@interface ViewController ()
@property (nonatomic, strong) DelegateView *bView;
@end

//使用前,记得初始化
self.bView.delegateSignal = [RACSubject subject];
[self.bView.delegateSignal subscribeNext:^(id  _Nullable x) {
    //订阅到 666 的消息
    NSLog(@"RACSubject result = %@", x);
}];

四、KVO (关于监听)

1、rac_valuesForKeyPath 通过keyPath监听

[[self.bView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id  _Nullable x) {
    //当self.bView的frame变化时,会收到消息
    NSLog(@"kvo = %@", x);
}];

2、RACObserve 属性监听

//counter是一个NSInteger类型的属性
[[RACObserve(self, counter) filter:^BOOL(id  _Nullable value) {
        return [value integerValue] >= 2;
}] subscribeNext:^(id  _Nullable x) {
        NSLog(@"RACObserve : value = %@", x);
}];

在进行监听时,同样可以使用filter信号,对值进行筛选
3、RAC 事件绑定

//当UITextField输入的内容为@"666"时,bView视图的背景颜色变为grayColor
RAC(self.bView, backgroundColor) = [self.textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
    return [value isEqualToString:@"666"]?[UIColor grayColor]:[UIColor orangeColor];
}];

#define RAC(TARGET, ...)这个宏定义是将对象的属性变化信号与其他信号关联,比如:登录时,当手机号码输入框的文本内容长度为11位时,"发送验证码" 的按钮才可以点击

五、事件信号

名词 描述 说明
RACTuple 元祖 只能存储OC对象 可以用于解包或者存储对象
bind 包装 获取到信号返回的值,包装成新值,
再次通过信号返回给订阅者
concat 合并 按一定顺序拼接信号,当多个信号发出的时候,
有顺序的接收信号
then 下一个 用于连接两个信号,当第一个信号完成,
才会连接then返回的信号
merge 合并 把多个信号合并为一个信号,
任何一个信号有新值的时候就会调用
zipWith 压缩 把两个信号压缩成一个信号,
只有当两个信号都发出一次信号内容后,
并且把两个信号的内容合并成一个元组,
才会触发压缩流的next事件(组合的数据都是一一对应的)
combineLatest 结合 将多个信号合并起来,并且拿到各个信号的最新的值,
必须每个合并的signal至少都有过一次sendNext,
才会触发合并的信号
(combineLatest 与 zipWith不同的是,每次只拿各个信号最新的值)
reduce 聚合 用于信号发出的内容是元组,
把信号发出元组的值聚合成一个值,
一般都是先组合在聚合
map 数据筛选 map 的底层实现是通过 flattenMap 实现的
flattenMap 信号筛选 flattenMap 的底层实现是通过bind实现的
filter 过滤 过滤信号,获取满足条件的信号

ps:表格排版加上<br>换行之后,才不至于列的内容挤到一起,累累累...

1、RACTuple 元祖

只能存储OC对象 可以用于解包或者存储对象

//解包数据
RACTupleUnpack(NSNumber *a, NSNumber *b) = x;

2、bind 包装

获取到信号返回的值,包装成新值, 再次通过信号返回给订阅者

[[self.textField.rac_textSignal bind:^RACSignalBindBlock _Nonnull{ return ^RACSignal*(id value, BOOL *stop){
    // 处理完成之后,包装成信号返回出去
    return [RACSignal return:[NSString stringWithFormat:@"hello: %@",value]];
    };
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"bind : %@",x); // hello: "x"
}];

3、concat 合并

按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号

RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signalA"];
    [subscriber sendCompleted];
    return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"signalB"];
    [subscriber sendCompleted];
    return nil;
}];
// 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活 顺序执行
[[signalA concat:signalB] subscribeNext:^(id  _Nullable x) {
    //先拿到 signalA 的结果 , 再拿到 signalB 的结果 , 执行两次
    NSLog(@"concat result = %@", x);
}];

4、then 下一个

用于连接两个信号,当第一个信号完成,才会连接then返回的信号

// 底层实现  1.使用concat连接then返回的信号  2.先过滤掉之前的信号发出的值
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendCompleted];
    return nil;
}] then:^RACSignal *{
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        //可以对第一个信号的数据进行过滤处理 , 不能直接获得第一个信号的数据返回值
        [subscriber sendNext:@2];
        return nil;
    }];
}] subscribeNext:^(id x) {
    // 只能接收到第二个信号的值,也就是then返回信号的值
    NSLog(@"then : %@",x); // 2
}];

5、merge 合并

把多个信号合并为一个信号,任何一个信号有新值的时候就会调用

//创建多个信号
RACSignal *mergeSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    return nil;
}];
RACSignal *mergeSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@2];
    return nil;
}];
// 合并信号,只要有信号发送数据,都能监听到.
RACSignal *mergeSignal = [mergeSignalA merge:mergeSignalB];
    
[mergeSignal subscribeNext:^(id x) {
    //每次获取单个信号的值
    NSLog(@"merge : %@",x);
}];

6、zipWith 压缩

把两个信号压缩成一个信号,只有当两个信号都发出一次信号内容后,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件(组合的数据都是一一对应的)

RACSignal *zipSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendNext:@2];
    return nil;
}];
RACSignal *zipSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    //3秒后执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subscriber sendNext:@3];
    });
    //5秒后执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subscriber sendNext:@5];
    });
            
    return nil;
}];
    
RACSignal *zipSignal = [zipSignalA zipWith:zipSignalB];
    
[zipSignal subscribeNext:^(id  _Nullable x) {
    // x 是一个元祖
    RACTupleUnpack(NSNumber *a, NSNumber *b) = x;
    NSLog(@"zip with : %@   %@", a, b);
    //第一次输出   1  3
    //第二次输出   2  5
}];

7、combineLatest 结合
将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号 (combineLatest 与 zipWith不同的是,每次只拿各个信号最新的值)

RACSignal *combineSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendNext:@2];
    return nil;
}];
    
RACSignal *combineSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subscriber sendNext:@3];
    });
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [subscriber sendNext:@5];
    });
            
    return nil;
}];
    
RACSignal *combineSignal = [combineSignalA combineLatestWith:combineSignalB];
    
[combineSignal subscribeNext:^(id  _Nullable x) {
    // x 是一个元祖
    RACTupleUnpack(NSNumber *a, NSNumber *b) = x;
    NSLog(@"combineLatest : %@   %@", a, b);
    //第一次输出 2 3
    //第二次输出 2 5
    //因为combineSignalA中的2是最新数据,所以,combineSignalA每次获取到的都是2
}];

8、reduce 聚合

用于信号发出的内容是元组,把信号发出元组的值聚合成一个值,一般都是先组合在聚合

RACSignal *reduceSignalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    return nil;
}];
    
RACSignal *reduceSignalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@3];
    return nil;
}];
    
RACSignal *reduceSignal = [RACSignal combineLatest:@[reduceSignalA, reduceSignalB] reduce:^id(NSNumber *a, NSNumber *b) { 
    //reduce中主要是对返回数据的处理  
    return [NSString stringWithFormat:@"%@ - %@", a, b];
}];
    
[reduceSignal subscribeNext:^(id  _Nullable x) {
    //返回值x 取决于reduce之后的返回
    NSLog(@"reduce : %@", x);
}];

9、map 数据过滤

map 的底层实现是通过flattenMap 实现的。map 直接对数据进行处理,并且返回处理后的数据

[[self.textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
    // 当源信号发出,就会调用这个block,修改源信号的内容
    // 返回值:就是处理完源信号的内容。
    return [NSString stringWithFormat:@"hello : %@",value];
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"Map : %@",x); // hello: "x"
}];

10、flattenMap 信号过滤

flattenMap 的底层实现是通过bind实现的。拿到原数据,处理完成之后,包装成信号返回

[[self.textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
    return  [RACSignal return:[NSString stringWithFormat:@"hello : %@", value]];
}] subscribeNext:^(id  _Nullable x) {
    NSLog(@"flattenMap : %@", x); // hello "x"
}];

10、filter 过滤

过滤信号,获取满足条件的信号

[[self.textField.rac_textSignal filter:^BOOL(NSString *value) {
    return value.length > 6;
}] subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"filter : %@", x); // x 值位数大于6
}];

六、结合网络请求使用

以下网络接口均基于MVVM模式

1、请求单个接口
//创建请求接口的信号,该方法可以定义并实现在ViewModel层
#pragma mark - 获取指定时间的课程
+ (RACSignal *)getCourseInfoByTime:(NSInteger)time {
    //此处为接口所需参数
    NSMutableDictionary *paramter = [NSMutableDictionary dictionary];
    [paramter setObject:@(time) forKey:@"exerciseTime"];
    
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        //这里为接口请求方法 
        //接口url: CombinePath(TY_DEBUG_HOST, SPORT_HOME_COURSE) 
        //postRequest:接口请求类型 post
        //paramter:参数
        //[SportCourseDataModel class]:接口返回类型model
        [Networking requestWithPath:CombinePath(TY_DEBUG_HOST, SPORT_HOME_COURSE) requestType:postRequest requestParamter:paramter responseObjctClass:[SportCourseDataModel class] completionBlock:^(BOOL isSuccess, id object, NSError *error) {
            //当接口返回结果后,根据状态,分别传递object或者error给订阅者
            if (isSuccess) {
                //将接口返回的接口object传递给subscribeNext
                [subscriber sendNext:object];
                //信号完成之后,最好调用sendCompleted
                [subscriber sendCompleted];
            }
            else {
                [subscriber sendError:error];
            }
        }];
        return nil;
    }];
}

//信号订阅
//在ViewController中定义一个方法,用来调用网络接口方法
- (void)getCourseByIsExperience:(BOOL)isExperience {
    //使用 @weakify(self) 和 @strongify(self) 避免循环引用
    @weakify(self)
    [[SportViewModel getCourseInfoByTime:0 isExperience:isExperience] subscribeNext:^(id  _Nullable x) {
        @strongify(self)
        //接口请求成功,订阅者可以在这里获取到接口返回的内容 x
    } error:^(NSError * _Nullable error) {
        @strongify(self)
        //当接口出错时,这里可以处理错误信息
    }];
}

分析:
1、在ViewModel类中,创建了一个信号,这个信号请求了一个获取课程的接口。信号创建之后,并不会立即执行,要等订阅者,订阅并调用subscribeNext时,才会执行。
2、在ViewController中,经过用户操作,开始调用getCourseByIsExperience方法。此时,订阅者开始订阅信号,信号中的createSignal开始执行接口请求方法。
3、当接口请求成功后,根据状态,将对应的object或者error通过sendNext:sendError:传递给订阅者
4、订阅者开始执行subscribeNext 或者 error block中的代码
(ps:如果接口请求之后,不需要获取返回值,则可以在信号中这样返回 [subscriber sendNext:nil]

优点:这个接口请求过程,ViewController只需要将接口所需参数传入,即可得到接口的结果,大大简化了控制器层面的内容,使得控制器更加专注于页面之间的业务处理,数据传递等功能。

2、多个接口的同时调用 (以下的接口信号创建过程,不再描述)
//获取血压收缩的数据 接口信号
RACSignal *systolicSignal = [DataStatisticsViewModel getItemDataByPersonId:personId baseItemId:self.systolicItemModel.baseItemId];
//获取血压舒张压的数据 接口信号
RACSignal *diastolicSignal = [DataStatisticsViewModel getItemDataByPersonId:personId baseItemId:self.diastolicItemModel.baseItemId];
@weakify(self)
//因为两个接口是需要同时获取到数据的,所以可以使用combineLatest组合信号
[[RACSignal combineLatest:@[systolicSignal, diastolicSignal]] subscribeNext:^(RACTuple * _Nullable x) {
    @strongify(self)
    //因为是请求了多个接口,所以会有多个数据返回,此处的x是一个元祖,所以使用RACTupleUnpack解包元祖
    //返回结果值(DataItemRecordModel)的顺序对应combineLatest中数组的信号顺序
    RACTupleUnpack(DataItemRecordModel *systolicModel, DataItemRecordModel *diastolicModel) = x;
    //这里可以直接使用返回值  systolicModel  和  diastolicModel
            
} error:^(NSError * _Nullable error) {
    @strongify(self)
    //没有数据
    [self handleTheErrorMessage:error];
}];

多个接口同时调用的过程同单个接口请求类似。
需注意:
(一)多个接口同时请求时,只要有其中一个返回错误信息,整个结果即为失败,即会走error:^(NSError * _Nullable error){}这个block,所以必须多个接口都成功时,才会调用subscribeNext:^(RACTuple * _Nullable x){}block。
(二)可以对结果先做聚合处理,返回再返回结果,比如:

[[RACSignal combineLatest:@[systolicSignal, diastolicSignal] reduce:^id(DataItemRecordModel *systolicModel, DataItemRecordModel *diastolicModel) {
    //reduce中对数据进行处理,可以将多个接口请求的数据,处理之后,统一返回一个结果
    return systolicModel;
    //也可以将处理完的数据包装成元祖返回
    RACTuple *tuple = RACTuplePack(systolicModel, diastolicModel);
    return tuple;
}] subscribeNext:^(id  _Nullable x) {
    //这里获取到reduce处理完成之后的数据
} error:^(NSError * _Nullable error) {
    //这里处理错误信息
}];
以上为网络请求接口时的例子,更多的使用方式可以结合(5、事件信号)中的各种信号事件😆

结尾

2、关于ReactiveObjC原理及流程简介

本文参考:http://blog.csdn.net/mazy_ma/article/details/77151425

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