老司機(jī)出品————多線程實踐

多線程實踐

有段時間沒寫博客了,不過這也不是一次兩次了。

厚顏無恥

嗯块茁,就不找理由也不檢討了,直奔主題吧辫狼。

在今天的博客中你將會看到:

  • 異步線程同步
  • NSOperation子類重寫
  • 條件模塊
  • 請求類封裝

異步線程同步

老司機(jī)今天講的不是多線程的基本用法初斑,這個東西往上的博客其實蠻多的,而且也基本是多線程的基本用法膨处。老司機(jī)今天主要的是介紹多個異步線程執(zhí)行結(jié)束后進(jìn)行回調(diào)的解決方案见秤,如果說這么說不太清楚的話,最常見的場景就是多個網(wǎng)絡(luò)請求都結(jié)束后觸發(fā)列表刷新真椿。

其實這個需求呢鹃答,還是挺常見的。主要呢突硝,目前有兩種解決思路测摔,一種呢是GCD中的dispatch_group,一種是NSOperation解恰。

dispatch_group

這個方案呢锋八,實現(xiàn)起來還比較簡單,先放一下代碼吧护盈。

-(void)testGCDGroup {
    dispatch_group_t g = dispatch_group_create();
    dispatch_queue_t q = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"Will enter task1");
    dispatch_group_enter(g);
    dispatch_group_async(g, q, ^{
        [self task1];
        dispatch_group_leave(g);
    });
    NSLog(@"Will enter task2");
    dispatch_group_enter(g);
    dispatch_group_async(g, q, ^{
        [self task2];
        dispatch_group_leave(g);
    });
    
    NSLog(@"Come to notify");
    dispatch_group_notify(g, q, ^{
        NSLog(@"Enter notify");
        [self taskComplete];
    });
    NSLog(@"Pass notify");
}

-(void)task1 {
    NSLog(@"Enter sleep 10.");
    [NSThread sleepForTimeInterval:10];
    NSLog(@"Leave sleep 10.");
}

-(void)task2 {
    NSLog(@"Enter sleep 5.");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"Leave sleep 5.");
}

-(void)taskComplete {
    NSLog(@"All task finished.");
}

控制臺輸出是這個樣子的:

2018-03-26 14:28:02.317556+0800 test[3446:287435] Will enter task1.
2018-03-26 14:28:02.317714+0800 test[3446:287435] Will enter task2.
2018-03-26 14:28:02.317733+0800 test[3446:287484] Enter sleep 10.
2018-03-26 14:28:02.317847+0800 test[3446:287435] Come to notify.
2018-03-26 14:28:02.317865+0800 test[3446:287486] Enter sleep 5.
2018-03-26 14:28:02.318093+0800 test[3446:287435] Pass notify.
2018-03-26 14:28:07.318474+0800 test[3446:287486] Leave sleep 5.
2018-03-26 14:28:12.321389+0800 test[3446:287484] Leave sleep 10.
2018-03-26 14:28:12.321740+0800 test[3446:287484] Enter notify.
2018-03-26 14:28:12.321932+0800 test[3446:287484] All task finished.

他呢挟纱,基本流程就是當(dāng)調(diào)用的dispatch_group_leave()與dispatch_group_enter()相等時,就會調(diào)用dispatch_group_notify()中的回調(diào)腐宋。不過這種實現(xiàn)方案呢紊服,還是有一個需要注意的點就是dispatch_group_enter()與dispatch_group_leave()要成對使用,否則就會進(jìn)入無限的等待狀態(tài)胸竞。

第二個解決方案就是使用NSOperation欺嗤。吶,我會放在第二節(jié)著重介紹一下的撤师。


NSOperation子類重寫

我們知道剂府,NSOperation是蘋果提供的一套面向?qū)ο蟮幕贕CD封裝的多線程解決方案。他在使用上更加符合面向?qū)ο蟮乃枷胩甓埽臃奖愕臑槿蝿?wù)添加依賴關(guān)系腺占,同時提供了四個支持KVO監(jiān)聽的代表當(dāng)前任務(wù)執(zhí)行狀態(tài)的屬性cancelled、executing痒谴、finished衰伯、ready。NSOperation內(nèi)部對這四個狀態(tài)行為作了預(yù)處理积蔚,根據(jù)任務(wù)的不同狀態(tài)這四個屬性的值會自動改變意鲸。當(dāng)NSOperation配合NSOperationQueue使用時,Queue會監(jiān)聽所有Operation的狀態(tài)從而分配任務(wù)的啟動時機(jī)≡豕耍總之读慎,NSOperation隱藏了很多內(nèi)部細(xì)節(jié),讓我們開發(fā)者無需關(guān)心任務(wù)的各種狀態(tài)槐雾。

系統(tǒng)行為

首先夭委,為了模仿系統(tǒng)行為,我們先觀察下系統(tǒng)的NSOperation的cancelled募强、executing株灸、finished、ready四個屬性的狀態(tài)變化情況擎值。那我們?nèi)ケO(jiān)聽一下NSOperation的四個屬性慌烧。代碼如下:

TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp1");
}];
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
[self logOp:bp1 keyPathes:keyPathes];  
[self addObserverForOp:bp1 keyPathes:keyPathes];
[bp1 start];
[bp1 cancel];

控制臺輸出:
2018-04-18 11:45:01.277354+0800 OperationDemo[72212:1655503] bp1 isReady = true
2018-04-18 11:45:01.277539+0800 OperationDemo[72212:1655503] bp1 isCancelled = false
2018-04-18 11:45:01.278212+0800 OperationDemo[72212:1655503] bp1 isExecuting = false
2018-04-18 11:45:01.278449+0800 OperationDemo[72212:1655503] bp1 isFinished = false
2018-04-18 11:45:01.278682+0800 OperationDemo[72212:1655503] bp1 before start
2018-04-18 11:45:01.278954+0800 OperationDemo[72212:1655503] bp1---isExecuting---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 11:45:01.279063+0800 OperationDemo[72212:1655503] bp1 before main
2018-04-18 11:45:01.279245+0800 OperationDemo[72212:1655503] enter bp1
2018-04-18 11:45:04.279669+0800 OperationDemo[72212:1655503] leave bp1
2018-04-18 11:45:04.280074+0800 OperationDemo[72212:1655503] bp1 after main
2018-04-18 11:45:04.281164+0800 OperationDemo[72212:1655503] bp1---isExecuting---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 11:45:04.281404+0800 OperationDemo[72212:1655503] bp1---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 11:45:04.281557+0800 OperationDemo[72212:1655503] bp1 after start
2018-04-18 11:45:04.281782+0800 OperationDemo[72212:1655503] bp1 before cancel
2018-04-18 11:45:04.281917+0800 OperationDemo[72212:1655503] bp1 after cancel

上述代碼中,我們監(jiān)聽了四個屬性并執(zhí)行了Operation鸠儿。根據(jù)日志我們可以總結(jié)如下:

初始狀態(tài)下屹蚊,ready為YES,其他均為NO

當(dāng)我們調(diào)用 -start 后捆交,執(zhí)行 -main 之前 isExecuting 屬性從NO被置為YES

調(diào)用 -main 之后開始執(zhí)行提交到Operation中的任務(wù)

任務(wù)完成后 isExecuting 屬性從YES被置為NO淑翼,isFinished 屬性從NO被置為YES

我們再看一下如果在執(zhí)行 -start 之前先執(zhí)行 -cancel 后會是什么狀態(tài):

TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp2");
}];
[self addObserverForObj:bp2 keyPathes:keyPathes];
self.bp2 = bp2;
[bp2 cancel];
[bp2 start];

控制臺輸出:
2018-04-18 11:44:03.597414+0800 OperationDemo[72184:1653790] bp2 before cancel
2018-04-18 11:44:03.597684+0800 OperationDemo[72184:1653790] bp2---isCancelled---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 11:44:03.597881+0800 OperationDemo[72184:1653790] bp2---isReady---{
    kind = 1;
    new = 1;
    old = 1;
}
2018-04-18 11:44:03.598051+0800 OperationDemo[72184:1653790] bp2 after cancel
2018-04-18 11:44:03.598138+0800 OperationDemo[72184:1653790] bp2 before start
2018-04-18 11:44:03.598279+0800 OperationDemo[72184:1653790] bp2---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 11:44:03.598393+0800 OperationDemo[72184:1653790] bp2 after start

在執(zhí)行 -start 之前調(diào)用 -cancel 后,isCancelled 屬性從NO被置為YES品追,isReady 屬性無論什么狀態(tài)都會被置為YES玄括。這里后面講到dependency的時候會說明。

-cancel 之后再調(diào)用 -start ,會將 isFinished 屬性從NO被置為YES肉瓦,然后并不調(diào)用 -main 方法遭京。

單個Operation的行為我們已經(jīng)基本了解,那么接下來我們來看一下當(dāng)兩個Operation添加到Queue中會是什么結(jié)果泞莉。

TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp1");
}];
bp1.name = @"bp1";
bp1.completionBlock = ^{
        NSLog(@"bp1 complete");
};
    
TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp2");
}];
bp2.name = @"bp2";
bp2.completionBlock = ^{
        NSLog(@"bp2 complete");
};
    
NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    
[self addObserverForOp:bp1 keyPathes:keyPathes];
[self addObserverForOp:bp2 keyPathes:keyPathes];
    
NSOperationQueue * q = [NSOperationQueue new];
[bp1 addDependency:bp2];
[q addOperation:bp1];
[q addOperation:bp2];
    
控制臺輸出:
2018-04-18 16:37:16.004963+0800 OperationDemo[84411:1940169] bp1 before addDependency:
2018-04-18 16:37:16.005291+0800 OperationDemo[84411:1940169] bp1---isReady---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 16:37:16.005640+0800 OperationDemo[84411:1940169] bp1 after addDependency:
2018-04-18 16:37:16.005842+0800 OperationDemo[84411:1940219] bp2 before start
2018-04-18 16:37:16.006277+0800 OperationDemo[84411:1940219] bp2---isExecuting---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:37:16.007394+0800 OperationDemo[84411:1940219] bp2 before main
2018-04-18 16:37:16.007669+0800 OperationDemo[84411:1940219] enter bp2
2018-04-18 16:37:19.010134+0800 OperationDemo[84411:1940219] leave bp2
2018-04-18 16:37:19.010351+0800 OperationDemo[84411:1940219] bp2 after main
2018-04-18 16:37:19.010701+0800 OperationDemo[84411:1940218] bp1 before start
2018-04-18 16:37:19.010707+0800 OperationDemo[84411:1940219] bp1---isReady---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:37:19.010857+0800 OperationDemo[84411:1940218] bp1---isExecuting---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:37:19.011126+0800 OperationDemo[84411:1940219] bp2---isExecuting---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 16:37:19.011134+0800 OperationDemo[84411:1940218] bp1 before main
2018-04-18 16:37:19.011143+0800 OperationDemo[84411:1940220] bp2 complete
2018-04-18 16:37:19.011229+0800 OperationDemo[84411:1940218] enter bp1
2018-04-18 16:37:19.011233+0800 OperationDemo[84411:1940219] bp2---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:37:19.011458+0800 OperationDemo[84411:1940219] bp2 after start
2018-04-18 16:37:22.011382+0800 OperationDemo[84411:1940218] leave bp1
2018-04-18 16:37:22.011571+0800 OperationDemo[84411:1940218] bp1 after main
2018-04-18 16:37:22.012029+0800 OperationDemo[84411:1940218] bp1---isExecuting---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 16:37:22.012050+0800 OperationDemo[84411:1940219] bp1 complete
2018-04-18 16:37:22.012375+0800 OperationDemo[84411:1940218] bp1---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:37:22.013382+0800 OperationDemo[84411:1940218] bp1 after start

當(dāng)為bp1添加bp2作為依賴以后哪雕,bp1的 isReady 屬性從YES置為NO。

由于bp2是bp1的依賴鲫趁,所以優(yōu)先執(zhí)行bp2斯嚎。

在bp2中任務(wù)完成之后,-main 方法調(diào)用結(jié)束之后挨厚, -start 方法調(diào)用結(jié)束之前堡僻,bp1調(diào)用 -start 并將 isReady 屬性置為YES。

其他行為與單個調(diào)用時基本一致疫剃。

我們再來看看當(dāng)bp1添加bp2作為依賴钉疫,并且在調(diào)用之前bp2調(diào)用 -cancel 時的狀態(tài)變化,代碼基本一致巢价,唯一變化是在添加在q之前bp2調(diào)用 -cancel牲阁,我就不放代碼了固阁,直接看日志輸出:

2018-04-18 16:39:38.612072+0800 OperationDemo[84462:1944038] bp1 before addDependency:
2018-04-18 16:39:38.612500+0800 OperationDemo[84462:1944038] bp1---isReady---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 16:39:38.612712+0800 OperationDemo[84462:1944038] bp1 after addDependency:
2018-04-18 16:39:38.613460+0800 OperationDemo[84462:1944038] bp2 before cancel
2018-04-18 16:39:38.613984+0800 OperationDemo[84462:1944038] bp2---isCancelled---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:39:38.614337+0800 OperationDemo[84462:1944038] bp2---isReady---{
    kind = 1;
    new = 1;
    old = 1;
}
2018-04-18 16:39:38.614512+0800 OperationDemo[84462:1944038] bp2 after cancel
2018-04-18 16:39:38.614804+0800 OperationDemo[84462:1944152] bp2 before start
2018-04-18 16:39:38.615286+0800 OperationDemo[84462:1944158] bp1 before start
2018-04-18 16:39:38.615321+0800 OperationDemo[84462:1944152] bp1---isReady---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:39:38.615614+0800 OperationDemo[84462:1944158] bp1---isExecuting---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:39:38.615629+0800 OperationDemo[84462:1944150] bp2 complete
2018-04-18 16:39:38.615661+0800 OperationDemo[84462:1944152] bp2---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:39:38.616030+0800 OperationDemo[84462:1944158] bp1 before main
2018-04-18 16:39:38.616115+0800 OperationDemo[84462:1944152] bp2 after start
2018-04-18 16:39:38.616132+0800 OperationDemo[84462:1944158] enter bp1
2018-04-18 16:39:41.618815+0800 OperationDemo[84462:1944158] leave bp1
2018-04-18 16:39:41.619170+0800 OperationDemo[84462:1944158] bp1 after main
2018-04-18 16:39:41.619551+0800 OperationDemo[84462:1944152] bp1 complete
2018-04-18 16:39:41.619591+0800 OperationDemo[84462:1944158] bp1---isExecuting---{
    kind = 1;
    new = 0;
    old = 1;
}
2018-04-18 16:39:41.619941+0800 OperationDemo[84462:1944158] bp1---isFinished---{
    kind = 1;
    new = 1;
    old = 0;
}
2018-04-18 16:39:41.620073+0800 OperationDemo[84462:1944158] bp1 after start

與單個Operation調(diào)用 -cancel 行為一致,不影響 -start 的調(diào)用城菊,同樣不會調(diào)用 -main备燃,不同點是在bp1調(diào)用 -start 之前 isReady 屬性會被置為YES,之后行為與單個Operation調(diào)用 -start 一致凌唬。

上述行為可以用一張流程圖來表現(xiàn):


Operation流程

重寫子類

通過觀察上述的日志我們可以看出赚爵,當(dāng)一個任務(wù)作為另一個任務(wù)的依賴時,只有當(dāng)被依賴的任務(wù)完成后法瑟,才會執(zhí)行另一個任務(wù),而這個時間點的時候唁奢,executing霎挟、finished兩個屬性會發(fā)生變化。那我們需要做的就是實現(xiàn)一個NSOperation的子類麻掸,讓他可以再我們需要的時候才被標(biāo)記為完成狀態(tài)酥夭,這樣,我們只要給刷新列表任務(wù)添加網(wǎng)絡(luò)請求任務(wù)作為依賴即可脊奋。所以熬北,我們需要做的只有兩件事,就是接過executing诚隙、finished兩個屬性的管理權(quán)以及在我們需要的時候改變他們的狀態(tài)讶隐。

需求知道了,實現(xiàn)就很簡單了久又。老司機(jī)直接就放一份簡單的源碼就好了巫延。

@class DWManualOperation;
typedef void(^OperationHandler)(DWManualOperation * op);
@interface DWManualOperation : NSOperation

/**
 以需要實現(xiàn)的任務(wù)生成operation對象

 @param handler 需要實現(xiàn)的任務(wù)
 @return operation實例
 
 */
+(instancetype)manualOperationWithHandler:(OperationHandler)handler;

/**
 立刻將當(dāng)前任務(wù)標(biāo)識為完成狀態(tài),isExecuting 為 NO,isFinished 為 YES地消。
 */
-(void)finishOperation;

@end

@interface DWManualOperation ()

@property (nonatomic ,assign ,getter=isFinished) BOOL finished;

@property (nonatomic ,assign ,getter=isExecuting) BOOL executing;

@property (nonatomic ,copy) OperationHandler handler;

@property (nonatomic ,strong) DWManualOperation * cycleSelf;

@end

@implementation DWManualOperation
@synthesize finished = _finished;
@synthesize executing = _executing;

#pragma mark --- interface method ---
+(instancetype)manualOperationWithHandler:(OperationHandler)handler {
    DWManualOperation * op = [DWManualOperation new];
    if (handler) {
        op.handler = handler;
    }
    return op;
}

-(void)finishOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    _finished = YES;
    _executing = NO;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

#pragma mark --- override ---
-(instancetype)init {
    if (self = [super init]) {
        _concurrentHandler = YES;
        self.completionBlock = nil;
    }
    return self;
}

-(void)start {
    NSLog(@"start");
    ///如果是被取消狀態(tài)則置為完成狀態(tài)并返回炉峰,為了配合NSOperationQueue使用
    if (self.isCancelled) {
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    if (self.isExecuting || self.isFinished) {///正在執(zhí)行或已經(jīng)完成的任務(wù)不可以調(diào)用開始方法。
        return;
    }
    self.cycleSelf = self;
    [super start];
}

-(void)cancel {
    [super cancel];
}

-(void)main {///系統(tǒng)實現(xiàn)中 -start 方法中會調(diào)用 -main 方法
    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [super main];
    __weak typeof(self)weakSelf = self;
    if (self.handler) {
        self.handler(weakSelf);
    }
}

-(void)dealloc {
    NSLog(@"dealloc");
}

#pragma mark --- tool func ---
static inline void freeOperation(DWManualOperation * op) {
    op.cycleSelf = nil;
}

#pragma mark --- setter/getter ---

-(void)setCompletionBlock:(void (^)(void))completionBlock {
    __weak typeof(self)weakSelf = self;
    dispatch_block_t ab = ^(void) {
        if (completionBlock) {
            completionBlock();
        }
        freeOperation(weakSelf);
    };
    [super setCompletionBlock:ab];
}

@end

吶脉执,寫到這里疼阔,我們就基本實現(xiàn)了一個跟系統(tǒng)Operation具有相同行為,但是我們可以隨意控制Operation是否完成的子類了半夷。


條件模塊

不知道該叫什么我就隨便起了個名婆廊,其實就是一個應(yīng)用,場景就是操作A一定要建立在某種狀態(tài)下才能執(zhí)行玻熙。最簡單的就是比如點贊功能必須是登錄后才可進(jìn)行否彩,那么我們就要對這種狀態(tài)做出判斷。如下圖:

條件模塊

你可能說這無非就是一個判斷的事嗦随,的確是列荔,不過像登錄狀態(tài)這種很多地方都要用的功能這樣寫也能很好的復(fù)用敬尺。這個思路能主要還是借鑒的大神Delpan的這篇博客:《操作抽象設(shè)計-實踐》,寫的很好贴浙,同學(xué)們感興趣可以去看看砂吞。

Demo傳送門


請求類封裝

吶,寫到這里其實就只是講思路了崎溃,至此我們已經(jīng)具有了一個可以控制完成時機(jī)的Operation了蜻直,只要我們將網(wǎng)絡(luò)請求與Operation同時 -start 后,請求回調(diào)結(jié)束后標(biāo)志Operation為完成狀態(tài)后就可以為請求添加依賴了袁串,同時也可以配合系統(tǒng)的其他Operation和Queue同時使用完成線程間通信概而。

說到這要是就結(jié)束了那就太虎頭蛇尾了,而且真愛粉們應(yīng)該知道囱修,一般到這個時候就是老司機(jī)的軟廣環(huán)節(jié)了赎瑰,著急的童鞋們可以關(guān)掉瀏覽器了哈~

2.jpg

老司機(jī)給予這個思路對AFN進(jìn)行了二次封裝,寫了一個自用的請求框架DWFlashFlow破镰。

首先它具有NSOperation的所有特性餐曼,可以跟普通Operation結(jié)合在一起使用,其次我還封裝了批量請求和請求量功能鲜漩,并且在功能層和邏輯層上進(jìn)行了分離源譬,也就是說你可以自由更換你的請求核心類,而邏輯層不變~哎孕似,最近都不會吹牛逼了踩娘,剩下的東西喜歡的同學(xué)自己看吧~

傳送門:DWFlashFlow

喜歡的童靴給點個小星星唄~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喉祭,隨后出現(xiàn)的幾起案子霸饲,更是在濱河造成了極大的恐慌,老刑警劉巖臂拓,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厚脉,死亡現(xiàn)場離奇詭異,居然都是意外死亡胶惰,警方通過查閱死者的電腦和手機(jī)傻工,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孵滞,“玉大人中捆,你說我怎么就攤上這事》蝗模” “怎么了泄伪?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匿级。 經(jīng)常有香客問我蟋滴,道長染厅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任津函,我火速辦了婚禮肖粮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尔苦。我一直安慰自己涩馆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布允坚。 她就那樣靜靜地躺著魂那,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠项。 梳的紋絲不亂的頭發(fā)上冰寻,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音皿渗,去河邊找鬼。 笑死轻腺,一個胖子當(dāng)著我的面吹牛乐疆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贬养,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼挤土,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了误算?” 一聲冷哼從身側(cè)響起仰美,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儿礼,沒想到半個月后咖杂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚊夫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年诉字,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片知纷。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡壤圃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琅轧,到底是詐尸還是另有隱情伍绳,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布乍桂,位于F島的核電站冲杀,受9級特大地震影響效床,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漠趁,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一扁凛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闯传,春花似錦谨朝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至共缕,卻和暖如春洗出,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背图谷。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工翩活, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人便贵。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓菠镇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親承璃。 傳聞我的和親對象是個殘疾皇子利耍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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