NSOperation

概述

iOS并发编程中,把每个并发任务定义为一个Operation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。

方法结构

NSOperation

把逻辑代码写在NSOperation中,就是把逻辑代码添加一层壳,执行NSOperation就是间接的执行逻辑代码。

  • 用来定义操作对象的基础(抽象)类。处理并发任务时,具体子类通常要重写main、isConcurrent、isExecuting 、isFinished方法。
  • Operation默认都是串行操作(FIFO),默认情况下Operation并不额外创建线程。
  • 启动一个Operation任务
    如果希望拥有更多的控制权,以及在一个操作中可以执行异步任务,那么就重写 start 方法。
    如果重写 start 方法,你必须手动管理操作的状态。 为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现。
- (void)start;
  • 取消一个Operation
    如果你在main方法中没有对cancel进行任何处理的话,发送cancel消息是没有任何效果的。为了让Operation响应cancel消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled属性,如果返回YES的话,应释放相关资源并立刻停止继续执行。
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
  • 用来执行你所想要执行的任务
    可以通过重写 main 方法 来定义自己的 operations,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。
- (void)main;
  • 判断Operation是否是可并发的
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous API_AVAILABLE(macos(10.8), ios(7.0), watchos(2.0), tvos(9.0));
  • 查看、添加、删除 操作依赖
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

下面就是intermediateOperation操作必须等到operation1operation2 完成后才能执行。

[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
  • Operation执行完成时自动执行completionBlock。可以在此进行一些完成的处理。
    每个Operation都可以设置一个completionBlock。
@property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
  • 线程优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;
  • 可以通过KVO监听Operation的一下状态改变的Key
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock

NSInvocationOperation

  • 初始化:
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

- (instancetype)initWithInvocation:(NSInvocation *)inv;
  • 变量
@property (readonly, retain) NSInvocation *invocation;
//个Operation完成后返回结果
@property (nullable, readonly, retain) id result;

NSBlockOperation

  • 初始化:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

Operation操作流程

一些公共方法:

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

-(void)logOperation:(NSOperation *)op keyPathes:(NSArray *)keyPathes {
    [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@ %@ = %@",op.name,obj,[[op valueForKey:obj] boolValue]?@"YES":@"NO");
    }];
}

-(void)addObserverForOperation:(id)op keyPathes:(NSArray *)keyPathes {
    [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [op addObserver:self forKeyPath:obj options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
    }];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSOperation class]]) {
        NSLog(@"observeValueForKeyPath:%@---%@---%@",[object name],keyPath,change);
    }
}
  • 先执行start,后执行cancel
-(void)operationKeysChange{
    TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter op");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave op");
    }];
    op.name = @"op";
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    [self logOperation:op keyPathes:keyPathes];
    [self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
        [self operationComplete];
    };
    [op start];
    [op cancel];
}

初始状态下,ready为YES,其他均为NO。
当我们调用 -start 后,执行 -main 之前 isExecuting 属性从NO被置为YES。
调用 -main 之后开始执行提交到Operation中的任务。
任务完成后 isExecuting 属性从YES被置为NO,isFinished 属性从NO被置为YES。

op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before start
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 1;old = 0;}
op before main
enter bp1
leave bp1
/*这里执行业务代码,完成后才进行下面的操作*/
op after main
All task finished.
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
op before cancel
op after cancel
  • 先执行cancel,后执行start
-(void)operationKeysChange{
    TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter op");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave op");
    }];
    op.name = @"op";
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    [self logOperation:op keyPathes:keyPathes];
    [self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
        [self operationComplete];
    };
    [op cancel];
    [op start];
}

先调用 -start ,后调用 -cancel ,isCancelled 属性从NO被置为YES,isReady 属性无论什么状态都会被置为YES。
先调用 -start ,后调用 -cancel ,会将 isFinished 属性从NO被置为YES,然后并不调用 -main 方法。

op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before cancel
observeValueForKeyPath:op---isCancelled---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:op---isReady---{kind = 1;new = 1;old = 1;}
op after cancel
op before start
All task finished.
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
  • NSOperationQueue 但是没有依赖
-(void) operationKeysChange {
    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 logOperation:bp1 keyPathes:keyPathes];
    [self logOperation:bp2 keyPathes:keyPathes];
    [self addObserverForOperation:bp1 keyPathes:keyPathes];
    [self addObserverForOperation:bp2 keyPathes:keyPathes];
    
    NSOperationQueue * q = [NSOperationQueue new];
    [q addOperation:bp1];
    [q addOperation:bp2];
}

NSOperationQueue 执行顺序和NSOperation一样


bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO

bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO

bp1 before start
bp2 before start

observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}

bp1 before main
bp2 before main

enter bp1
enter bp2

leave bp1
leave bp2

bp2 after main
bp1 after main

bp2 complete
bp1 complete

observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}

observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}

bp1 after start
bp2 after start
  • NSOperationQueue 但是有依赖
-(void) operationKeysChange {
    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 logOperation:bp1 keyPathes:keyPathes];
    [self logOperation:bp2 keyPathes:keyPathes];
    [self addObserverForOperation:bp1 keyPathes:keyPathes];
    [self addObserverForOperation:bp2 keyPathes:keyPathes];
    
    NSOperationQueue * q = [NSOperationQueue new];
    [bp1 addDependency:bp2];
    [q addOperation:bp1];
    [q addOperation:bp2];
}

当为bp1添加bp2作为依赖以后,bp1的 isReady 属性从YES置为NO。
由于bp2是bp1的依赖,所以优先执行bp2。
在bp2中任务完成之后,-main 方法调用结束之后, -start 方法调用结束之前,bp1调用 -start 并将 isReady 属性置为YES。

bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO

bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO

bp1 before addDependency:
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 0;old = 1;}
bp1 after addDependency:

bp2 before start
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 before main
enter bp2
leave bp2
bp2 after main
bp1 before start
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 complete
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
bp1 before main
enter bp1
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
bp2 after start
leave bp1
bp1 after main
bp1 complete
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
bp1 after start
NSOperation流程
  • 通过上面的流程图自定义实现简单的并发操作
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation{
    BOOL isFinished;//监听是否执行结束
    BOOL isExecuting;//监听是否正在执行
}

/*1.自定义初始化方法*/
-(instancetype)init{
    if (self == [super init]) {
        isExecuting = NO;
        isFinished = NO;
    }
    return self;
}
-(void)start{
    //如果被取消了就直接返回结果,不会执行main方法
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        isFinished = NO;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    //没有被取消,使用独立线程执行main方法中的操作
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];
    
}

/*2.自定义辅助方法*/
-(void)main{
    @autoreleasepool{//使用独立的内存释放池,不然会内存泄漏
        @try {
            if (![self isCancelled]) {
                NSLog(@"Begin%@",[NSThread currentThread]);
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"End%@",[NSThread currentThread]);
                //任务结束,修改状态值
                [self willChangeValueForKey:@"isFinished"];
                [self willChangeValueForKey:@"isExecuting"];
                isExecuting = NO;
                isFinished = YES;
                [self didChangeValueForKey:@"isExecuting"];
                [self willChangeValueForKey:@"isFinished"];

            }
        } @catch (NSException *exception) {
        } @finally {
        }
    }
}

-(BOOL)isConcurrent{
    return YES;
}

-(BOOL)isExecuting{
    return isExecuting;
}

-(BOOL)isFinished{
    return isFinished;
}
@end

使用:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"begin func");
    SAMOperation *op1 = [SAMOperation new];
    [op1 start];
    NSLog(@"end func");
}

NSOperationQueue

  • 两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。
  • NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。
  • NSOperationQueue 可以动态的创建多个线程来完成相应Operation,总线程数量通过maxConcurrentOperationCount属性来控制。
  • 当操作添加到队列中,它会待在队列中,直到被显式取消或者执行完为止。
  • 创建队列
// 主队列队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
  • 设置最大并发数
    maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
    maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
    maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
@property NSInteger maxConcurrentOperationCount;
  • 取消操作
- (void)cancelAllOperations;

NSOperationQueue 就相当于管道,Operation以FIFO的形式通过管道,maxConcurrentOperationCount 就是管道数量。下面的demo就是限制总管道数量为1,也就是所有的Operation必须以FIFO形式通过管道,也就是串行。

- (void)demo {
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2-%@",[NSThread currentThread]);
    }];
    operation2.completionBlock = ^{NSLog(@"operation2-completionBlock-%@",[NSThread currentThread]);};
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation3-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue waitUntilAllOperationsAreFinished];
}

执行结果:从结果可以看出,maxConcurrentOperationCount = 1 相当于在addOperation的时候就设置了操作之间的依赖。

operation1-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation2-<NSThread: 0x604000270540>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation3-<NSThread: 0x604000270540>{number = 4, name = (null)}

设置queue.maxConcurrentOperationCount = 2;的执行结果

operation2-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation1-<NSThread: 0x600000474140>{number = 3, name = (null)}
operation3-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x600000474340>{number = 5, name = (null)}
  • 添加Operation的三种方式
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
  • 简单的自定义Operation
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation
-(void)main{
    @autoreleasepool{
        @try {
            if (![self isCancelled]) {
                NSLog(@"Begin");
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"End");
            }
        } @catch (NSException *exception) {
        } @finally {
        }
    }
}
@end

使用:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"begin func");
    SAMOperation *op1 = [SAMOperation new];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:op1];
    //[queue waitUntilAllOperationsAreFinished];
    NSLog(@"end func");
}

打印

begin func
end func
Begin
End

//把[queue waitUntilAllOperationsAreFinished];注释打开的打印
begin func
Begin
End
end func

线程安全和 线程同步

@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@property (nonatomic, strong)NSLock *lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketSurplusCount = 50;
    self.lock = [[NSLock alloc] init];
    
    [self initTicketStatusNotSave];
}

/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    __weak typeof(self) weakSelf = self;
    
    // queue1 代表北京火车票售卖窗口
    NSOperationQueue *queueO1 = [[NSOperationQueue alloc]init];
    queueO1.maxConcurrentOperationCount = 1;
    
    [queueO1 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"北京"];
    }];
    // queue2 代表上海火车票售卖窗口
    NSOperationQueue *queueO2 = [[NSOperationQueue alloc]init];
    queueO2.maxConcurrentOperationCount = 1;
    [queueO2 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"上海"];
    }];
    
    // queue2 代表上海火车票售卖窗口
    NSOperationQueue *queueO3 = [[NSOperationQueue alloc]init];
    queueO3.maxConcurrentOperationCount = 1;
    [queueO3 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"深圳"];
    }];
}


/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe:(NSString*)who{
    while (1) {
        [self.lock lock];
        if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@====%@", (long)self.ticketSurplusCount,who, [NSThread currentThread]]);
            //[NSThread sleepForTimeInterval:0.2];
            [self.lock unlock];
        } else { //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
             [self.lock unlock];
            break;
        }
    }
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容