Working with Blocks - -Block的使用

在編程領(lǐng)域里噪裕,一個牛逼程序員和一個二逼程序員之間的區(qū)別主要是其對所用編程語言優(yōu)秀特性的運(yùn)用方式。要說到Objective-C語言時怎虫,那么一般開發(fā)者和大牛的區(qū)別可能就是對Block書寫代碼的運(yùn)用能力了暑认。

Block編程并不是Objective-C語言獨(dú)創(chuàng)的一個編程方式,Block也同時也以其他的命名方式存在于其他的編程語言中大审,例如在Javascript中閉包蘸际;Block首次于iOS 4.0版本中引入,其后便被廣泛地接受和運(yùn)用徒扶。在隨后的iOS版本中粮彤,為了適用Block,Apple重寫了很多的framework方法姜骡。似乎Block在一定程度上已經(jīng)成為了未來的一種編程方式导坟。但是Block到底是什么呢?

Block是什么

Block是一種添加到C圈澈、Objective-C和C++語言中的一個語言層面的特性惫周,它允許您創(chuàng)建不同的代碼段,并像值一樣的傳遞到方法或函數(shù)中康栈。Block是一個Objective-C對象递递,這就意味著其可以被保存在NSArray或者NSDictionary中,Block還能夠在自己的封閉作用域中截獲到值(即所謂的變量截獲)啥么,Block其實和其他編程語言中的closure(閉包)或lambda是很類似的登舞。

Block 語法

blocks.jpg

在定義Block的語法中我們使用脫字符(^)來標(biāo)識這是一個Block,如下所示:

    ^{
         NSLog(@"This is a block");
    }

與函數(shù)和方法定義一樣饥臂,大括號同時也代表著Block的開始與結(jié)束逊躁。 在這個例子中,Block不返回任何值隅熙,并且不接受任何參數(shù)。

與通過使用函數(shù)指針來引用C函數(shù)的類似方式核芽,你也可以通過聲明一個變量來記錄Block囚戚,如:

void (^simpleBlock)(void);

如果你對處理C語言的函數(shù)指針不熟悉,那么上面的這種語法看起來會有點(diǎn)讓人摸不著頭腦轧简。 上面的例子中聲明了一個名字為simpleBlock的變量维苔,用以引用一個沒有參數(shù)也沒有返回值的Block桨武,這意味著這個Block變量可以被最上面的Block所賦值蚂维,如下所示:

    simpleBlock = ^{
        NSLog(@"This is a block");
    };

這和任何其他變量賦值一樣命黔,所以語法上必須以大括號后面的分號作為結(jié)束。 您也可以將Block變量的聲明和賦值組合起來:

    void (^simpleBlock)(void) = ^{
        NSLog(@"This is a block");
    };

一旦Block被聲明且賦值后籍茧,您就可以調(diào)用Block了,調(diào)用方法如下:

simpleBlock();

注意:如果你試圖調(diào)用一個沒有被賦值過的Block變量,你的應(yīng)用會崩潰的分飞。

Block的參數(shù)和返回值

像方法和函數(shù)一樣,Block即接受參數(shù)也有返回值睹限;例如譬猫,一個返回兩個值乘積的Block變量:

double (^multiplyTwoValues)(double, double);

對應(yīng)于上面的Block變量,其相應(yīng)的Block應(yīng)該是這樣的:

    ^ (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }

firstValue和secondValue用于引用在調(diào)用Block時提供的值羡疗,就像任何函數(shù)定義一樣染服。 在此示例中,返回類型是從Block內(nèi)的return語句推斷的叨恨。

如果你喜歡柳刮,你可以通過在脫字符(^)和參數(shù)列表之間指定來使返回類型顯式地寫出:

    ^ double (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }

一旦你聲明和定義了Block,你就可以像調(diào)用函數(shù)那樣調(diào)用Block:

    double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, double secondValue) {
                                  return firstValue * secondValue;
                              };
 
    double result = multiplyTwoValues(2,4);
 
    NSLog(@"The result is %f", result);

Block可以截獲外部變量

除了包含可執(zhí)行代碼之外痒钝,Block還具有從其封閉的作用域內(nèi)截獲變量狀態(tài)的能力诚亚。

例如,如果在方法中聲明一個Block午乓,它可以截獲該方法作用域內(nèi)可訪問的任何變量的值站宗,如下所示:

- (void)testMethod {
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    testBlock();
}

在此示例中,anInteger是在Block之外聲明的一個變量益愈,但是Block卻在定義時截獲了變量的值梢灭。

    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();

Block截獲的變量的值沒有改變。 這意味著日志的輸出將顯示為:

Integer is: 42

這也意味著Block不能改變原始變量的值蒸其,甚至是截獲變量值(被截獲的變量變成了一個常量)敏释。

__block修飾的變量

當(dāng)一個Block被復(fù)制后(當(dāng)Block截獲到外部變量時,Block就會被復(fù)制到堆上)摸袁,__block聲明的棧變量的引用也會被復(fù)制到了堆里钥顽,復(fù)制完成之后,無論是棧上的Block還是剛剛產(chǎn)生在堆上的Block(棧上Block的副本)都會引用該變量在堆上的副本靠汁。

你可以像下面這樣重寫當(dāng)前的例子:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();

因為變量anInteger被聲明為一個__block變量蜂大,它的內(nèi)存地址與聲明中Block的變量地址是共享的。 這意味著日志輸出現(xiàn)在將顯示:

Integer is: 84

這同時也標(biāo)志著Block可以修改其變量的原始值蝶怔,如下所示:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
        anInteger = 100;
    };
 
    testBlock();
    NSLog(@"Value of original variable is now: %i", anInteger);

這次的輸出會是:

Integer is: 42

Value of original variable is now: 100

Block作為方法或函數(shù)的參數(shù)

前面的每個例子都是在定義之后會立即調(diào)用Block奶浦。 在日常代碼編寫中,通常將Block作為參數(shù)傳遞給函數(shù)或方法以在其他地方進(jìn)行調(diào)用踢星。 例如澳叉,您可以使用GCD在后臺調(diào)用Block,或者定義一個要重復(fù)調(diào)用任務(wù)的Block,例如枚舉集合時成洗。 并發(fā)和枚舉將在后面討論五督。

Block也用于回調(diào),即定義任務(wù)完成時要執(zhí)行的代碼瓶殃。 例如充包,您的應(yīng)用程序可能需要通過創(chuàng)建執(zhí)行復(fù)雜任務(wù)的對象(例如從Web服務(wù)請求信息)來響應(yīng)用戶操作。 因為任務(wù)可能需要很長時間碌燕,您應(yīng)該在任務(wù)發(fā)生時顯示某種進(jìn)度指示器(菊花)误证,然后在任務(wù)完成后隱藏該指示器(菊花)。

當(dāng)然修壕,你可以使用委托來完成這個任務(wù):你需要創(chuàng)建一個合適的委托協(xié)議愈捅,實現(xiàn)所需的方法,將你的對象設(shè)置為任務(wù)的委托慈鸠,然后等待蓝谨,一旦任務(wù)完成時它在你的對象上調(diào)用一個委托方法。

然而青团,Block可以讓這些更加容易譬巫,因為您可以在啟動任務(wù)時定義回調(diào)行為,如下所示:

- (IBAction)fetchRemoteInformation:(id)sender {
    [self showProgressIndicator];
 
    XYZWebTask *task = ...
 
    [task beginTaskWithCallbackBlock:^{
        [self hideProgressIndicator];
    }];
}

此示例調(diào)用一個方法來顯示進(jìn)度指示器(菊花)督笆,然后創(chuàng)建任務(wù)并指示它開始芦昔。 回調(diào)Block指定任務(wù)完成后要執(zhí)行的代碼; 在這種情況下,它只是調(diào)用一個方法來隱藏進(jìn)度指示器(菊花)娃肿。 注意咕缎,這個回調(diào)block截獲了self,以便能夠在調(diào)用時調(diào)用hideProgressIndicator方法料扰。 在截獲self時要小心凭豪,因為它很容易創(chuàng)建一個strong類型的循環(huán)引用,詳情見后面的如何在block截獲了self后避免循環(huán)引用晒杈。

在代碼可讀性方面嫂伞,該Block使得在一個位置上很容易看到在任務(wù)完成之前和完成之后會發(fā)生哪些情況,從而避免需要通過委托方法來查找將要發(fā)生的事情拯钻。

此示例中顯示的beginTaskWithCallbackBlock:方法的聲明如下所示:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;

(void(^)(void))上一個沒有參數(shù)沒有返回值的Block帖努。 該方法的實現(xiàn)可以以通常的方式調(diào)用Block:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
    ...
    callbackBlock();
}

Block作為方法的參數(shù),其所擁有的多個或一個參數(shù)在形式上應(yīng)與單純的Block變量相同:

- (void)doSomethingWithBlock:(void (^)(double, double))block {
    ...
    block(21.0, 2.0);
}

Block應(yīng)該始終作為方法的最后一個參數(shù)

如果方法中含有Block以及其他非Block的參數(shù)说庭, 那么Block參數(shù)應(yīng)該始終作為方法的最后一個參數(shù)寫出然磷,如:

- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;

這使得在指定Block內(nèi)聯(lián)時更容易讀取方法的調(diào)用,如下所示:

    [self beginTaskWithName:@"MyTask" completion:^{
        NSLog(@"The task is complete");
    }];

使用類型定義來簡化Block語法

如果需要使用相同的Block類型來定義多個Block刊驴,您可能需要為該類型進(jìn)行重新的定義。
例如,您可以為沒有參數(shù)沒有返回值的簡單Block定義類型(即為Block類型取一個別名):

typedef void (^XYZSimpleBlock)(void);

然后捆憎,可以使用自定義類型的Block作為方法的參數(shù)或用自定義類型來創(chuàng)建Block變量:

    XYZSimpleBlock anotherBlock = ^{
        ...
    };
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
    ...
    callbackBlock();
}

自定義類型定義在處理作為返回值的Block或?qū)⑵渌鸅lock用作參數(shù)的Block時特別有用舅柜。 請看以下示例:

void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
    ...
    return ^{
        ...
    };
};

complexBlock變量指的是將另一個Block作為參數(shù)(aBlock)并返回另一個Block的Block。
使用類型定義來重寫上面的代碼躲惰,這使的這段代碼更加可讀:

XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
    ...
    return ^{
        ...
    };
};

對象使用Block作為屬性

定義一個Block屬性的語法類似于聲明一個Block變量:

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

注意:您應(yīng)該將copy指定為屬性修飾符致份,變量被Block截獲后,會改變自身在內(nèi)存的位置础拨,由棧區(qū)變?yōu)槎褏^(qū)氮块,所以Block也需要將自己復(fù)制到堆區(qū),以應(yīng)對這種改變诡宗。 當(dāng)使用自動引用計數(shù)時滔蝉,你是不需要擔(dān)心的,因為它會自動發(fā)生的塔沃,但是屬性修飾符的最佳做法是顯示結(jié)果行為蝠引。 有關(guān)更多信息,請參閱Blocks Programming Topics蛀柴。

Block屬性的設(shè)置及調(diào)用和其他的Block變量是一樣的:

    self.blockProperty = ^{
        ...
    };
    self.blockProperty();

同時也可以使用類型定義的方式聲明一個Block屬性螃概,如下:

typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

<a id="no1"></a>如何在Block截獲了self后避免循環(huán)引用

如果在定義一個Block回調(diào)時,需要在Block中截獲self鸽疾,內(nèi)存管理的問題是需要引起重視的吊洼。

Block對任何截獲的對象都是強(qiáng)引用,包括self制肮;記住這一點(diǎn)后冒窍,想要解開循環(huán)引用就不是很難了,如下弄企,一個擁有Block屬性的對象超燃,在Block內(nèi)截獲了self

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
    self.block = ^{
        [self doSomething];    // Block對self是強(qiáng)引用的
                               // 這就產(chǎn)生了循環(huán)引用
    };
}
...
@end

像上面這樣的一個簡單例子中,編譯器是會在你編寫代碼時報警告的拘领;但是對于有多個強(qiáng)應(yīng)用對象在一起產(chǎn)生的循環(huán)引用問題意乓,編譯器是很難發(fā)現(xiàn)循環(huán)引用問題的:

為了避免出現(xiàn)這種問題,最好的方式是截獲一個弱引用的self约素,如下所示:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self; 
      
    //或__weak typeof(self) weakSelf = self;
    
    self.block = ^{
        [weakSelf doSomething];   // 截獲一個弱引用self
                                  // 以此來避免循環(huán)引用   
    }
}

通過在Block內(nèi)截獲了一個弱指針指向的self届良,這樣Block就不會再維持對XYZBlockKeeper對象的強(qiáng)引用關(guān)系了。如果對象在Block被調(diào)用之前釋放了圣猎,指針weakSelf就會被置為空士葫;

Block可以用來簡化枚舉

除了作為基本的回調(diào)使用外,許多的Cocoa 和 Cocoa Touch 框架的API也用Block來簡化任務(wù)送悔,如集合枚舉慢显。例如爪模,NSArray就提供了三個含有Block的方法:

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;

這個方法接受一個Block的參數(shù),這個參數(shù)對于數(shù)組中的每個項目調(diào)用一次:

    NSArray *array = ...
    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"Object at index %lu is %@", idx, obj);
    }];

上面的Block需要三個參數(shù)荚藻,前兩個參數(shù)指向當(dāng)前對象及其在數(shù)組中的索引屋灌。 第三個參數(shù)是一個指向布爾變量的指針,可以用來停止枚舉应狱,如下所示:

    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        if (...) {
            *stop = YES;
        }
    }];  

還可以使用enumerateObjectsWithOptions:usingBlock:方法自定義枚舉共郭。 例如,指定NSEnumerationReverse這一選項將會反向遍歷集合疾呻。

如果枚舉Block中的代碼是處理器密集型(processor-intensive)并且是安全的并發(fā)執(zhí)行 -- 您可以使用NSEnumerationConcurrent選項:

    [array enumerateObjectsWithOptions:NSEnumerationConcurrent
                            usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        ...
    }];
    

這個flag指示Block枚舉的調(diào)用可能會是多線程分布的除嘹,如果Block代碼是專門針對處理器密集型的,那么這樣做對性能會有潛在的提升。注意岸蜗,當(dāng)使用這個選項時尉咕,這個枚舉的順序是未定義的。

NSDictionary同時也提供一些基于Block的方法散吵,如下所示:

    NSDictionary *dictionary = ...
    [dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
        NSLog(@"key: %@, value: %@", key, obj);
    }];

如上面的例子所示:相比使用傳統(tǒng)的循環(huán)遍歷龙考,使用枚舉鍵值對的方式會更加方便,

Block可以用來簡化并發(fā)任務(wù)

每個Block代表一個不同的工作單元矾睦,就是可執(zhí)行代碼與Block周圍作用域中截獲的可選狀態(tài)組合晦款。 這使的Block成為OS X和iOS中理想的異步并發(fā)調(diào)用可選項之一。 且無需弄清楚如何使用線程等低級機(jī)制枚冗,您可以使用Block定義任務(wù)缓溅,然后讓系統(tǒng)在處理器資源可用時執(zhí)行這些任務(wù)。

OS X和iOS提供了多種并發(fā)技術(shù)赁温,包括兩種任務(wù)調(diào)度機(jī)制:Operation queues和GCD坛怪。 這些機(jī)制圍繞著一個等待被調(diào)用的任務(wù)隊列而設(shè)。 您按照需要調(diào)用它們的順序?qū)lock添加到這一隊列中股囊,當(dāng)處理器時間和資源可用時袜匿,系統(tǒng)將對這一隊列中的Block進(jìn)行調(diào)用。

串行隊列只允許一次執(zhí)行一個任務(wù) -- 隊列中的下一個任務(wù)直到前一個任務(wù)完成才會被調(diào)用稚疹,在此期間這一任務(wù)將不會離開隊列居灯。 并發(fā)隊列會調(diào)用盡可能多的任務(wù),而不必等待前面的任務(wù)完成内狗。

使用Block操作隊列

操作隊列是Cocoa和Cocoa Touch框架的任務(wù)調(diào)度方式怪嫌。 您創(chuàng)建一個NSOperation實例來封裝一個工作單元以及任何必要的數(shù)據(jù),然后將該操作添加到NSOperationQueue中來執(zhí)行柳沙。

雖然您可以創(chuàng)建自己的自定義NSOperation子類來實現(xiàn)復(fù)雜的任務(wù)岩灭,但也可以通過NSBlockOperation使用Block的方式創(chuàng)建一個操作,如下所示:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];

您可以手動執(zhí)行操作赂鲤,但操作通常添加到現(xiàn)有的操作隊列或您自己創(chuàng)建的隊列中去執(zhí)行:

// 在主隊列執(zhí)行任務(wù):
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
 
// 在后臺隊列執(zhí)行任務(wù):
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

如果使用操作隊列噪径,可以配置操作之間的優(yōu)先級或依賴關(guān)系柱恤,例如指定一個操作先不執(zhí)行,直到一組其他操作完成才執(zhí)行熄云。例如膨更,您還可以通過KVO的方式監(jiān)聽操作狀態(tài)的改變妙真,然后在任務(wù)完成時缴允,更新進(jìn)度指示器(菊花):

更多關(guān)于操作和隊列操作的信息,見Operation Queues

使用GCD在調(diào)度隊列中給Block進(jìn)行進(jìn)度安排珍德。

如果需要安排任意Block代碼執(zhí)行的話练般,您可以直接使用由Grand Central Dispatch(GCD)控制的調(diào)度隊列(dispatch queues)。 調(diào)度隊列使得相對于調(diào)用者同步或異步地執(zhí)行任務(wù)變得容易锈候,并且以先進(jìn)先出的順序執(zhí)行它們的任務(wù)薄料。

您可以創(chuàng)建自己的調(diào)度隊列(dispatch queue)或使用GCD自動提供的隊列。 例如泵琳,如果需要安排并發(fā)執(zhí)行的任務(wù)摄职,可以通過使用dispatch_get_global_queue()函數(shù)并指定隊列優(yōu)先級來獲取對現(xiàn)有隊列的引用,如下所示:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//要將該block分派到隊列中获列,您可以使用dispatch_async()或dispatch_sync()函數(shù)谷市。
// dispatch_async()函數(shù)不會等待要調(diào)用的block執(zhí)行完畢,而是立即返回:

dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
});

更多關(guān)于隊列調(diào)度和GCD的問題見Dispatch Queues.

文章主要翻譯自Apple官方文檔Working with Blocks

喜歡的話击孩,給個關(guān)注迫悠,謝謝!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巩梢,隨后出現(xiàn)的幾起案子创泄,更是在濱河造成了極大的恐慌,老刑警劉巖括蝠,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞠抑,死亡現(xiàn)場離奇詭異,居然都是意外死亡忌警,警方通過查閱死者的電腦和手機(jī)搁拙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慨蓝,“玉大人感混,你說我怎么就攤上這事±窳遥” “怎么了弧满?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長此熬。 經(jīng)常有香客問我庭呜,道長滑进,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任募谎,我火速辦了婚禮扶关,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘数冬。我一直安慰自己节槐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布拐纱。 她就那樣靜靜地躺著铜异,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秸架。 梳的紋絲不亂的頭發(fā)上揍庄,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音东抹,去河邊找鬼蚂子。 笑死,一個胖子當(dāng)著我的面吹牛缭黔,可吹牛的內(nèi)容都是我干的食茎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼试浙,長吁一口氣:“原來是場噩夢啊……” “哼董瞻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起田巴,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤钠糊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壹哺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抄伍,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年管宵,在試婚紗的時候發(fā)現(xiàn)自己被綠了截珍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡箩朴,死狀恐怖岗喉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炸庞,我是刑警寧澤钱床,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站埠居,受9級特大地震影響查牌,放射性物質(zhì)發(fā)生泄漏事期。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一纸颜、第九天 我趴在偏房一處隱蔽的房頂上張望兽泣。 院中可真熱鬧,春花似錦胁孙、人聲如沸唠倦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牵敷。三九已至,卻和暖如春法希,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背靶瘸。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工苫亦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怨咪。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓屋剑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诗眨。 傳聞我的和親對象是個殘疾皇子唉匾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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