iOS多线程 - NSOperation详解

前言

本来想对NSOperation进行比较详细的说明,但是刚好看到一篇文章,介绍的比较好这里我就不再去介绍NSOperation,对于NSOperation的介绍可以参考:

iOS 多线程:『NSOperation、NSOperationQueue』详尽总结

这边我通过一些例子和实战演练来介绍NSOperation:

介绍和优点

NSOperation是一个抽象类,不能直接使用它,而是使用系统定义的子类NSInvocationOperationNSBlockOperation执行实际任务。尽管是抽象的,但基本实现确实包含了协调安全执行任务的重要逻辑。这种内置逻辑的存在使您可以专注于任务的实际实现,而不是确保它与其他系统对象正常工作所需的粘合代码。
NSOperation是对GCD的封装的面向对象,因此比起GCD的灵活性,封装后使用上可能相对方便,缺也有不少的局限性。
那么具体有哪些优点呢?

1、确定执行的顺序

NSOperation与GCD一样,都可以增加优先级queuePriority,会影响到出列和执行block的顺序,但是影响的只是执行顺序开始而已,并不代表执行顺序结束会按照优先级来。比如,两个人吃苹果,你优先级高, 你先开始吃,我后面吃,但是不代表你比我早吃完,
这就表示,优先级只能用来对非依赖性的操作进行分类,而不是用来对不同对象之间的依赖关系管理。其优先级如下:默认NSOperationQueuePriorityNormal

NSOperationQueuePriorityVeryLow
NSOperationQueuePriorityLow
NSOperationQueuePriorityNormal
NSOperationQueuePriorityHigh
NSOperationQueuePriorityVeryHigh

NSOperation提供给用户管理队列执行依赖顺序的方式addOperation,用此方法添加依赖关系。就可以确保来任务执行顺序。例如我依赖你,你先吃苹果,你吃完我才开始吃苹果。

注意: addOperation的对象执行的时候,不能再被依赖的对象执行之前。同时也不能添加相互依赖,否则会造成死锁。

例如下代码,operation1 依赖于operation2 但是operation1 被先执行,直接崩溃

NSOperationQueue *queue1 = [NSOperationQueue new];
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
[operation1 addDependency:operation2];
[operation1 start];
[operation2 start];

例如下代码,两个之间添加相互依赖不管谁先执行都会死锁。

NSOperationQueue *queue1 = [NSOperationQueue new];
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
[operation1 addDependency:operation2];
[operation2 addDependency:operation1];
[operation1 start];
[operation2 start];
2、响应取消命令

Operation完成任务并不意味着完成了任务,操作也能取消。取消操作对象会将对象留在队列中,但会通知对象它应该尽快停止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止它正在执行的操作,并将自己标记为已完成。对于排队但尚未执行的操作,队列仍必须调用操作对象的start方法,以便它可以处理取消事件并将自身标记为已完成。
取消操作不会立即强制它停止正在进行的操作。开发人员自己去判断isCancelled并根据需要中止。默认实现NSOperation包括检查取消。例如,如果在start调用其方法之前取消操作,则该start方法将退出而不启动任务。
举几个例子,当还没开始执行时,取消操作会直接不执行:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"开始");
    NSOperationQueue *queue1 = [NSOperationQueue new];
    _operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    [queue1 addOperation:_operation1];
    [_operation1 cancel];
    NSLog(@"结束");
}

- (void)test {
    if (_operation1.isCancelled) {
        NSLog(@"取消了");
    } else {
        NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
    }
}
2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束

当正在进行中的操作时,取消操作需要手动结束

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"开始");
    NSOperationQueue *queue1 = [NSOperationQueue new];
    _operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    [queue1 addOperation:_operation1];
    sleep(2);
    [_operation1 cancel];
    NSLog(@"结束");
}

- (void)test {
    sleep(3);
    if (_operation1.isCancelled) {
        NSLog(@"取消了");
    } else {
        NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
    }
}
2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束
2019-04-25 15:54:55.376668+0800 test_1[23375:1924399] 取消了
3、符合KVO的属性

NSOperation的属性与KVO和KVC兼容并有其性质。这就表示,这些属性不管在哪个线程都是实时变化的,又KVO通知其变化并改变状态。根据用户的操作和系统运行。让用户可以对线程的变化做响应处理。

isCancelled - 只读
// 让用户知道被请求的操作的取消,需要用户手动处理

isAsynchronous - 只读
// 是否是异步执行

isExecuting - 只读
// 让用户了解操作是否正在积极分配给它的任务。

isFinished - 只读
// 让用户知道操作对象成功完成了它的任务或遭到取消,正在退出。在key更改为之前,操作对象不会清除依赖关系。
// 因此在该值为true之前对于保持队列不通过正在进行或取消的操作进行备份至关重要。

isReady - 只读
// 让用户知道,当一个操作已准备好执行。该属性包含操作现在可以立即执行的值,或者是否仍有未完成的操作依赖于该操作。

dependencies - 只读
// 在当前对象开始执行之前必须完成执行的操作对象数组。

queuePriority - 可读和可写
// 队列等级

completionBlock - 可读和可写
// 操作主要任务完成后执行的块。

线程之间的通信和同步

同步和异步线程

NSOperation子类NSInvocationOperationNSBlockOperation,本身是不具备开辟线程的能力,并且只执行一次。

NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
NSLog(@"结束");
2019-04-25 16:38:10.577710+0800 test_1[23677:1936783] 开始
2019-04-25 16:38:10.577894+0800 test_1[23677:1936783] 当前的线程为--<NSThread: 0x280b41a80>{number = 1, name = main}
2019-04-25 16:38:10.577912+0800 test_1[23677:1936783] 结束
NSLog(@"开始");
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[operation start];
NSLog(@"结束");
2019-04-25 16:39:12.363320+0800 test_1[23704:1937254] 开始
2019-04-25 16:39:12.363537+0800 test_1[23704:1937254] 当前的线程为--<NSThread: 0x280eb9a80>{number = 1, name = main}
2019-04-25 16:39:12.363556+0800 test_1[23704:1937254] 结束

两种方式结果是一样的,都是在主线程执行,并且是串行的方式。
想要实现开辟新线程的能力,则需要与队列NSOperationQueue结合使用。

NSLog(@"开始");
NSOperationQueue *queue1 = [NSOperationQueue new];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"当前的线程为--%@", [NSThread currentThread]);
}];
[queue1 addOperation:operation];
NSLog(@"结束");
2019-04-25 16:43:22.327504+0800 test_1[23752:1938565] 开始
2019-04-25 16:43:22.327656+0800 test_1[23752:1938565] 结束
2019-04-25 16:43:22.327763+0800 test_1[23752:1938650] 当前的线程为--<NSThread: 0x2823ecd00>{number = 3, name = (null)}
使用 addExecutionBlock 和 maxConcurrentOperationCount

NSBlockOperation提供一个方式addExecutionBlock,可以用这种方式可以实现线程安全,即能让程序在线程中串行执行的同时又有开辟新线程的能力:

NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
    NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
    NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
    NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
NSLog(@"结束");
2019-04-25 16:44:47.825744+0800 test_1[23773:1939191] 开始
2019-04-25 16:44:47.825935+0800 test_1[23773:1939191]  1 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
2019-04-25 16:44:47.825953+0800 test_1[23773:1939235]  2 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
2019-04-25 16:44:47.825961+0800 test_1[23773:1939191]  3 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
2019-04-25 16:44:47.825981+0800 test_1[23773:1939235]  4 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
2019-04-25 16:44:47.826010+0800 test_1[23773:1939191] 结束
使用 addOperationWithBlock 和 maxConcurrentOperationCount

NSOperationQueue 提供了方法addOperationWithBlock 可以用来实现异步队列,提供了maxConcurrentOperationCount用来设置最大并发数,通过这些可以实现GCD的并发请求的控制。

NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSLog(@"结束");
同步队列

我们就以NSBlockOperation方式为例来实现,实现串行同步。

NSLog(@"开始");
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
[operation start];
[operation2 start];
[operation3 start];
[operation4 start];
NSLog(@"结束");
2019-04-25 17:21:09.496324+0800 test_1[24021:1954586] 开始
2019-04-25 17:21:09.496545+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496579+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496599+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496615+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
2019-04-25 17:21:09.496625+0800 test_1[24021:1954586] 结束
串行异步

同样的使用NSOperationQueue 来实现异步方式。
第一种方式:

NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 0 当前的线程为--%@", [NSThread currentThread]);
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation start];
    [operation2 start];
    [operation3 start];
    [operation4 start];
}];
[queue addOperation:op];
NSLog(@"结束");
2019-04-25 17:30:43.933648+0800 test_1[24080:1958061] 开始
2019-04-25 17:34:04.869315+0800 test_1[24095:1959001] 结束
2019-04-25 17:30:43.933959+0800 test_1[24080:1958076]  0 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934065+0800 test_1[24080:1958076]  1 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934100+0800 test_1[24080:1958076]  2 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934118+0800 test_1[24080:1958076]  3 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
2019-04-25 17:30:43.934134+0800 test_1[24080:1958076]  4 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}

第二种方式通过addOperationWithBlock,并且控制maxConcurrentOperationCount最大并发数为1

 NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
for (int i=0; I<5; i++) {
    [queue addOperationWithBlock:^{
        NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
    }];
}
NSLog(@"结束");
2019-04-26 11:33:45.983327+0800 test_1[27906:2321076] 开始
2019-04-26 11:33:45.983518+0800 test_1[27906:2321076] 结束
2019-04-26 11:33:45.983595+0800 test_1[27906:2321143]  0 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983700+0800 test_1[27906:2321143]  1 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983738+0800 test_1[27906:2321143]  2 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983761+0800 test_1[27906:2321143]  3 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
2019-04-26 11:33:45.983785+0800 test_1[27906:2321143]  4 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}

第三种方式,利用NSOperationQueueaddDependency相互依赖的方式,但是这种方式虽然是异步串行缺不一定在同一个异步线程中:

NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[operation4 addDependency:operation3];
[operation3 addDependency:operation2];
[operation2 addDependency:operation];
[queue addOperation:operation];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
NSLog(@"结束");
2019-04-26 11:40:32.095244+0800 test_1[27947:2323900] 开始
2019-04-26 11:40:32.095411+0800 test_1[27947:2323900] 结束
2019-04-26 11:40:32.095888+0800 test_1[27947:2323953]  1 当前的线程为--<NSThread: 0x2838f7300>{number = 3, name = (null)}
2019-04-26 11:40:32.096086+0800 test_1[27947:2323955]  2 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
2019-04-26 11:40:32.096119+0800 test_1[27947:2323955]  3 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
2019-04-26 11:40:32.096144+0800 test_1[27947:2323955]  4 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
并行异步

通过队列NSOperationQueue 将多个Operation实现并行:

NSLog(@"开始");
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
}];
[queue addOperation:operation];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
NSLog(@"结束");
2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
2019-04-25 17:37:21.283182+0800 test_1[24110:1959950]  1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
2019-04-25 17:37:21.283216+0800 test_1[24110:1959949]  4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
2019-04-25 17:37:21.283221+0800 test_1[24110:1959948]  2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
2019-04-25 17:37:21.283243+0800 test_1[24110:1959951]  3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}

如果使用addOperationWithBlock,并且取消掉最大并发数maxConcurrentOperationCount,或者设置最大并发数大于1,则就是并行队列了。

NSLog(@"开始");
NSOperationQueue *queue = [NSOperationQueue new];
for (int i=0; i<20; i++) {
    [queue addOperationWithBlock:^{
        NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
    }];
}
NSLog(@"结束");
2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
2019-04-25 17:37:21.283182+0800 test_1[24110:1959950]  1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
2019-04-25 17:37:21.283216+0800 test_1[24110:1959949]  4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
2019-04-25 17:37:21.283221+0800 test_1[24110:1959948]  2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
2019-04-25 17:37:21.283243+0800 test_1[24110:1959951]  3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}
异步执行回到主线程更新UI
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
    // 异步进行耗时操作
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
    
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // 进行一些 UI 刷新等操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}];
2019-04-26 17:53:20.178591+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
2019-04-26 17:53:22.184004+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
2019-04-26 17:53:24.185889+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}
2019-04-26 17:53:26.187373+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容