iOS架構(gòu)-MVC

前言

MVC是軟件工程中的一種軟件架構(gòu)模式,它把軟件系統(tǒng)分為三個基本的部分:模型Model、視圖View以及控制器Controller。這種模式的目的是為了實現(xiàn)一種動態(tài)的程序設(shè)計,簡化后續(xù)對軟件系統(tǒng)的修改和擴展植袍,并使得程序的某一部分的復用成為可能。三個部分按照其各自的職責劃分:

  • 數(shù)據(jù)Model: 負責封裝數(shù)據(jù)籽懦、存儲和處理數(shù)據(jù)運算等工作
  • 視圖View: 負責數(shù)據(jù)展示于个、監(jiān)聽用戶觸摸等工作
  • 控制器Controller: 負責業(yè)務邏輯、事件響應暮顺、數(shù)據(jù)加工等工作

在傳統(tǒng)的MVC結(jié)構(gòu)中厅篓,數(shù)據(jù)層在發(fā)生改變之后會通知視圖層進行對應的處理,視圖層能直接訪問數(shù)據(jù)層拖云。但在iOS中贷笛,M和V之間禁止通信,必須由C控制器層來協(xié)調(diào)M和V之間的變化宙项。如下圖所示乏苦,C對M和V的訪問是不受限的,但M和V不允許直接接觸控制器層,而是由多種Callbacks方式來通知控制器


如何分層

MVC是iOS開發(fā)者最常用的框架結(jié)構(gòu)汇荐,即便是越來越熱門的MVVM或是其他框架結(jié)構(gòu)洞就,幾乎都是基于MVC模式下對各個組塊的職責進一步的細化分層罷了。那么掀淘,在開發(fā)的時候如何制定三部分的層次劃分呢旬蟋?基本上所有的應用無非都是在做這些事情:



雖然上圖不能囊括所有的應用,但是基本而言大眾開發(fā)者干的活就是這些了革娄。簡單的根據(jù)這些事情來分工倾贰,我們可以很快的得出MVC和工作內(nèi)容的對應關(guān)系:

controller <--> 網(wǎng)絡(luò)請求、事件響應
view <--> 數(shù)據(jù)展示拦惋、動效展示
model <--> 數(shù)據(jù)處理

通過對我們開發(fā)工作的分工匆浙,MVC架構(gòu)的代碼分層幾乎已經(jīng)可以確定了,下面筆者會對這三部分進行更詳細的講述

模型Model應該放什么代碼

在以往開發(fā)中厕妖,對于模型層筆者存在這么幾個疑惑:

  • 模型Model只是一個純粹的數(shù)據(jù)結(jié)構(gòu)
  • 負責數(shù)據(jù)I/O操作的操作屬于C還是M

第一個問題筆者認為原因在于認知錯誤首尼,過往開發(fā)的過程中,筆者曾經(jīng)一度認為數(shù)據(jù)和模型之間的轉(zhuǎn)換屬于業(yè)務操作言秸,將這些處理放在控制器Controller層中執(zhí)行:

- (void)analyseRequestJSON: (NSDictionary *)JSON {
    NSArray *modelsData = JSON[@"result"];
    NSMutableArray *models = @[].mutableCopy;

    for (NSDictionary *data in modelsData) {
        LXDRecord *record = [[LXDRecord alloc] init];
        record.content = data[@"content"];
        record.recorder = data[@"recorder"];
        record.createDate = data[@"createDate"];
        record.updateDate = data[@"updateDate"];
        [models addObject: record];
    }
}

這是典型的認知錯誤引發(fā)的代碼錯誤放置的錯誤软能,對于這種情況,直接常見的做法是在Model中直接加入全能構(gòu)造器Designed Initializer來將這部分代碼轉(zhuǎn)移至Model中:

@interface LXDRecord: NSObject
//properties
- (instancetype)initWithCreateDate: (NSString *)createDate
                        updateDate: (NSString *)updateDate
                           content: (NSString *)content
                          recorder: (NSString *)recorder;
@end

//Controller
- (void)analyseRequestJSON: (NSDictionary *)JSON {
    NSArray *modelsData = JSON[@"result"];
    NSMutableArray *models = @[].mutableCopy;

    for (NSDictionary *data in modelsData) {
        LXDRecord *record = [[LXDRecord alloc] initWithCreateDate: data[@"createDate"]
                                    updateDate: data[@"updateDate"]
                                       content: data[@"content"]
                                      recorder: data[@"recorder"]];
        [models addObject: record];
    }
}

在轉(zhuǎn)移數(shù)據(jù)->模型這一邏輯處理之后數(shù)據(jù)層相對而言就充實的多举畸,但這還不夠查排。數(shù)據(jù)在完成抽象轉(zhuǎn)換的工作之后,通常要展示到視圖層面上抄沮。但往往模型還需要進行額外的加工才能展示雹嗦,比如筆者曾經(jīng)項目中的一個需求:用戶在繳納寬帶費用后將寬帶辦理期間顯示出來,這需求建立在服務器只有辦理時間和辦理時長兩個字段合是。在MVC的結(jié)構(gòu)下,將這部分代碼放在C層會導致代碼過多過于雜亂的后果锭环,因此筆者將其放在Model中:

@interface YQBNetworkRecord: YQBModel

@property (nonatomic, copy, readonly) NSString *dealDate;    //辦理時間
@property (nonatomic, copy, readonly) NSString *effectTime;  //辦理時長

- (NSString *)timeOfNetworkDuration;

@end


@implementation YQBNetworkRecord

- (NSString *)timeOfNetworkDuration {
    NSTimeInterval effectInterval = [_effectTime stringToInterval];
    return [_dealDate stringByAppendString: [_dealDate dateAfterInterval: effectInterval]];
}

@end

這一做法將一部分C 層次的邏輯放到了M中聪全,由于這一部分的邏輯屬于弱業(yè)務,屬于幾乎不會改動的業(yè)務邏輯辅辩,因此并不會影響MVC的整體結(jié)構(gòu)难礼。但同樣也存在著風險:

  • 代碼依賴于Model的差異化,復用性低
  • 代碼量取決于Model的數(shù)量玫锋,容易導致胖Model的情況

雖然存在著這些不足蛾茉,但是如果是在MVC模式下對控制器進行減負的情況下,這種做法簡單有效撩鹿。另外,使用category將這些邏輯代碼分離出去可以使得復用性變得不那么的糟键思。當然上面的數(shù)據(jù)->模型過程中也存在著因為數(shù)據(jù)類型變化而導致構(gòu)造器失效的問題,這時候參考YYModel的做法可以減少或者解決這些問題的發(fā)生

I/O操作

首先是I/O操作的業(yè)務歸屬問題吼鳞。假設(shè)我們的M采用了序列歸檔的持久化方案,那么M層應該實現(xiàn)NSCoding協(xié)議:

@interface LXDRecord: NSObject<NSCoding>
@end

@implementation LXDRecord

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject: _content forKey: @"content"];
    [aCoder encodeObject: _recorder forKey: @"recorder"];
    [aCoder encodeObject: _createDate forKey: @"createDate"];
    [aCoder encodeObject: _updateDate forKey: @"updateDate"];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        _content = [aDecoder decodeObjectForKey: @"content"];
        _recorder = [aDecoder decodeObjectForKey: @"recorder"];
        _createDate = [aDecoder decodeObjectForKey: @"createDate"];
        _updateDate = [aDecoder decodeObjectForKey: @"updateDate"];
    }
    return self;
}

@end

從序列化歸檔的實現(xiàn)中我們可以看到這些核心代碼是放在模型層中實現(xiàn)的赔桌,雖然還要借助NSKeyedArchiver來完成存取操作供炎,但是在這些實現(xiàn)上將I/O操作歸屬為M層的業(yè)務也算的上符合情理。另一方面疾党,合理的將這一業(yè)務放到模型層中既減少了控制器層的代碼量音诫,也讓模型層不僅僅是花瓶角色。通常情況下仿贬,我們的I/O操作不會直接放在控制器的代碼中纽竣,而是會將這部分操作封裝成一個數(shù)據(jù)庫管理者來執(zhí)行:

@interface LXDDataManager: NSObject

+ (instancetype)sharedManager;

- (void)insertData: (LXDRecord *)record;
- (NSArray<LXDRecord *> *)storedRecord;

@end

這是一段非常常見的數(shù)據(jù)庫管理者的代碼,缺點是顯而易見的:I/O操作的業(yè)務實現(xiàn)對象過于依賴數(shù)據(jù)模型的結(jié)構(gòu)茧泪,這使得這部分業(yè)務幾乎不可復用蜓氨,僅能服務指定的數(shù)據(jù)模型。解決的方案之一采用數(shù)據(jù)庫關(guān)鍵字<->屬性變量名映射的方式傳入映射字典:

@interface LXDDataManager: NSObject

+ (instancetype)managerWithTableName: (NSString *)tableName;

- (void)insertData: (id)dataObject mapper: (NSDictionary *)mapper;    

@end

@implementation LXDDataManager

- (void)insertData: (id)dataObject mapper: (NSDictionary *)mapper {
    NSMutableString * insertSql = [NSMutableString stringWithFormat: @"insert into %@ (", _tableName];
    NSMutableArray * keys = @[].mutableCopy;
    NSMutableArray * values = @[].mutableCopy;
    NSMutableArray * valueSql = @[].mutableCopy;

    for (NSString * key in mapper) {
        [keys addObject: key];
        [values addObject: ([dataObject valueForKey: key] ?: [NSNull null])]; 
        [valueSql addObject: @"?"];
    }

    [insertSql appendString: [keys componentsJoinedByString: @","];
    [insertSql appendString @") values ("];
    [insertSql appendString: [valueSql componentsJoinedByString: @","];
    [insertSql appendString: @")"];

    [_database executeUpdate: insertSql withArgumentsInArray: values];
}

@end

通過鍵值對映射的方式讓數(shù)據(jù)管理者可以動態(tài)的插入不同的數(shù)據(jù)模型队伟,這樣可以減少I/O操作業(yè)務中對數(shù)據(jù)模型結(jié)構(gòu)的依賴穴吹,使得其更易用。更進一步還可以將這段代碼中mapper的映射任務分離出來嗜侮,通過聲明一個映射協(xié)議來完成這一工作:

@protocol LXDModelMapper <NSObject>

- (NSArray<NSString *> *)insertKeys;
- (NSArray *)insertValues;

@end

@interface LXDDataManager: NSObject

+ (instancetype)managerWithTableName: (NSString *)tableName;

- (void)insertData: (id<LXDModelMapper>)dataObject;    

@end

@implementation LXDDataManager

- (void)insertData: (id<LXDModelMapper>)dataObject mapper: (NSDictionary *)mapper {
    NSMutableString * insertSql = [NSMutableString stringWithFormat: @"insert into %@ (", _tableName];
    NSMutableArray * keys = [dataObject insertKeys];
    NSMutableArray * valueSql = @[].mutableCopy;

    for (NSInteger idx = 0; idx < keys.count; idx++) {
        [valueSql addObject: @"?"];
    }

    [insertSql appendString: [keys componentsJoinedByString: @","];
    [insertSql appendString @") values ("];
    [insertSql appendString: [valueSql componentsJoinedByString: @","];
    [insertSql appendString: @")"];
    [_database executeUpdate: insertSql withArgumentsInArray: [dataObject insertValues]];
}

@end

將這些邏輯分離成協(xié)議來實現(xiàn)的好處包括:

  • 移除了I/O業(yè)務中不必要的邏輯港令,侵入性更低
  • 讓開發(fā)者實現(xiàn)協(xié)議返回的數(shù)據(jù)排序會更對齊
  • 擴展支持I/O操作的數(shù)據(jù)模型

總結(jié)一下M層可以做的事情:

1.提供接口來提供數(shù)據(jù)->展示內(nèi)容的實現(xiàn),盡可能以category的方式完成
2.對于M層統(tǒng)一的業(yè)務比如存取可以以協(xié)議實現(xiàn)的方式提供所需信息

視圖層的Self-Manager

通常情況下锈颗,視圖層只是簡單負責數(shù)據(jù)展示和負責將事件響應轉(zhuǎn)交給控制器C層執(zhí)行顷霹,創(chuàng)建視圖的代碼都在控制器層中完成,因此V層的狀態(tài)也不見得比M好得多击吱。比如當我自定義一個扇形展開的菜單視圖淋淀,在點擊時的響應:

//LXDMenuView.m
- (void)clickMenuItem: (LXDMenuItem *)menuItem {
    if ([_delegate respondsToSelector: @selector(menuView:didSelectedItem:)]) {
        [_delegate menuView: self didSelectedItem: menuItem.tag];
    }
}

//ViewController.m
- (void)menuView: (LXDMenuView *)menuView didSelectedItem: (NSInteger)index {
    Class controllerCls = NSClassFromString(_controllerNames[index]);
    UIViewController *nextController = [[controllerCls alloc] init];
    [self.navigationController pushViewController: nextController animated: YES];
}

這段代碼是最常見的視圖->控制器事件處理流程,當一個控制器界面的自定義視圖覆醇、控件響應事件過多的時候朵纷,即便我們已經(jīng)使用#pragma mark -的方式將這些事件進行分段,但還是會占用過大的代碼量永脓。MVC公認的問題是C完成了太多的業(yè)務邏輯袍辞,導致過胖,跟M層的處理一樣的常摧,筆者同樣將一部分弱業(yè)務轉(zhuǎn)移到V層上搅吁,比如上面的這段頁面跳轉(zhuǎn):

@interface LXDMenuView: UIView

@property (nonatomic, strong) NSArray<NSString *> * itemControllerNames;

@end


@implementation LXDMenuView

- (void)clickMenuItem: (LXDMenuItem *)menuItem {
    UIViewController *currentController = [self currentController];
    if (currentController == nil) { return; }

    Class controllerCls = NSClassFromString(_itemControllerNames[menuItem.tag]);
    UIViewController *nextController = [[controllerCls alloc] init];
    if ([currentController respondsToSelector: @selector(menuView:transitionToController:)]) {
        [currentController menuView: self transitionToController: nextController];
    }
    [currentController.navigationController pushViewController: nextController animated: YES];
}

- (UIViewController *)currentController {
    UIResponder *nextResponder = self.nextResponder;
    while (![nextResponder isKindOfClass: [UIWindow class]]) {
        if ([nextResponder isKindOfClass: [UIViewController class]]) {
            return (UIViewController *)nextResponder;
        }
        nextResponder = nextResponder.nextResponder;
    }
    return nil;
}

@end

這種業(yè)務轉(zhuǎn)移的思路來自于開發(fā)中的Self-Manager模式一文那婉。在這種代碼結(jié)構(gòu)中党瓮,如果V層決定了控制器接下來的跳轉(zhuǎn)寞奸,那么可以考慮將跳轉(zhuǎn)的業(yè)務遷移到V中執(zhí)行枪萄。通過事件鏈查找的方式獲取所在的控制器,這一過程并不能說違背了MVC的訪問限制原則聚凹,在整個過程中V不在乎其所在的currentController和nextController的具體類型妒牙,通過自定義一個協(xié)議來在跳轉(zhuǎn)前將nextController發(fā)送給當前控制器完成跳轉(zhuǎn)前的配置湘今。

這里要注意的是摩瞎,Self-Manager有其特定的使用場景旗们。當視圖層的回調(diào)處理需要兩層或者更多的時候蚪拦,Self-Manager能有效的執(zhí)行

如果抽離的足夠高級,甚至可以定義一個同一個的Self-Manager協(xié)議來提供給自定義視圖完成這些工作洛巢。這樣同一套業(yè)務邏輯可以給任意的自定義視圖復用次兆,只要其符合視圖<->控制器的捆綁關(guān)系:

@protocol LXDViewSelfManager <NSObject>

@optional
- (void)customView: (UIView *)customView transitionToController: (UIViewController *)nextController;

@end

視圖層的動畫效果

動畫實現(xiàn)也是屬于V部分的邏輯漓库,這點的理由有這么兩個:

  • 動畫實現(xiàn)和演示視圖存在依賴關(guān)系
  • 將動畫實現(xiàn)放到視圖層可以實現(xiàn)動效視圖的復用

話是這么說渺蒿,但是在許多的項目中茂装,這樣的代碼比比皆是:

@implementation ViewController: UIViewController

//彈窗動畫效果
- (void)animatedAlertView {
    AlertView *alert = [[AlertView alloc] initWithMessage: @"這是一條彈窗警告信息"];
    alert.alpha = 0;
    alert.center = self.view.center;
    alert.transform = CGAffineTransformMakeScale(0.01, 0.01);

    [UIView animateWithDuration: 0.25 animations: ^{
        alert.alpha = 1;
        alert.transform = CGAffineTransformIdentity;
    }];
}

@end

具體的槽點筆者就不吐了城侧,對于動畫實現(xiàn)筆者只有一個建議:無論你要實現(xiàn)的動畫多么簡單彼妻,統(tǒng)一扔到View中去實現(xiàn)侨歉,提供接口給C層調(diào)用展示摊册。要知道茅特,飽受開發(fā)者吐槽的UIAlertView在彈窗效果上的接口簡潔的挑不出毛病棋枕,僅僅一個- (void)show就完成了眾多的動畫效果兵睛。如果你不喜歡因為動畫效果就要自定義視圖窥浪,那么將常用的動畫效果以category的方式擴展出來使用:

@interface UIView (Animation)

- (void)pop;

@end

@implementation UIView (Animation)

- (void)pop {
    CGPoint center = CGPointMake(self.superView.frame.size.width / 2, self.superView.frame.size.height / 2);
    self.center = center;
    self.alpha = 0;
    self.transform = CGAffineTransformMakeScale(0.01, 0.01);

    [UIView animateWithDuration: 0.25 animations: ^{
        self.alpha = 1;
        self.transform = CGAffineTransformIdentity;
    }];
}

@end

瘦身Controller

MVC中最大的問題在于C層負擔了太多的業(yè)務假颇,所以導致Controller過大骨稿。那么將一些不屬于的Controller業(yè)務的邏輯分離到其他層中是主要的解決思路。iOS的MVC模式也被稱作重控制器模式哥桥,這是在實際開發(fā)中激涤,我們可以看到V和C難以相互獨立已卸,這兩部分總是緊緊的粘合在一起的:



在iOS中累澡,Controller管理著自己的視圖的生命周期愧哟,因此會和這個視圖本身產(chǎn)生較大的耦合關(guān)系蕊梧。這種耦合最大的表現(xiàn)在于我們的V總是幾乎在C中創(chuàng)建的肥矢,生命周期由C層來負責,所以對于下面這種視圖創(chuàng)建代碼我們并不會覺得有什么問題:

//ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [[UIButton alloc] initWithFrame: CGRectMake(20, 60, self.view.bounds.size.width - 40, 45)];
    [btn setTitle: @"點擊" forState: UIControlStateNormal];
    [btn addTarget: self action: @selector(clickButton:) forControlEvents: UIControlEventTouchUpInside];
    [self.view addSubview: btn];
}

但是按照業(yè)務邏輯來說十艾,我們可以在Controller里面創(chuàng)建視圖忘嫉,但是配置的任務不應該輕易的放在C層庆冕。因此愧杯,這些創(chuàng)建工作完全可以使用視圖的category來實現(xiàn)配置業(yè)務,對于常用的控件你都可以嘗試封裝一套構(gòu)造器來減少Controller中的代碼:

@interface UIButton(LXDDesignedInitializer)

+ (instancetype)buttonWithFrame: (CGRect)frame text: (NSString *)text;
+ (instancetype)buttonWithFrame: (CGRect)frame text: (NSString *)text textColor: (UIColor *)textColor;
+ (instancetype)buttonWithFrame: (CGRect)frame text: (NSString *)text textColor: (UIColor *)textColor fontSize: (CGFloat)fontSize target: (id)target action: (SEL)action;
+ (instancetype)buttonWithFrame: (CGRect)frame text: (NSString *)text textColor: (UIColor *)textColor fontSize: (CGFloat)fontSize cornerRadius: (CGFloat)cornerRadius;
+ (instancetype)buttonWithFrame: (CGRect)frame text: (NSString *)text textColor: (UIColor *)textColor fontSize: (CGFloat)fontSize cornerRadius: (CGFloat)cornerRadius target: (id)target action: (SEL)action backgroundColor: (UIColor *)backgroundColor;
+ (instancetype)buttonWithFrame:(CGRect)frame text:(NSString *)text textColor:(UIColor *)textColor fontSize: (CGFloat)fontSize cornerRadius: (CGFloat)cornerRadius target: (id)target action: (SEL)action image: (NSString *)image selectedImage: (NSString *)selectedImage backgroundColor: (UIColor *)backgroundColor;

@end

此外跌前,如果我們需要使用代碼設(shè)置視圖的約束時抵乓,Masonry大概是減少這些代碼的最優(yōu)選擇灾炭。視圖配置代碼是我們瘦身Controller的一部分蜈出,其次在于大量的代理協(xié)議方法铡原。因此,使用category將代理方法實現(xiàn)移到另外的文件中是一個好方法:

@interface ViewController (LXDDelegateExtension)<UITableViewDelegate, UITableViewDataSource>

@end

@implementation ViewController(LXDDelegateExtension)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //configurate and return cell
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    //return rows in section of cell number
}

@end

這種方式簡單的把代理方法挪移到category當中卵洗,但是也存在著一些缺點,因此適用場合會比較局限:

  • 在category中不能訪問原類的私有屬性酪夷、方法。這點Swift要超出OC太多
  • 在減少原類的代碼量的情況下實際上使得整個項目結(jié)構(gòu)讀起來更加復雜

筆者在通過上述的方式分離代碼之后晚岭,控制器層的代碼量基本可以得到控制鸥印。當然,除了上面提到的之外坦报,還有一個小的補充库说,我們基本都使用#pragma mark給控制器的代碼分段,一個比較有層次的分段注釋大概是這樣的:

#pragma mark - View Life
//視圖生命周期
#pragma mark - Setup
//創(chuàng)建視圖等
#pragma mark - Lazy Load片择、Getter潜的、Setter
//懶加載、Getter和Setter
#pragma mark - Event字管、Callbacks
//事件啰挪、回調(diào)等
#pragma mark - Delegate And DataSource
//代理和數(shù)據(jù)源方法
#pragma mark - Private
//私有方法

認真看是不是發(fā)現(xiàn)了其實很多的業(yè)務邏輯我們都能通過category的方式從Controller中分離出去信不。在這里我非常同意Casa大神的話:不應該出現(xiàn)私有方法下硕。對于控制器來說嫩码,私有方法基本都是數(shù)據(jù)相關(guān)的業(yè)務處理,將這些業(yè)務通category
或者策略模式分離出去會讓控制器更加簡潔

尾言

其實不管是熱門的MVVM架構(gòu)、或者其他稍冷的MVCS、VIPER之類的架構(gòu)模式,都是基于MVC改進的。本文不是要講MVC的代碼應該怎么分層辜膝,只是把自己對于這個模式的思考簡單的分享一下忱辅,希望能讓各位有所領(lǐng)悟。當然谈为,沒有一種結(jié)構(gòu)是絕對完美的签舞,業(yè)務職責的劃分必然帶來其相應的負面影響搂鲫,找到這些劃分的平衡點就是我們學習架構(gòu)設(shè)計的意義所在

原文鏈接

作者:sindri的小巢
鏈接:http://www.reibang.com/p/4847c9a1e19b
來源:簡書
著作權(quán)歸作者所有擦酌。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)介袜,非商業(yè)轉(zhuǎn)載請注明出處巍耗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末可缚,一起剝皮案震驚了整個濱河市涩赢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驮俗,老刑警劉巖索烹,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屹耐,居然都是意外死亡惶岭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巧娱,你說我怎么就攤上這事叁征。” “怎么了逛薇?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵捺疼,是天一觀的道長。 經(jīng)常有香客問我永罚,道長啤呼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任呢袱,我火速辦了婚禮官扣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羞福。我一直安慰自己惕蹄,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布治专。 她就那樣靜靜地躺著卖陵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪张峰。 梳的紋絲不亂的頭發(fā)上泪蔫,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音喘批,去河邊找鬼撩荣。 笑死,一個胖子當著我的面吹牛饶深,可吹牛的內(nèi)容都是我干的餐曹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼敌厘,長吁一口氣:“原來是場噩夢啊……” “哼凸主!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起额湘,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤卿吐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锋华,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗡官,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年毯焕,在試婚紗的時候發(fā)現(xiàn)自己被綠了衍腥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磺樱。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖婆咸,靈堂內(nèi)的尸體忽然破棺而出竹捉,到底是詐尸還是另有隱情,我是刑警寧澤尚骄,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布块差,位于F島的核電站,受9級特大地震影響倔丈,放射性物質(zhì)發(fā)生泄漏憨闰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一需五、第九天 我趴在偏房一處隱蔽的房頂上張望鹉动。 院中可真熱鬧,春花似錦宏邮、人聲如沸泽示。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽边琉。三九已至,卻和暖如春记劝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背族扰。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工厌丑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渔呵。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓怒竿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扩氢。 傳聞我的和親對象是個殘疾皇子耕驰,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容