iOS 客户端编程框架的学习

本文介绍 MVVM 和 MVVM without Binding with DataController 这两种编程框架,主要以代码方式展现。发展历史和概念性问题可自行�查找哈。

MVVM

推荐阅读 被误解的 MVC 和被神化的 MVVM
Model-View-ViewModel
MVVM是为了实现Model层与View层的解耦。将ViewModel作为VM的�中介,在Model发生改变的时候,View也会自动变化。使用双向绑定技术,通常使用RAC(ReactiveCocoa)这个框架来完成。

该�例子主要使用RAC的方法是 RACObserve(TARGET, KEYPATH) 。TARGET是监听目标,KEYPATH是要观察的属性值,当属性值发生变化的时候会调用 subscribeNext:(void (^)(id x))nextBlock 这个方法,并将KEYPATH的最新属性值通过x传递。

实现目的:共有三个控制器,商品分类列表 SortListViewController --> 商品列表 GoodsListViewController --> 商品详情页 GoodsDetailViewController。功能为 商品分类列表 将分类ID传入 商品列表界面,使用分类ID请求该分类下的数据在 商品列表页面展示,点击商品列表将商品ID传入商品详情页。

�流程图.png
SortListViewController
GoodsListModel *model = [[GoodsListModel alloc]init];
model.sortID = @"分类ID";
GoodsListViewModel *viewModel = [[GoodsListViewModel alloc]initWithGoodsListModel:model];
    
GoodsListViewController *goodsListVC = [[GoodsListViewController alloc]init];
goodsListVC.viewModel = viewModel;
[self.navigationController pushViewController:goodsListVC animated:YES];

此处的push传参是改变model.sortID 使GoodsListViewModel中的sortID监听发生改变,并保证每次传入不同的sortID都会执行方法。之后通过initWithGoodsListModel: 这个方法让GoodsListViewModel 进行数据请求,从而给ViewModel中的viewModelArray赋值,并将GoodsListViewModel与GoodsListViewController控制器绑定。

GoodsListViewController

商品列表这个控制器展现的为一个UICollectionView ,使用MVVM的框架修改,具体分为
GoodsListModel
GoodsListViewController
GoodsListViewModel
GoodsListView
GoodsListCellViewModel
GoodsListCell

  • GoodsListModel
GoodsListViewModel.h
#import <Foundation/Foundation.h>
@interface GoodsListModel : NSObject
@property (strong ,nonatomic) NSString *sortID;             //分类ID
@property (strong ,nonatomic) NSString *goodsID;            //商品ID
@property (strong ,nonatomic) NSString *goodsName;          //商品名称
@property (strong ,nonatomic) NSString *goodsPrice;         //商品价格
@end

这里解释下sortID, 商品列表GoodsListViewController这个控制器只是需要sortID来进行数据请求,并不要把这个sortID写入该控制器的Model类中。之所以写入,是便于每次传入sortID 而进行的数据请求,仅仅是为了方便起见,在GoodsListViewModel中网络请求完成的字典转模型时并不需要该参数。
也可以使用商品分类列表SortListViewController控制器的model类,在传入GoodsListViewModel 和 GoodsListViewModel 监听的model 做出修改即可。

  • GoodsListViewController
GoodsListViewController.h 
#import <UIKit/UIKit.h>
@class GoodsListViewModel;
@interface GoodsListViewController : UIViewController
@property (strong ,nonatomic) GoodsListViewModel *viewModel;
@end

GoodsListViewController.m
#import "GoodsListViewController.h"
#import "GoodsListViewModel.h"
#import "GoodsListView.h"
#import "GoodsDetailViewController.h"
@interface GoodsListViewController ()
@property (strong ,nonatomic) GoodsListView *goodsListView;
@end

@implementation GoodsListViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.goodsListView]
    [RACObserve(self.viewModel, viewModelArray) subscribeNext:^(id x) {
        self.goodsListView.viewModel = self.viewModel;
    }];
   
    [[self.viewModel.cellClickSubject takeUntil:self.rac_willDeallocSignal]subscribeNext:^(id x) {
        GoodsDetailViewController *goodsDetailsVC = [[GoodsDetailViewController alloc]init];
        goodsDetailsVC.goodsID = x;
        [self.navigationController pushViewController:goodsDetailsVC animated:YES];
    }];
}

- (GoodsListView *)goodsListView{
    if (!_goodsListView) {
        _goodsListView = [[GoodsListView alloc]initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH)];
    }
    return _goodsListView;
}
@end

商品列表控制器,在上个控制器push进来的时候已经将GoodsListViewModel与GoodsListViewController绑定。
RACObserve这个方法是监听GoodsListViewModel 中的viewModelArray (viewModelArray可以看做是网络请求字典转模型之后的模型数组)。当viewModelArray改变时将新的ViewModel传递给GoodsListView。实现了GoodsListViewController 与 goodsListView的绑定。这样双向绑定就完成了。
goodsListView 可以看做子视图的集合View,控制器仅仅需要在数据发生改变的时候告诉goodsListView即可,不需要管理数据的内容和数据的展示方式。

  • GoodsListViewModel
GoodsListViewModel.h
#import <Foundation/Foundation.h>
@class GoodsListCellViewModel;
@class GoodsListModel;
@interface GoodsListViewModel : NSObject
@property (copy, nonatomic) NSArray * viewModelArray;
@property (nonatomic, strong) RACSubject *cellClickSubject;
- (instancetype)initWithGoodsListModel: (GoodsListModel *) model;
- (void)first;
- (void)next;
- (GoodsListCellViewModel *)itemViewModelForIndex:(NSInteger)index;
@end

GoodsListViewModel.m
#import "GoodsListViewModel.h"
#import "GoodsListModel.h"
#import "GoodsListCellViewModel.h"
@interface GoodsListViewModel ()
@property (strong ,nonatomic) NSString *sortID;
@property (strong, nonatomic) NSNumber  *nextPageNumber;
@property (strong, nonatomic)NSDictionary *paramsDic;
@end

@implementation GoodsListViewModel
- (instancetype)initWithGoodsListModel:(GoodsListModel *)model{
    self = [super init];
    if (nil != self) {
        [RACObserve(model, sortID) subscribeNext:^(NSString *sort) {
            self.sortID = sort;
            self.nextPageNumber = @1;
        }];
        [self requestInfo];
    }
    return self;
}

- (void)requestInfo{
    [RACObserve(self, nextPageNumber)  subscribeNext:^(NSNumber * nextPageNumber) {
        NSDictionary *params = @{@"id":self.sortID,
                                 @"page":nextPageNumber,
                                 @"num":@15,};
        self.paramsDic = params;
    }];
 
    [[RACObserve(self, paramsDic) filter:^BOOL(id value) {
        return value;
    }] subscribeNext:^(NSDictionary * params) {
        //网络请求并给_viewModelArray赋值
        
        //RAC 的网络请求
        [[self.httpClient rac_POST:@"URL" parameters:params] subscribeNext:^(RACTuple *JSONAndHeaders) {
            JSONAndHeaders.first  为请求内容
        }];
    }];
}

- (void)first{
    self.nextPageNumber = @1;
}

- (void)next{
    self.nextPageNumber = [NSNumber numberWithInteger: [self.nextPageNumber integerValue] + 1];
}

- (GoodsListCellViewModel *)itemViewModelForIndex:(NSInteger)index{
    return [[GoodsListCellViewModel alloc]initWithModel:[_viewModelArray objectAtIndex:index]];
}

- (RACSubject *)cellClickSubject {
    if (!_cellClickSubject) {
        _cellClickSubject = [RACSubject subject];
    }
    return _cellClickSubject;
}

@end

ViewModel核心代码。可以看出nextPageNumber页码参数,paramsDic都是被观察的对象,每当新的sortID传入时,nextPageNumber都会重置为1,从而影响paramsDic,进而走网络请求。
initWithModel 方法将模型数组_viewModelArray 转为 GoodsListCellViewModel中的参数。
itemViewModelForIndex 方法在GoodsListView中给cell绑定数据的时候调用。
first 方法用于下拉刷新,将nextPageNumber重置为1。
next 方法用于上拉加载,将nextPageNumber +1。

  • GoodsListView
GoodsListView.h
#import <UIKit/UIKit.h>
@class GoodsListViewModel;
@interface GoodsListView : UIView
@property (nonatomic, strong) GoodsListViewModel *viewModel;
- (instancetype)initWithFrame:(CGRect)frame;
@end

GoodsListView.m
#import "GoodsListView.h"
#import "GoodsListViewModel.h"
#import "GoodsListCell.h"
@interface GoodsListView ()
@property (strong ,nonatomic) UICollectionView *collectionView;
@end

@implementation GoodsListView
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupView];
        [RACObserve(self, self.viewModel) subscribeNext:^(id x) {
            [_collectionView reloadData];
        }];
    }
    return self;
}

- (void)setupView{
    //_collectionView 添加
    _collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [self.viewModel first];
    }];
    _collectionView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
        [self.viewModel next];
    }];
}
//此处省去�UICollectionView的代理

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    GoodsListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];
    cell.cellViewModel = [self.viewModel itemViewModelForIndex:indexPath.row];
    return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    [self.viewModel.cellClickSubject sendNext:@"点击商品的ID"];
}
@end

子视图的集合,继承于UIView。这里仅放置了一个UICollectionView,添加了上拉加载,下拉刷新的功能,省去了_collectionView的创建和代理的设置。在初始化的时候将self.viewModel作为被观察的对象,当商品列表控制器传递过来的viewModel改变时将会调用UICollectionView的刷新方法。
itemViewModelForIndex:indexPath 这个方法是给cell绑定cellViewModel的数据,其实这里也可以省略,直接将viewModel的viewModelArray直接传给cell,省去了GoodsListCellViewModel的创建。

  • GoodsListCellViewModel
GoodsListCellViewModel.h
#import <Foundation/Foundation.h>
@class GoodsListModel;
@interface GoodsListCellViewModel : NSObject
- (instancetype)initWithModel:(GoodsListModel *)model;
@property (strong ,nonatomic) NSString *goodsID;
@property (strong ,nonatomic) NSString *goodsName;
@property (strong ,nonatomic) NSString *goodsPrice;
@end

GoodsListCellViewModel.m
#import "GoodsListCellViewModel.h"
#import "GoodsListModel.h"
@interface GoodsListCellViewModel ()
@property (strong ,nonatomic) GoodsListModel *model;
@end

@implementation GoodsListCellViewModel
- (instancetype)initWithModel:(GoodsListModel *)model{
    self = [super init];
    if (self) {
        self.model = model;
    }
    return self;
}
- (NSString *)goodsID{
    return self.model.goodsID;
}
- (NSString *)goodsName{
    return self.model.goodsName;
}
- (NSString *)goodsPrice{
    return self.model.goodsPrice;
}
@end

将viewModel中viewModelArray的模型转为 GoodsListCellViewModel

  • GoodsListCell
GoodsListCell.h
#import <UIKit/UIKit.h>
@class GoodsListCellViewModel;
@interface GoodsListCell : UICollectionViewCell
@property (strong ,nonatomic) GoodsListCellViewModel *cellViewModel;
@end

GoodsListCell.m
#import "GoodsListCell.h"
#import "GoodsListCellViewModel.h"
@implementation GoodsListCell
//Cell的布局
- (void)setCellViewModel:(GoodsListCellViewModel *)cellViewModel{
    _cellViewModel = cellViewModel;
    //cell控件的赋值
}
@end

使用GoodsListCellViewModel进行赋值即可

MVVM without Binding with DataController

参考 猿题库 iOS 客户端架构设计

�图片引自<猿题库 iOS 客户端架构设计>.png

控制器通过 DataController 请求数据,并将数据装配给ViewModel,ViewModel将数据中的模型转为View 对应的ViewModel ,View负责展示UI,并将事件传递给控制器。

本例子为商品列表页,默认子视图为CollectionView。分为
GoodsListViewController
GoodsListModel
GoodsListDataController
GoodsListViewModel
GoodsListCellViewModel
GoodsListView
GoodsListCell

  • GoodsListViewController
GoodsListViewController.m
#import "GoodsListViewController.h"
#import "GoodsListDataController.h"
#import "GoodsListView.h"
#import "GoodsListViewModel.h"

@interface GoodsListViewController ()<GoodsListViewDelegate>
@property (strong ,nonatomic) GoodsListDataController *dataManager;
@property (strong ,nonatomic) GoodsListView *goodsListView;
@end

@implementation GoodsListViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"商品列表";
    [self.view addSubview:self.goodsListView];
    [self fectchGoodsModelData];
}

- (void)fectchGoodsModelData{
    [self.dataManager requestGoodsModelDataWhenSucess:^{
        if (self.dataManager.goodsListModelArray.count > 0) {
            [self renderGoodsListView];
        }
    } fail:^(NSError *error) {
    }];
}

- (void)renderGoodsListView{
    GoodsListViewModel *viewModel = [GoodsListViewModel viewModelWithGoodsModel:self.dataManager.goodsListModelArray];
    [self.goodsListView bindDataWithViewModel:viewModel];
}

- (void)didSelectedRowOfGoodsID:(NSString *)goodsID{
    //子视图传递事件的代理
}

- (GoodsListDataController *)dataManager{
    if (!_dataManager) {
        _dataManager = [[GoodsListDataController alloc]init];
    }
    return _dataManager;
}
- (GoodsListView *)goodsListView{
    if (!_goodsListView) {
        _goodsListView = [[GoodsListView alloc]initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH)];
        _goodsListView.delegate = self;
    }
    return _goodsListView;
}
@end

商品列表控制器。添加子视图集合GoodsListView,初始化GoodsListDataController。
fectchGoodsModelData 方法使dataManager请求�网络请求,成功之后调用 renderGoodsListView 方法将数据装配到 ViewModel 中,再将ViewModel传递至 GoodsListView,更新数据刷新UI。 GoodsListView 中的事件通过代理传递到控制器中。

  • GoodsListModel
GoodsListModel.h
#import <Foundation/Foundation.h>
@interface GoodsListModel : NSObject
@property (strong ,nonatomic) NSString *goodsID;
@property (strong ,nonatomic) NSString *goodsName;
@property (strong ,nonatomic) NSString *goodsPrice;
@end

简单的商品属性

  • GoodsListDataController
GoodsListDataController.h
#import <Foundation/Foundation.h>
@class GoodsListModel;
@interface GoodsListDataController : NSObject
@property (strong ,nonatomic) NSMutableArray<GoodsListModel *> *goodsListModelArray;
- (void)requestGoodsModelDataWhenSucess:(void (^)())sucess fail:(void(^)(NSError *))fail;
@end

GoodsListDataController.m
#import "GoodsListDataController.h"
#import "GoodsListModel.h"
@implementation GoodsListDataController
- (instancetype)init{
    self = [super init];
    if (self) {
        self.goodsListModelArray = [[NSMutableArray alloc]init];
    }
    return self;
}

- (void)requestGoodsModelDataWhenSucess:(void (^)())sucess fail:(void (^)(NSError *))fail{
    self.goodsListModelArray = 模型数组
}
@end

DataController负责数据请求,并将模型数组赋值给self.goodsListModelArray。DataController数据的获取和处理从ViewModel抽离,一定程度上简化了ViewModel的工作量,当DataController收到外界ViewController的请求后进行数据处理,进而更新UI。这样ViewController不需要关心数据的获取和处理,DataController不需要关心数据的展示和交互。

  • GoodsListViewModel
GoodsListViewModel.h
#import <Foundation/Foundation.h>
@class GoodsListModel;
@class GoodsListCellViewModel;
@interface GoodsListViewModel : NSObject
@property (strong ,nonatomic) NSArray <GoodsListCellViewModel *> *cellViewModelArray;
+ (GoodsListViewModel *)viewModelWithGoodsModel:(NSArray <GoodsListModel *>*)goodsListModelArray;
@end

GoodsListViewModel.m
#import "GoodsListViewModel.h"
#import "GoodsListModel.h"
#import "GoodsListCellViewModel.h"
@implementation GoodsListViewModel
+ (GoodsListViewModel *)viewModelWithGoodsModel:(NSArray <GoodsListModel *>*)goodsListModelArray{
    GoodsListViewModel *viewModel = [[GoodsListViewModel alloc]init];
    NSMutableArray *cellViewModelArray = [[NSMutableArray alloc]init];
    
    for (GoodsListModel *goodsListModel in goodsListModelArray) {
        GoodsListCellViewModel *cellViewModel = [GoodsListCellViewModel viewModelWithGoodsListModel:goodsListModel];
        [cellViewModelArray addObject:cellViewModel];
    }
    
    viewModel.cellViewModelArray = cellViewModelArray;
    return viewModel;
}
@end

ViewModel,将数据装配到viewModel。此处使用工厂方法创建一个带有cellViewModelArray属性的GoodsListViewModel对象。具体是将网络请求之后的goodsListModelArray模型数组转为 GoodsListCellViewModel类型的数组,并将这种类型的数组作为ViewModel的一个属性。

  • GoodsListCellViewModel
GoodsListCellViewModel.h
#import <Foundation/Foundation.h>
@class GoodsListModel;
@interface GoodsListCellViewModel : NSObject
@property (strong ,nonatomic) NSString *goodsID;
@property (strong ,nonatomic) NSString *goodsName;
@property (strong ,nonatomic) NSString *goodsPrice;
+ (GoodsListCellViewModel *)viewModelWithGoodsListModel:(GoodsListModel *)goodsListModel;
@end

GoodsListCellViewModel.m
#import "GoodsListCellViewModel.h"
#import "GoodsListModel.h"
@implementation GoodsListCellViewModel
+ (GoodsListCellViewModel *)viewModelWithGoodsListModel:(GoodsListModel *)goodsListModel{
    GoodsListCellViewModel *cellViewModel = [[GoodsListCellViewModel alloc]init];
    cellViewModel.goodsID = goodsListModel.goodsID;
    cellViewModel.goodsName = goodsListModel.goodsName;
    cellViewModel.goodsPrice = goodsListModel.goodsPrice;
    return cellViewModel;
}

@end

将viewModel中goodsListModelArray的模型转为 GoodsListCellViewModel

  • GoodsListView
GoodsListView.h
#import <UIKit/UIKit.h>
@class GoodsListViewModel;
@protocol GoodsListViewDelegate <NSObject>
- (void)didSelectedRowOfGoodsID:(NSString *)goodsID;
@end
@interface GoodsListView : UIView
@property (weak, nonatomic) id<GoodsListViewDelegate> delegate;
- (void)bindDataWithViewModel:(GoodsListViewModel *)viewModel;
@end

GoodsListView.m
#import "GoodsListView.h"
#import "GoodsListViewModel.h"
#import "GoodsListCell.h"
#import "GoodsListCellViewModel.h"
@interface GoodsListView ()
@property (strong ,nonatomic) GoodsListViewModel *viewModel;
@property (strong ,nonatomic) UICollectionView *collectionView;
@end
@implementation GoodsListView
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        //子视图 初始化,布局
    }
    return self;
}
- (void)bindDataWithViewModel:(GoodsListViewModel *)viewModel{
    self.viewModel = viewModel;
    [self.collectionView reloadData];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return self.viewModel.cellViewModelArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    GoodsListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"GoodsListCell" forIndexPath:indexPath];
    GoodsListCellViewModel *cellViewModel = self.viewModel.cellViewModelArray[indexPath.row];
    [cell bindGoodsListCellDataWithCellViewModel:cellViewModel];
    return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    GoodsListCellViewModel *cellViewModel = self.viewModel.cellViewModelArray[indexPath.row];
    if ([self.delegate respondsToSelector:@selector(didSelectedRowOfGoodsID:)]) {
        [self.delegate didSelectedRowOfGoodsID:cellViewModel.goodsID];
    }
}
@end

GoodsListView,子视图的集合。当数据装配至ViewModel时,通过bindDataWithViewModel: 方法将带有cellViewModelArray属性的ViewModel传递至子视图,使子视图刷新UI。bindGoodsListCellDataWithCellViewModel: 方法为Cell绑定数据,collectionView的点击方法通过代理传递给控制器。

  • GoodsListCell
GoodsListCell.h
#import <UIKit/UIKit.h>
@class GoodsListCellViewModel;
@interface GoodsListCell : UICollectionViewCell
- (void)bindGoodsListCellDataWithCellViewModel:(GoodsListCellViewModel *)cellViewModel;
@end

GoodsListCell.m
#import "GoodsListCell.h"
#import "GoodsListCellViewModel.h"
@implementation GoodsListCell
- (void)bindGoodsListCellDataWithCellViewModel:(GoodsListCellViewModel *)cellViewModel{
    //控件赋值
}
@end

将单个的cellViewModel值传递到Cell,并给Cell中的控件赋值。

结语

MVVM 使用的是双向绑定技术,需要RAC的支持,学习成本较大。
MVVM without Binding with DataController 是通过数据的装配和代理的方法实现了解耦。�并且将ViewModel中的网络请求给单独�抽离出来作为DataController,使ViewModel的结构更加简洁。理解较为容易。
MVC 简单经典的�架构,如果把基类写的足够便利,简单的页面还是推荐使用。
需要依据项目的开发和维护来选择合适的架构,有利有弊。

这是几个月前技术分享的内容,PPT上显示的是16/6/28,貌似好久了。当时用项目中的代码,把大家讲的可晕。现在把思路重写理了一遍,发现好多了,就记录下来,以后看着也方便。

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

推荐阅读更多精彩内容