block底層實(shí)現(xiàn)原理

block的本質(zhì)

block在開發(fā)中的使用頻率非常高.

block本質(zhì)上是一個(gè)OC對(duì)象总珠,它內(nèi)部也有isa指針领铐,這個(gè)對(duì)象封裝了函數(shù)調(diào)用地址以及函數(shù)調(diào)用環(huán)境(函數(shù)參數(shù)特石、返回值啤呼、捕獲的外部變量等)卧秘。當(dāng)我們定義一個(gè)block,在編譯后它的底層存儲(chǔ)結(jié)構(gòu)是怎樣的呢,以下面這個(gè)簡(jiǎn)單的block為例

int age = 20;
void (^block)(void) = ^ {
     NSLog(@"age == %d",age);
};

如果想探究它的底層實(shí)現(xiàn)的話官扣,可以在命令行運(yùn)行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m將這個(gè)main.m文件轉(zhuǎn)成編譯后的c/c++文件翅敌,然后在這個(gè)文件搜索__main_block_impl_0就可以找到這個(gè)block的結(jié)構(gòu)體。整體結(jié)構(gòu)如下圖:

16f8481974bfbfae.png

  • impl->isa:就是isa指針惕蹄,可見它就是一個(gè)OC對(duì)象蚯涮。
  • impl->FuncPtr:是一個(gè)函數(shù)指針治专,也就是底層將block中要執(zhí)行的代碼封裝成了一個(gè)函數(shù),然后用這個(gè)指針指向那個(gè)函數(shù)遭顶。
  • Desc->Block_size:block占用的內(nèi)存大小张峰。
  • age:捕獲的外部變量age,可見block會(huì)捕獲外部變量并將其存儲(chǔ)在block的底層結(jié)構(gòu)體中棒旗。

當(dāng)我們調(diào)用block()時(shí)喘批,實(shí)際上就是通過函數(shù)指針FuncPtr找到封裝的函數(shù)并將block的地址作為參數(shù)傳給這個(gè)函數(shù)進(jìn)行執(zhí)行,把block傳給函數(shù)是因?yàn)楹瘮?shù)執(zhí)行中需要用到的某些數(shù)據(jù)是存在block的結(jié)構(gòu)體中的(比如捕獲的外部變量)铣揉。如果定義的是帶參數(shù)的block饶深,調(diào)用block時(shí)是將block地址和block的參數(shù)一起傳給封裝好的函數(shù)。

block的變量捕獲機(jī)制

block外部的變量是可以被block捕獲的逛拱,這樣就可以在block內(nèi)部使用外部的變量了敌厘。不同類型的變量的捕獲機(jī)制是不一樣的。下面我們來看一個(gè)示例:

int c = 1000; // 全局變量
static int d = 10000; // 靜態(tài)全局變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int a = 10; // 局部變量
        static int b = 100; // 靜態(tài)局部變量
        void (^block)(void) = ^{
             NSLog(@"a = %d",a);
             NSLog(@"b = %d",b);
             NSLog(@"c = %d",c);
             NSLog(@"d = %d",d);
         };
         a = 20;
         b = 200;
         c = 2000;
         d = 20000;
         block();
    }
    return 0;
}

//*打印結(jié)果*
a = 10
b = 200
c = 2000
d = 20000

c文件中的結(jié)構(gòu)體如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
};

只有2個(gè)局部變量被捕獲了朽合,而且2個(gè)局部變量的捕獲方式還不一樣俱两。

  • 2.1 全局變量的捕獲
    不管是普通全局變量還是靜態(tài)全局變量,block都不會(huì)捕獲曹步。因?yàn)槿肿兞吭谀睦锒伎梢栽L問锋华,所以block內(nèi)部不捕獲也是可以直接訪問全局變量的,所以外部更改全局變量的值時(shí)箭窜,block內(nèi)部打印的就是最新更改的值毯焕。
  • 2.2 靜態(tài)局部變量的捕獲
    我們發(fā)現(xiàn)定義的靜態(tài)局部變量b被block捕獲后,在block結(jié)構(gòu)體里面是以int *b;的形式來存儲(chǔ)的磺樱,也就是說block其實(shí)是捕獲的變量b的地址纳猫,block內(nèi)部是通過b的地址去獲取或修改b的值,所以block外部更改b的值會(huì)影響block里面獲取的b的值竹捉,block里面更改b的值也會(huì)影響block外面b的值芜辕。所以上面會(huì)打印b = 200。
  • 2.3 普通局部變量的捕獲
    普通局部變量就是在一個(gè)函數(shù)或代碼塊中定義的類似int a = 10;的變量块差,它其實(shí)是省略了auto關(guān)鍵字侵续,等價(jià)于auto int a = 10,所以也叫auto變量憨闰。和靜態(tài)局部變量不同的是状蜗,普通局部變量被block捕獲后再block底層結(jié)構(gòu)體中是以int a;的形式存儲(chǔ),也就是說block捕獲的其實(shí)是a的值(也就是10)鹉动,并且在block內(nèi)部重新定義了一個(gè)變量來存儲(chǔ)這個(gè)值轧坎,這個(gè)時(shí)候block外部和里面的a其實(shí)是2個(gè)不同的變量,所以外面更改a的值不會(huì)影響block里面的a泽示。所以打印的結(jié)果是a = 10缸血。

為什么普通局部變量要捕獲值蜜氨,跟靜態(tài)局部變量一樣捕獲地址不行嗎?
是的捎泻,不行飒炎。因?yàn)槠胀ň植孔兞縜在出了大括號(hào)后就會(huì)被釋放掉了,這個(gè)時(shí)候如果我們?cè)诖罄ㄌ?hào)外面調(diào)用這個(gè)block笆豁,block內(nèi)部通過a的指針去訪問a的值就會(huì)拋出異常郎汪,因?yàn)閍已經(jīng)被釋放了。而靜態(tài)局部變量的生命周期是和整個(gè)程序的生命周期是一樣的渔呵,也就是說在整個(gè)程序運(yùn)行過程中都不會(huì)釋放b怒竿,所以不會(huì)出現(xiàn)這種情況砍鸠。
那有人又有疑問了扩氢,既然靜態(tài)局部變量一直都不會(huì)被釋放,那block為什么還要捕獲它爷辱,直接拿來用不就可以了嗎录豺?這是因?yàn)殪o態(tài)局部變量作用域只限制在這個(gè)大括號(hào)類,出了這個(gè)大括號(hào)饭弓,雖然它還存在双饥,但是外面無法訪問它。而前面已經(jīng)介紹過弟断,block里面的代碼在底層是被封裝成了一個(gè)函數(shù)咏花,那這個(gè)函數(shù)肯定是在b所在的大括號(hào)外面,所以這個(gè)函數(shù)是無法直接訪問到b的阀趴,所以block必須將其捕獲昏翰。

block捕獲變量小結(jié)

  • 全局變量--不會(huì)捕獲,是直接訪問刘急。
  • 靜態(tài)局部變量--是捕獲變量地址棚菊。
  • 普通局部變量--是捕獲變量的值。

block的3種類型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )
  • NSStackBlock ( _NSConcreteStackBlock )
  • NSMallocBlock ( _NSConcreteMallocBlock )

通過一個(gè)簡(jiǎn)單的代碼查看一下block在什么情況下其類型會(huì)各不相同

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 內(nèi)部沒有調(diào)用外部變量的block
        void (^block1)(void) = ^{  __NSGlobalBlock__
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用外部變量的block
        int a = 10;
        void (^block2)(void) = ^{  __NSStackBlock__
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接調(diào)用的block的class 
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{  __NSMallocBlock__
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}
block是如何定義其類型

在MRC的環(huán)境下叔汁,可以得到下圖:


1637de34b6966052.png

沒有訪問auto變量的block是NSGlobalBlock類型的统求,存放在數(shù)據(jù)段中。
訪問了auto變量的block是NSStackBlock類型的据块,存放在棧中码邻。
NSStackBlock類型的block調(diào)用copy成為NSMallocBlock類型并被復(fù)制存放在堆中。

block在內(nèi)存中的存儲(chǔ)

通過下面一張圖看一下不同block的存放區(qū)域

1637de34c0579805.png

上圖中可以發(fā)現(xiàn)另假,根據(jù)block的類型不同冒滩,block存放在不同的區(qū)域中。
數(shù)據(jù)段中的NSGlobalBlock直到程序結(jié)束才會(huì)被回收浪谴,不過我們很少使用到NSGlobalBlock類型的block开睡,因?yàn)檫@樣使用block并沒有什么意義因苹。
NSStackBlock類型的block存放在棧中,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放篇恒,作用域執(zhí)行完畢之后就會(huì)被立即釋放扶檐,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉。
NSMallocBlock是在平時(shí)編碼過程中最常使用到的胁艰。存放在堆中需要我們自己進(jìn)行內(nèi)存管理款筑。

各類型block調(diào)用copy
1637de351bcee494.png

所以在平時(shí)開發(fā)過程中MRC環(huán)境下經(jīng)常需要使用copy來保存block,將棧上的block拷貝到堆中腾么,即使棧上的block被銷毀奈梳,堆上的block也不會(huì)被銷毀,需要我們自己調(diào)用release操作來銷毀解虱。而在ARC環(huán)境下系統(tǒng)會(huì)自動(dòng)調(diào)用copy操作攘须,使block不會(huì)被銷毀。

ARC環(huán)境下的block

ARC環(huán)境下殴泰,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy操作于宙,將block復(fù)制到堆上。

什么情況下ARC會(huì)自動(dòng)將block進(jìn)行一次copy操作悍汛? 以下代碼都在ARC環(huán)境下執(zhí)行捞魁。

  1. block作為函數(shù)返回值時(shí)
typedef void (^Block)(void);
Block myblock()
{
    int a = 10;
    // 上文提到過,block中訪問了auto變量离咐,此時(shí)block類型應(yīng)為__NSStackBlock__
    Block block = ^{
        NSLog(@"---------%d", a);
    };
    return block;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
       // 打印block類型為 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
}
  1. 將block賦值給__strong指針時(shí)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // block內(nèi)沒有訪問auto變量
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"%@",[block class]);
        int a = 10;
        // block內(nèi)訪問了auto變量谱俭,但沒有賦值給__strong指針
        NSLog(@"%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        // block賦值給__strong指針
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@",[block1 class]);
    }
    return 0;
}
  1. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
}];

  1. block作為GCD API的方法參數(shù)時(shí)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
});

block對(duì)對(duì)象型的局部變量的捕獲

block對(duì)對(duì)象類型和對(duì)基本數(shù)據(jù)類型變量的捕獲是不一樣的,對(duì)象類型的變量涉及到強(qiáng)引用和弱引用的問題宵蛀,強(qiáng)引用和弱引用在block底層是怎么處理的呢昆著?
如果block是在棧上,不管捕獲的對(duì)象時(shí)強(qiáng)指針還是弱指針糖埋,block內(nèi)部都不會(huì)對(duì)這個(gè)對(duì)象產(chǎn)生強(qiáng)引用宣吱。所以我們主要來看下block在堆上的情況。
首先來看下強(qiáng)引用的對(duì)象被block捕獲后在底層結(jié)構(gòu)體中是如何存儲(chǔ)的瞳别。

// OC代碼
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [[Person alloc] init];
        person.age = 20;

        void (^block)(void) = ^{
            NSLog(@"age--- %ld",person.age);
         };
        block();

    }
    return 0;
}

// 底層結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
};

可以看到和基本數(shù)據(jù)類型不同的是征候,person對(duì)象被block捕獲后,在結(jié)構(gòu)體中多了一個(gè)修飾關(guān)鍵字__strong祟敛。

我們?cè)賮砜聪氯跻脤?duì)象被捕獲后是什么樣的:

// OC代碼
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [[Person alloc] init];
        person.age = 20;

        __weak Person *weakPerson = person;
        void (^block)(void) = ^{
            NSLog(@"age--- %ld",weakPerson.age);
         };
        block();

    }
    return 0;
}

// 底層block
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
};

可見此時(shí)block中weakPerson的關(guān)鍵字變成了__weak疤坝。
在block中修飾被捕獲的對(duì)象類型變量的關(guān)鍵字除了__strong__weak外還有一個(gè)__unsafe_unretained馆铁。那這結(jié)果關(guān)鍵字起什么作用呢跑揉?
當(dāng)block被拷貝到堆上時(shí)是調(diào)用的copy函數(shù),copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)就會(huì)根據(jù)這3個(gè)關(guān)鍵字來進(jìn)行操作历谍。

  • 如果關(guān)鍵字是__strong现拒,那block內(nèi)部就會(huì)對(duì)這個(gè)對(duì)象進(jìn)行一次retain操作,引用計(jì)數(shù)+1望侈,也就是block會(huì)強(qiáng)引用這個(gè)對(duì)象印蔬。也正是這個(gè)原因,導(dǎo)致在使用block時(shí)很容易造成循環(huán)引用脱衙。
  • 如果關(guān)鍵字是__weak__unsafe_unretained侥猬,那block對(duì)這個(gè)對(duì)象是弱引用,不會(huì)造成循環(huán)引用捐韩。所以我們通常在block外面定義一個(gè)__weak__unsafe_unretained修飾的弱指針指向?qū)ο笸诉耄缓笤赽lock內(nèi)部使用這個(gè)弱指針來解決循環(huán)引用的問題。

block從堆上移除時(shí)荤胁,則會(huì)調(diào)用block內(nèi)部的dispose函數(shù)瞧预,dispose函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放強(qiáng)引用的變量。

__block修飾符的作用

下面這段代碼:

- (void)test{
    int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
}

編譯器會(huì)直接報(bào)錯(cuò)寨蹋。
因?yàn)?code>age是一個(gè)局部變量松蒜,它的作用域和生命周期就僅限在是test方法里面扔茅,而前面也介紹過了已旧,block底層會(huì)將大括號(hào)中的代碼封裝成一個(gè)函數(shù),也就相當(dāng)于現(xiàn)在是要在另外一個(gè)函數(shù)中訪問test方法中的局部變量召娜,這樣肯定是不行的运褪,所以會(huì)報(bào)錯(cuò)。
如果我想在block里面更改age的值要怎么做呢玖瘸?我們可以將age定義成靜態(tài)局部變量static int age = 10;秸讹。雖然靜態(tài)局部變量的作用域也是在test方法里面,但是它的生命周期是和程序一樣的雅倒,而且block捕獲靜態(tài)局部變量實(shí)際是捕獲的age的地址璃诀,所以block里面也是通過age的地址去更改age的值,所以是沒有問題的蔑匣。
但我們并不推薦這樣做劣欢,因?yàn)殪o態(tài)局部變量在程序運(yùn)行過程中是不會(huì)被釋放的,所以還是要盡量少用裁良。那還有什么別的方法來實(shí)現(xiàn)這個(gè)需求呢凿将?這就是我們要講的__block關(guān)鍵字。

- (void)test1{
    __block int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
    block();
    NSLog(@"%d",age);
}

當(dāng)我們用__block關(guān)鍵字修飾后价脾,底層到底做了什么讓我們能在block里面訪問age呢牧抵?下面我們來看下上面代碼轉(zhuǎn)成c++代碼后block的存儲(chǔ)結(jié)構(gòu)是什么樣的。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
};

struct __Block_byref_age_0 {
  void *__isa; // isa指針
__Block_byref_age_0 *__forwarding; // 如果這block是在堆上那么這個(gè)指針就是指向它自己,如果這個(gè)block是在棧上犀变,那這個(gè)指針是指向它拷貝到堆上后的那個(gè)block
 int __flags;
 int __size; // 結(jié)構(gòu)體大小
 int age; // 真正捕獲到的age
};

我們可以看到妹孙,age用__block修飾后,在block的結(jié)構(gòu)體中變成了__Block_byref_age_0 *age;获枝,而__Block_byref_age_0是個(gè)結(jié)構(gòu)體涕蜂,里面有個(gè)成員int age;,這個(gè)才是真正捕獲到的外部變量age映琳,實(shí)際上外部的age的地址也是指向這里的机隙,所以不管是外面還是block里面,修改age時(shí)其實(shí)都是通過地址找到這里來修改的。

所以age用__block修飾后它就不再是一個(gè)test1方法內(nèi)部的局部變量了萨西,而是被包裝成了一個(gè)對(duì)象有鹿,age就被存儲(chǔ)在這個(gè)對(duì)象中。之所以說是包裝成一個(gè)對(duì)象谎脯,是因?yàn)?code>__Block_byref_age_0這個(gè)結(jié)構(gòu)體的第一個(gè)成員就是isa指針葱跋。

__block修飾變量的內(nèi)存管理

__block不管是修飾基礎(chǔ)數(shù)據(jù)類型還是修飾對(duì)象數(shù)據(jù)類型,底層都是將它包裝成一個(gè)對(duì)象源梭,然后block結(jié)構(gòu)體中有個(gè)指針指向這個(gè)對(duì)象娱俺。既然是一個(gè)對(duì)象,那block內(nèi)部如何對(duì)它進(jìn)行內(nèi)存管理呢废麻?

  • 當(dāng)block在棧上時(shí)荠卷,block內(nèi)部并不會(huì)對(duì)這個(gè)對(duì)象產(chǎn)生強(qiáng)引用。
  • 當(dāng)block調(diào)用copy函數(shù)從椫蚶ⅲ拷貝到堆中時(shí)油宜,它同時(shí)會(huì)將這個(gè)對(duì)象也拷貝到堆上,并對(duì)這個(gè)對(duì)象產(chǎn)生強(qiáng)引用怜姿。
  • 當(dāng)block從堆中移除時(shí)慎冤,會(huì)調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部又會(huì)調(diào)用_Block_object_dispose函數(shù)來釋放這個(gè)對(duì)象沧卢。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚁堤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子但狭,更是在濱河造成了極大的恐慌披诗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟空,死亡現(xiàn)場(chǎng)離奇詭異藤巢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)息罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門掂咒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事绍刮∥略玻” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵孩革,是天一觀的道長(zhǎng)岁歉。 經(jīng)常有香客問我,道長(zhǎng)膝蜈,這世上最難降的妖魔是什么锅移? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮饱搏,結(jié)果婚禮上非剃,老公的妹妹穿的比我還像新娘。我一直安慰自己推沸,他們只是感情好备绽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鬓催,像睡著了一般肺素。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宇驾,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天倍靡,我揣著相機(jī)與錄音,去河邊找鬼飞苇。 笑死菌瘫,一個(gè)胖子當(dāng)著我的面吹牛蜗顽,可吹牛的內(nèi)容都是我干的布卡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼雇盖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼忿等!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起崔挖,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贸街,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后狸相,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薛匪,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年脓鹃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逸尖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖娇跟,靈堂內(nèi)的尸體忽然破棺而出岩齿,到底是詐尸還是另有隱情,我是刑警寧澤苞俘,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布盹沈,位于F島的核電站,受9級(jí)特大地震影響吃谣,放射性物質(zhì)發(fā)生泄漏乞封。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一岗憋、第九天 我趴在偏房一處隱蔽的房頂上張望歌亲。 院中可真熱鬧,春花似錦澜驮、人聲如沸陷揪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悍缠。三九已至,卻和暖如春耐量,著一層夾襖步出監(jiān)牢的瞬間飞蚓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工廊蜒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趴拧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓山叮,卻偏偏與公主長(zhǎng)得像著榴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屁倔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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