LPDMvvmKit分析

  在饿了么蜂鸟团队版项目组工作了有半年时间了,从我刚进来的时候,团队版就已经开始转型为MVVM+RAC的方式来作为底层核心框架,但对于一个iOS开发新人的我来说,以前只是熟悉MVC的开发模式,并且对于RAC从未接触,突然要开始接触全新的理念无疑是有点困难的,刚开始的两个月时间就是熟悉项目组的各种基础框架以及业务需求逻辑,说实话对于底层框架,对我非科班出身的应届生来说,基本算是云里雾里,但是从多位前辈大佬的口中得知刚开始不要想着一口吃个胖子,可以先从抄代码开始,也就是动手实践,后面的时间慢慢的跟着做业务迭代,开始熟悉这个框架的基本用法,只是模仿着怎么去用,但是从前人的口中知道刚开始就是先学会用,再慢慢去理解,我后来也觉得是这样。这个框架是之前iOS组的leader从零开始写起来的,来来回回我也看了好几遍,现在终于有些理解其中的真谛,心里还是挺开心的。
  首先我们来看一下这个库的基本结构

一、MVVM基本结构:

定义
1 Model 数据结构
2 View View 或者 ViewController,一般情况下在 ViewController 中进行 View 与 ViewModel 之间的数据绑定,如果 View 是 UITableViewCell 和 UICollectionViewCell 等,也会在 View 中进行数据绑定
3 ViewModel 管理ViewController的生命周期,网络请求获取数据模型,响应用户操作(导航,点击,滑动,手势等)
4 Service 这一层提供系统依赖的外部接口,如网络调用层、系统定位等
5 Controls 这一层提供一些工具类

  要让 ViewController 廋下来的,就需要将对应的业务逻辑移到 ViewModel 层,主要把导航从VC映射到VM,所有需要push,pop,present,dismiss操作的接口都封装到Navigation相关的两个protocol:LPDNavigationControllerProtocolLPDNavigationViewModelProtocol 中,当需要 Present 或者 Push 一个 ViewController,必须要嵌套在 NavigationViewController 中,同样的 Present 或者 Push 一个 ViewModel 时,必须要嵌套在 NavigationViewModel中,这样并不会带来更多的复杂性,但是在需要用 Navigation 时,不需要做任何改动。实际上导航操作还是原生的方法处理的,顺带映射到VM中的导航栈数组变化

ViewModel 与 ViewController 解藕存在的问题 解决方案对应的 Protocol
1 导航同步问题 LPDNavigationControllerProtocolLPDNavigationViewModelProtocol
2 子ViewController问题 LPDViewControllerProtocol, LPDViewModelProtocol
3 生命周期同步问题 LPDViewModelBecomeActiveProtocol, LPDViewModelDidLoadViewProtocol,LPDViewModelDidLayoutSubviewsProtocol
4 表单提交进度条 LPDViewModelSubmittingProtocol
5 加载进度条,网络较差重试页面 LPDViewModelLoadingProtocol
6 toast LPDViewModelToastProtocol
7 empty LPDViewModelEmptyProtocol
8 networkstatus LPDViewModelNetworkStatusProtocol
9 下拉刷新、上拉加载更多 LPDScrollViewModelProtocol, LPDScrollViewControllerProtocol

  该框架中用了大量的protocol,主要是将一些同类的方法集中声明起来,在该用的地方服从协议并实现

二、具体分析

LPDNavigationController分析

通过RAC的方式去封锁可能出现的导航操作的所有方式,包括两种可能:
1.在LPDNavigationController及其子类中调用push,pop,present,dismiss
2.在LPDNavigationViewModel及其子类中调用push,pop,present,dismiss

  • 一对协议(LPDNavigationControllerProtocol, LPDNavigationViewModelProtocol)
@protocol LPDNavigationControllerProtocol <NSObject>

@required

- (instancetype)initWithViewModel:(__kindof id<LPDNavigationViewModelProtocol>)viewModel;

@property (nullable, nonatomic, strong, readonly) __kindof id<LPDNavigationViewModelProtocol> viewModel;

- (void)presentNavigationController:(UINavigationController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion;

- (void)dismissNavigationControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion;

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;

- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated;

- (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated;

- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated;

- (void)setViewControllers:(NSMutableArray <UIViewController *> *)viewControllers animated:(BOOL)animated;

@property(nullable, nonatomic,readonly,strong) UIViewController *topViewController;

@property(nullable, nonatomic,readonly,strong) UIViewController *visibleViewController;

@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;

@end
@protocol LPDNavigationViewModelProtocol <NSObject>

@required

- (instancetype)initWithRootViewModel:(__kindof id<LPDViewModelProtocol>)viewModel;

@property (nullable, nonatomic, strong, readonly) __kindof id<LPDViewModelProtocol> topViewModel;

@property (nullable, nonatomic, strong, readonly) __kindof id<LPDViewModelProtocol> visibleViewModel;

@property (nullable, nonatomic, strong) __kindof id<LPDNavigationViewModelProtocol> presentedViewModel;

@property (nullable, nonatomic, strong) __kindof id<LPDNavigationViewModelProtocol> presentingViewModel;

@property (nonatomic, strong, readonly) NSArray<__kindof id<LPDViewModelProtocol>> *viewModels;

@property (nullable, nonatomic, strong) __kindof id<LPDTabBarViewModelProtocol> tabBar;


@optional

- (void)pushViewModel:(__kindof id<LPDViewModelProtocol>)viewModel animated:(BOOL)animated;

- (void)popViewModelAnimated:(BOOL)animated;

- (void)popToViewModel:(__kindof id<LPDViewModelProtocol>)viewModel animated:(BOOL)animated;

- (void)popToRootViewModelAnimated:(BOOL)animated;

- (void)presentNavigationViewModel:(__kindof id<LPDNavigationViewModelProtocol>)viewModel
                          animated:(BOOL)animated
                        completion:(nullable void (^)())completion;

- (void)dismissNavigationViewModelAnimated:(BOOL)animated completion:(nullable void (^)())completion;

- (void)setViewModels:(NSMutableArray <id<LPDViewModelProtocol>> *)viewModels animated:(BOOL)animated;

@end

  • @brief 设置同步ViewModel的导航到ViewController的导航的信号
- (void)subscribePushSignals {
  @weakify(self);
  [[self rac_signalForSelector:@selector(pushViewController:animated:)]
    subscribeNext:^(RACTuple *tuple) {
    @strongify(self);
    __kindof id<LPDViewControllerProtocol> viewControllerToPush = tuple.first;
    if ([viewControllerToPush isKindOfClass:LPDViewController.class] &&
        [viewControllerToPush respondsToSelector:@selector(viewModel)] &&
        [self.viewModel respondsToSelector:@selector(_pushViewModel:)]) {
      [self.viewModel performSelector:@selector(_pushViewModel:) withObject:viewControllerToPush.viewModel];
    }
  }];
  [[[self.viewModel rac_signalForSelector:@selector(pushViewModel:animated:)] deliverOnMainThread]
    subscribeNext:^(RACTuple *tuple) {
      @strongify(self);
      id<LPDViewControllerProtocol> viewController =
        (id<LPDViewControllerProtocol>)[LPDViewControllerFactory viewControllerForViewModel:tuple.first];
      [self pushViewController:viewController animated:[tuple.second boolValue]];
    }];
}

导航操作对应的逻辑示意图:

image.png
LPDTabBarController分析
  • 一对协议(LPDTabBarControllerProtocol,LPDTabBarViewModelProtocol)
@protocol LPDTabBarControllerProtocol <NSObject>

@required

- (instancetype)initWithViewModel:(__kindof id<LPDTabBarViewModelProtocol>)viewModel;

@property (nullable, nonatomic, strong, readonly) __kindof id<LPDTabBarViewModelProtocol> viewModel;

@end
@protocol LPDTabBarViewModelProtocol <NSObject>

@required

- (instancetype)initWithViewModels:(NSArray<__kindof id<LPDNavigationViewModelProtocol>> *)viewModels;

@property (nonatomic, readonly, strong) __kindof id<LPDNavigationViewModelProtocol> selectedViewModel;

@property (nonatomic) NSUInteger selectedIndex;

@property (nonatomic, readonly, strong) NSMutableArray<__kindof id<LPDNavigationViewModelProtocol>> *viewModels;

@end
  • LPDTabBarController和LPDTabBarViewModel的绑定
- (instancetype)initWithViewModel:(__kindof id<LPDTabBarViewModelProtocol>)viewModel {
  self = [super init];
  if (self) {
    self.viewModel = viewModel;

    NSMutableArray *viewControllers = [NSMutableArray array];
    for (id<LPDNavigationViewModelProtocol> childViewModel in self.viewModel.viewModels) {
      UIViewController *viewController = [LPDViewControllerFactory viewControllerForViewModel:childViewModel];
      [viewControllers addObject:viewController];
    }
    self.viewControllers = viewControllers;

    RAC(self, selectedIndex) = RACObserve(self.viewModel, selectedIndex);
  }
  return self;
}

LPDTabBarController一般和LPDNavigationController配对使用

LPDViewController分析
  • 一对协议(LPDViewControllerProtocol,LPDViewModelProtocol)
@protocol LPDViewControllerProtocol <LPDViewSubmittingProtocol,
                                     LPDViewToastProtocol,
                                     LPDViewNetworkStatusProtocol,
                                     LPDViewLoadingProtocol,
                                     LPDViewEmptyProtocol>

@required

- (instancetype)initWithViewModel:(__kindof id<LPDViewModelProtocol>)viewModel;

@property (nullable, nonatomic, strong, readonly) __kindof id<LPDViewModelProtocol> viewModel;

@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController;

@property(nonatomic,readonly) NSArray<__kindof UIViewController *> *childViewControllers NS_AVAILABLE_IOS(5_0);

- (void)addChildViewController:(UIViewController *)childController NS_AVAILABLE_IOS(5_0);

- (void)removeFromParentViewController NS_AVAILABLE_IOS(5_0);
@protocol LPDViewModelProtocol <LPDViewModelBecomeActiveProtocol,
                                LPDViewModelDidLoadViewProtocol,
                                LPDViewModelDidLayoutSubviewsProtocol,
                                LPDViewModelLoadingProtocol,
                                LPDViewModelSubmittingProtocol,
                                LPDViewModelToastProtocol,
                                LPDViewModelEmptyProtocol,
                                LPDViewModelNetworkStatusProtocol>

@required

/**
 *  @brief navigation bar title
 */
@property (nullable, nonatomic, copy) NSString *title;

/**
 *  @brief 添加childViewModel,如果此viewModel对应的viewController还没有加载,
 *  则会先加载到childViewModels中,等对应的viewController加载后会将所有childViewModels
 *  中的viewModel对应的的viewController加载
 */
- (void)addChildViewModel:(id<LPDViewModelProtocol>)childViewModel;

/**
 *  @brief 从父viewModel中移除
 */
- (void)removeFromParentViewModel;

/**
 *  @brief childViewModels,从对应viewController中的addChildViewController方法可以添加
 *  或者通过viewModel的addChildViewModel方法添加
 */
@property (nonatomic, copy, readonly) NSArray<id<LPDViewModelProtocol>> *childViewModels;

@property (nonatomic, weak, readonly) id<LPDViewModelProtocol> parentViewModel;

/**
 *  @brief navigation view model
 */
@property (nullable, nonatomic, weak) __kindof id<LPDNavigationViewModelProtocol> navigation;

@end
  • 实现从LPDViewController到LPDViewModel的绑定
    例如(均类似这种):
    1.生命周期函数的绑定(
    LPDViewModelBecomeActiveProtocol,
    LPDViewModelDidLoadViewProtocol,
    LPDViewModelDidLayoutSubviewsProtocol,
- (void)subscribeActiveSignal {
  @weakify(self);
  [[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) {
     @strongify(self);
     self.viewModel.active = YES;
  }];
  
  [[self rac_signalForSelector:@selector(viewWillDisappear:)] subscribeNext:^(id x) {
     @strongify(self);
     self.viewModel.active = NO;
  }];
}

2.业务逻辑的绑定(
LPDViewModelLoadingProtocol,
LPDViewModelSubmittingProtocol,
LPDViewModelToastProtocol,
LPDViewModelEmptyProtocol,
LPDViewModelNetworkStatusProtocol

- (void)subscribeLoadingSignal {
  @weakify(self);
  [[RACObserve(self.viewModel, loading) skip:1] subscribeNext:^(id x) {
     @strongify(self);
     if ([x boolValue]) {
        [self showLoading];
     } else {
        [self hideLoading];
     }
  }];
}
LPDScrollViewController分析

主要负责下拉刷新和上拉加载
LPDScrollViewController : LPDViewController
LPDScrollViewModel : LPDViewModel

  • 一对协议(LPDScrollViewControllerProtocol,LPDScrollViewModelProtocol)
@protocol LPDScrollViewControllerProtocol <LPDViewControllerProtocol>

/**
 *  @brief  设置下拉刷新当前页面上的数据,生效当且仅当scrollView被有效赋值
 *          当赋值为YES,可以出现下拉刷新控件,为NO时不出现,默认为NO
 */
@property (nonatomic, assign) BOOL needLoadingHeader;

/**
 *  @brief  设置列表中上滑加载更多数据,生效当且仅当loadingView被有效赋值
 *          当赋值为YES,可以出现上拉加载更多控件,为NO时不出现,默认为NO
 */
@property (nonatomic, assign) BOOL needLoadingFooter;

/**
 *  @brief  scrollView,可以是UIScrollView,
 *          UITableView,UICollectionView,UIWebView等
 *          loadingView赋值后,可以设置needLoading和needLoadingMore
 */
@property (nullable, nonatomic, weak) __kindof UIScrollView *scrollView;

@optional
/**
 *  @brief 初始化下拉刷新Header
 */
- (MJRefreshHeader *)customLoadingHeader:(MJRefreshComponentRefreshingBlock)refreshingBlock;

/**
 *  @brief 初始化上拉加载Footer
 */
- (MJRefreshFooter *)customLoadingFooter:(MJRefreshComponentRefreshingBlock)refreshingBlock;

@end
@protocol LPDScrollViewModelProtocol <LPDViewModelProtocol, LPDViewModelLoadingMoreProtocol>

@end
  • 下拉刷新和上拉加载的绑定
    LPDScrollViewModel下拉刷新中的setLoading和setLoadingSignal方法重用了LPDViewModel中的,上拉加载要重新写两个set方法
- (void)subscribeNeedLoadingHeaderSignal {
  @weakify(self);
  [[RACObserve(self, needLoadingHeader) filter:^BOOL(id value) {
    @strongify(self);
    return self.scrollView != nil;
  }] subscribeNext:^(id x) {
    @strongify(self);
    if ([x boolValue]) {
      if (nil == self.loadingHeader) {
        MJRefreshComponentRefreshingBlock refreshingBlock = ^{
          @strongify(self);
          self.viewModel.loading = YES;
        };
        if ([self respondsToSelector:@selector(customLoadingHeader:)]) {
          self.loadingHeader = [self customLoadingHeader:refreshingBlock];
        } else {
          self.loadingHeader = [MJRefreshNormalHeader headerWithRefreshingBlock:refreshingBlock];
        }
        self.scrollView.mj_header = self.loadingHeader;
      }
    } else {
      self.scrollView.mj_header = nil;
      self.loadingHeader = nil;
    }
  }];
}

- (void)subscribeLoadingSignal {
  @weakify(self);
  [[[RACObserve(self.viewModel, loading) skip:1] filter:^BOOL(id value) {
    @strongify(self);
    return nil != self.scrollView;
  }] subscribeNext:^(id x) {
    @strongify(self);
    if (self.needLoadingHeader) {
      if ([x boolValue]) {
        [self.viewModel setEmpty:NO];
        if (!self.loadingHeader.isRefreshing) { // 非下拉刷新触发
          [self performSelector:@selector(showLoading)];
        }
      } else {
        if (self.loadingHeader.isRefreshing) {
          [self.loadingHeader endRefreshing];
        } else {
          [self performSelector:@selector(hideLoading)];
        }
      }
    } else {
      if ([x boolValue]) {
        [self.viewModel setEmpty:NO];
        [self performSelector:@selector(showLoading)];
      } else {
        [self performSelector:@selector(hideLoading)];
      }
    }
  }];
}

- (void)subscribeNeedLoadingFooterSignal {
  @weakify(self);
  [[RACObserve(self, needLoadingFooter) filter:^BOOL(id value) {
    @strongify(self);
    return self.scrollView != nil;
  }] subscribeNext:^(id x) {
    @strongify(self);
    if ([x boolValue]) {
      if (nil == self.loadingFooter) {
        MJRefreshComponentRefreshingBlock refreshingBlock = ^{
          @strongify(self);
          [self.viewModel setLoadingMoreState:LPDLoadingMoreStateBegin];
        };
        if ([self respondsToSelector:@selector(customLoadingFooter:)]) {
          self.loadingFooter = [self customLoadingFooter:refreshingBlock];
        } else {
          self.loadingFooter = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:refreshingBlock];
        }
        self.scrollView.mj_footer = self.loadingFooter;
      }
    } else {
      self.scrollView.mj_footer = nil;
      self.loadingFooter = nil;
    }
  }];
}

- (void)subscribeLoadingMoreSignal {
  @weakify(self);
  [[RACObserve(((id<LPDViewModelLoadingMoreProtocol>)self.viewModel), loadingMoreState) filter:^BOOL(id value) {
    @strongify(self);
    return nil != self.scrollView;
  }] subscribeNext:^(NSNumber *value) {
    @strongify(self);
    LPDLoadingMoreState loadingMoreState = [value integerValue];
    if (loadingMoreState == LPDLoadingMoreStateBegin) {
      [self.loadingFooter beginRefreshing];
    } else if (loadingMoreState == LPDLoadingMoreStateEnd){
      [self.loadingFooter endRefreshing];
    } else {
      [self.loadingFooter  noticeNoMoreData];
    }
  }];
}

三、用法举例

(1) 导航一一对应的例子

LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] init];
LPDHomeViewController *vc = [[LPDHomeViewController alloc] initWithViewModel:vm];
[self.navigation pushViewController:vc animated:YES];

LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] init];
[self.navigation pushViewModel:vm animated:YES];
[self.navigation popViewControllerAnimated:YES];

[self.navigation popViewModelAnimated:YES];
[self.navigation popToRootViewControllerAnimated];

[self.navigation popToRootViewModelAnimated:YES];
LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] init];
LPDNavigationViewModel *nvm = [[LPDNavigationViewModel alloc] initWithRootViewModel:vm];
[self.navigation presentViewController:[[LPDNavigationController alloc] initWithViewModel:nvm] animated:YES completion:nil];

LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] init];
[self.navigation presentViewModel:[[LPDNavigationViewModel alloc] initWithRootViewModel:vm] animated:YES completion:nil];
[self.navigation dismissViewControllerAnimated:YES completion:nil];

[self.navigation dismissViewModelAnimated:YES completion:nil];

(2) 子 ViewController 的例子

要实现子 ViewController,现在可以这么做了:

LPDWaybillsViewModel *waybillsViewModel = [[LPDWaybillsViewModel alloc] init];
waybillsViewModel.title = @"待取餐";
waybillsViewModel.waybillStatus = LPDWaybillStatusFetching;
[self addChildViewModel:waybillsViewModel];
waybillsViewModel = [[LPDWaybillsViewModel alloc] init];
waybillsViewModel.title = @"待送达";
waybillsViewModel.waybillStatus = LPDWaybillStatusDelivering;
[self addChildViewModel:waybillsViewModel];

(3) 表单提交的进度条的例子

更简单了,一行代码:

self.submitting = YES;  // Show

self.submitting = NO;  // hide

(4) 加载的进度条的例子

需要设置 BeginLoadingBlock 和 EndLoadingBlock 来实现显示和取消加载进度条,然后不需要做其它事情了,剩下的交给框架来实现就好了,后面会说到 Tableview 和 Collectionview 加载的实现:

[self beginLoadingBlock:^(UIView *_Nonnull view) {
     UIView *contentView = [view viewWithTag:777777];
     if (contentView) {
         return;
     }
     contentView = [[UIView alloc] initWithFrame:view.bounds];
     contentView.tag = 777777;
     contentView.backgroundColor = [UIColor clearColor];
     [view addSubview:contentView];
     UIView *loadingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
     loadingView.layer.cornerRadius = 10;
     loadingView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];

     UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 57, 42)];
     imageView.animationImages = @[
                                   [UIImage imageNamed:@"01"],
                                   [UIImage imageNamed:@"02"],
                                   [UIImage imageNamed:@"03"],
                                   [UIImage imageNamed:@"04"],
                                   [UIImage imageNamed:@"05"],
                                   [UIImage imageNamed:@"06"]
                                   ];
     [loadingView addSubview:imageView];
     imageView.center = CGPointMake(loadingView.width / 2, loadingView.height / 2);
     [contentView addSubview:loadingView];
     //      loadingView.center = CGPointMake(contentView.width / 2, contentView.height / 2);

     if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
         loadingView.center = [[UIApplication sharedApplication]
                               .keyWindow convertPoint:CGPointMake(UIScreen.width / 2, UIScreen.height / 2)
                               toView:view];
     } else {
         loadingView.center = CGPointMake([UIApplication sharedApplication].keyWindow.center.x,
                                          [UIApplication sharedApplication].keyWindow.center.y - 64);
     }
     [imageView startAnimating];
 }];
[self endLoadingBlock:^(UIView *_Nonnull view) {
    UIView *contentView = [view viewWithTag:777777];
    if (contentView) {
        [contentView removeFromSuperview];
    }
}];

(5) 下拉刷新的例子

下拉刷新默认使用 MJRefresh,可以扩展然后通过 InitHeaderBlock 来定制自己的下拉刷新效果,要实现下拉刷新不要太简单了,在LPDScrollViewController 的子类中添加两行代码,在对应的 ViewModel 中实现 LoadingSignal:

self.scrollView = self.tableView;
self.needLoading = YES;

(6) 上拉加载更多的例子

上拉加载更多默认使用 MJRefresh,目前暂时不支持定制,上拉加载更多的实现也很简单,在 LPDScrollViewController 的子类中添加一行代码,剩下的就是在对应的 ViewModel 中实现 LoadingMoreSignal:

self.needLoadingMore = YES;

该库确实做到了降低vc的重量,将逻辑放到vm中来,但是该库依然有一些还需改进的地方,我们会一直维护下去。

以下贴出源码地址:
LPDMvvmKit

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