Block 編程

這是 Objective-C 高級(jí)編程 第二章钧敞。這一章的內(nèi)容蜡豹,囧,太底層了溉苛,還是要把知識(shí)往實(shí)際上靠镜廉。下周就是最后一章,就看完它了愚战,應(yīng)該過(guò)不久又要學(xué) Swift 了娇唯,囧囧囧囧_

一些測(cè)試代碼

棧上 Block 對(duì)象捕獲強(qiáng)引用和弱引用變量:

NSObject *a = [NSObject new];
NSObject * __weak b = a;

NSLog(@"%@ %lu", ^{
    a;
}, _objc_rootRetainCount(a));
// 輸出:<__NSStackBlock__: 0x7fff5fbff7c0> 2

NSLog(@"%@ %lu", ^{
    b;
}, _objc_rootRetainCount(a));
// 輸出:<__NSStackBlock__: 0x7fff5fbff7f8> 2

NSLog(@"%@ %lu", ^{
    a;
}, _objc_rootRetainCount(a));
// 輸出:<__NSStackBlock__: 0x7fff5fbff798> 3

棧上和堆上對(duì) __strong 變量的捕獲,以及堆上 Block 調(diào)用 copy:

NSObject *obj = [NSObject new]; // 引用計(jì)數(shù)為 1
NSLog(@"%@ %lu", ^{ NSLog(@"blk0: %@", obj); }, _objc_rootRetainCount(obj));
// 輸出:<__NSStackBlock__: 0x7fff5048aac0> 2寂玲,因?yàn)楸?Stack Block 持有
    
id a = ^{ NSLog(@"blk1: %@", obj); };
NSLog(@"%lu", _objc_rootRetainCount(obj));
// 輸出:4塔插。因?yàn)橘x值時(shí),將 Block 復(fù)制到堆上拓哟,就有了兩個(gè) Block 對(duì)象持有 obj想许。
    
id b = [a copy];
NSLog(@"%lu", _objc_rootRetainCount(obj));
// 輸出:4。已經(jīng)在堆上的 Block 再調(diào)用 copy 不會(huì)對(duì) obj 有影響断序,可能在 Block 上后再調(diào)用 copy 不會(huì)做任何事流纹。

棧上和堆上對(duì) __block 對(duì)象的變量的持有情況:

void (^blk)() = nil;
{
    __block NSMutableArray * __strong a = [NSMutableArray new];
    __block NSMutableArray * __strong b = a;    
    NSLog(@"%lu", _objc_rootRetainCount(b)); // 輸出:2
        
    blk = ^{
        [a addObject:[NSObject new]];
        NSLog(@"%p %@ %lu", a, a, _objc_rootRetainCount(a));
        // 輸出:0x7f8064318a80 ("<NSObject: 0x7f8064006490>") 1
    };
    NSLog(@"%p %@ %lu", a, a, _objc_rootRetainCount(a));
    // 輸出:0x7f8064318a80 () 2
    // 這里如果按上一個(gè)解釋的話應(yīng)該輸出 4 才對(duì),棧和堆各持有一次违诗,實(shí)際上不是
}
blk();
// 這里的解釋是:1. Block 棧對(duì)象是直接持有的 __block a 對(duì)象的指針捧颅,所以不會(huì)對(duì) array a 對(duì)象持有引用。
// 2. 當(dāng) Block 從棧復(fù)制到堆時(shí)较雕,
//    若 Block 使用的變量為附有 __block 說(shuō)明符的 id 類型或?qū)ο箢愋偷淖詣?dòng)變量碉哑,不會(huì)被 retain;
//    若 Block 使用的變量為沒(méi)有 __block 說(shuō)明符的 id 類型或?qū)ο箢愋偷淖詣?dòng)變量亮蒋,則被 retain 扣典。

上一份測(cè)試代碼的補(bǔ)充:

// 上面代碼不會(huì) crash,但是 blk() 里的 array 確實(shí)已經(jīng)是野指針了慎玖。
NSMutableArray * __unsafe_unretained c;
{
    NSMutableArray * __strong a = [NSMutableArray new];
    c = a;
}
[c addObject:[NSObject new]];
// c 是野指針贮尖,也不會(huì) crash 。<這要看運(yùn)氣咯>

id __strong array = __cself->array 并不會(huì) retain趁怔。

void (^blk)(id) = nil;
{
    id array = [[NSMutableArray alloc] init];
    blk = [^(id obj) {
    [array addObject:obj];
    NSLog(@"%ld %lu", [array count], _objc_rootRetainCount(array)); // 輸出:1 1
    // 在 ARC 中是不允許在結(jié)構(gòu)體中使用 Objective-C 對(duì)象的湿硝,所以這里總有些不合邏輯的地方薪前。
    // 在 Block 實(shí)現(xiàn)的函數(shù)中,最開始就有語(yǔ)句:id __strong array = __cself->array;
    // 按理應(yīng)該會(huì)再持有一次 array 的关斜,但是 array 的引用計(jì)數(shù)沒(méi)有變示括。
    // 仍然只是 Block 對(duì)象的成員變量持有的一次 array。
    } copy];
}    
blk([NSObject new]);

截獲自動(dòng)變量

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d";
        NSObject *a = [NSObject new];
        NSObject __weak *b = a;
        
        void (^blk)() = ^{
            printf(fmt, val);
            b; // 測(cè)試弱引用
        };
        blk();
    }
    
    return 0;
}

Console 使用 clang -rewrite-objc main.m 轉(zhuǎn)換為下面 C語(yǔ)言代碼(并不難看懂):

struct __block_impl {
  void *isa;  // _NSConcreteStackBlock痢畜、_NSConcreteGlobalBlock垛膝、_NSConcreteMallocBlock
  int Flags;  // 標(biāo)志位
  int Reserved; // 版本升級(jí)可能用到的保留位
  void *FuncPtr; // Block 函數(shù)
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  NSObject *__weak b;
  
  // 構(gòu)造函數(shù)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, NSObject *__weak _b, int flags=0) : fmt(_fmt), val(_val), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  NSObject *__weak b = __cself->b; // bound by copy

  printf(fmt, val);
  b;
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d";
        NSObject *a = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        NSObject __attribute__((objc_gc(weak))) *b = a;

        void (*blk)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val, b, 570425344);
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }

    return 0;
}

main 函數(shù)看到,Block 實(shí)際上被轉(zhuǎn)換成了位于棧上的 __main_block_impl_0 自動(dòng)變量丁稀。__main_block_copy_0 中的 _Block_object_assign 函數(shù)相當(dāng)于 retain 實(shí)例方法吼拥,使 Block 的成員變量 b 持有捕獲到的對(duì)象。 __main_block_dispose_0 中的 _Block_object_dispose 函數(shù)相當(dāng)于 release 實(shí)例方法线衫,釋放 Block 的成員變量 b 持有的對(duì)象凿可。

Block 不能捕獲數(shù)組的原因是:在 __main_block_fun_0() 中需要對(duì)捕獲到的變量賦值,而 C語(yǔ)言不支持?jǐn)?shù)組賦值授账。

int a[20];
int b[20] = a; // 編譯錯(cuò)誤

截獲的自動(dòng)變量不能在 Block 中賦值矿酵,產(chǎn)生編譯錯(cuò)誤。因?yàn)榫退愀淖?Block 中該變量的值也不能改變 Block 外部變量的值矗积,既然如此全肮,編譯器就不讓你改了,但可以在 Block 中再聲明一個(gè)變量棘捣,然后去修改這個(gè)新的變量辜腺。但對(duì)于 靜態(tài)全局變量(static) 和 全局變量,因?yàn)樗鼈儽旧砭涂梢员蝗衷L問(wèn)乍恐,所以 Block 不會(huì)捕獲它們评疗,且可以在 Block 中修改它們的值;對(duì)于 靜態(tài)局部變量 因?yàn)樗荒鼙蝗衷L問(wèn)茵烈,當(dāng)截獲它時(shí)會(huì)自動(dòng)截獲它的地址作為 Block 的成員變量百匆,也可以在 Block 中修改它們的值。為什么不對(duì)所有變量都保存指針呢呜投?因?yàn)椴话踩有伲植孔兞吭谧饔糜蚪Y(jié)束就會(huì)被釋放,但 Block 仍然保留著它的地址仑荐,就形成野指針了雕拼。

__block storage-class-specifier

__block 類似于 static auto register 說(shuō)明符,它們用于指定將變量設(shè)置到那個(gè)存儲(chǔ)域中粘招。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        __block int val = 10;
        void (^blk)(void) = ^{ val = 1; };
    }   
    return 0;
}

Console 使用 clang -rewrite-objc main.m 轉(zhuǎn)換為下面 C語(yǔ)言代碼:

// 聲明為 __block 的變量會(huì)被轉(zhuǎn)換成該類型的結(jié)構(gòu)體
struct __Block_byref_val_0 {
  void *__isa;
  __Block_byref_val_0 *__forwarding;
  int __flags;
  int __size;
  int val;  // __block 原自動(dòng)變量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  
  // 構(gòu)造函數(shù)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// Block 函數(shù)體
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
 (val->__forwarding->val) = 1; 
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344);

    }
    return 0;
}

從轉(zhuǎn)換后的代碼中可以看到 __block 指定的變量被轉(zhuǎn)換成了 __Block_byref_val_0 結(jié)構(gòu)體啥寇,并且在 __main_block_impl_0 中以前捕獲自動(dòng)變量的位置被替換成了對(duì)應(yīng)的 __Block_byref_val_0 指針。__main_block_impl_0__block_impl 成員的 isa 指針一般為 _NSConcreteStackBlock,但當(dāng)滿足以下任一情況時(shí)辑甜,Block 將被指定為:_NSConcreteGlobalBlock 類對(duì)象衰絮。

  • 在全局變量的地方使用 Block
  • Block 的表達(dá)式中沒(méi)有截獲自動(dòng)變量

配置在全局變量上的 Block,從變量作用域外也可以通過(guò)指針安全訪問(wèn)(全局變量作用域好大的)磷醋。但設(shè)置在棧上的 Block猫牡,如果其所屬的變量作用域結(jié)束,該 Block 就被廢棄子檀。由于 __block 變量也配置在棧上镊掖,同樣地乃戈,如果其所屬的變量作用域結(jié)束褂痰,該 Block 就被廢棄。

實(shí)際上當(dāng) ARC 有效時(shí)症虑,大多數(shù)情況下編譯器會(huì)恰當(dāng)?shù)剡M(jìn)行判斷缩歪,自動(dòng)生成 Block 從棧上復(fù)制到堆上的代碼。只有在 向方法或函數(shù)的參數(shù)中傳遞 Block 時(shí) 需要手動(dòng)生成代碼谍憔。但是在函數(shù)或方法中適當(dāng)?shù)貜?fù)制了傳遞過(guò)來(lái)的參數(shù)匪蝙,那么就不必調(diào)用 copy 方法手動(dòng)復(fù)制了。以下方法或函數(shù)不用手動(dòng)復(fù)制:

  • Cocoa 框架的方法且方法名中含有 usingBlock 等
  • Grand Central Dispatch 的 API

舉個(gè)例子习贫,以下是會(huì) crash 的代碼(我是在 ViewController 中測(cè)試的)

- (id) getBlockArray {
    int val = 10;
    NSArray *arr = [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0: %d", val); },
            ^{NSLog(@"blk1: %d", val); },
            nil];
    // initWithObjects 沒(méi)有將 Block 從棧上 copy 到堆上逛球,在函數(shù)結(jié)束時(shí),它們就隨著棧一起被釋放了
    return arr;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSArray *arr = [self getBlockArray];
    void (^blk)() = [arr objectAtIndex:0];
    blk(); // Block 對(duì)象已經(jīng)被釋放苫昌,將 crash
}

但是下面的代碼不會(huì) crash

- (void)viewDidLoad {
    [super viewDidLoad];
    
    int val = 10;
    NSArray *arr = [[NSArray alloc] initWithObjects:
                    ^{NSLog(@"blk0: %d", val); },
                    ^{NSLog(@"blk1: %d", val); },
                    nil];
    // NSLog(@"%@", [arr objectAtIndex:0]); // => 輸出:<__NSMallocBlock__: 0x7fed53f276e0>
    ((void (^)())[arr objectAtIndex:0])();  // 不會(huì) crash 
    // 在 ARC 有效時(shí)颤绕,如果函數(shù)(上面的 objectAtIndex)返回的是 Block 則會(huì)被自動(dòng) copy 到堆中。
    // 還有祟身,在 ARC 有效時(shí)奥务,向 id 或 Block 變量賦值時(shí)也會(huì)自動(dòng)將 Block 從棧 copy 到堆中。
}

Block 對(duì)象使用 copy 方法的效果總結(jié):

Block 的類 副本源的存儲(chǔ)位置 賦值效果
_NSConcreteStackBlock 從棧賦值到堆
_NSConcreteStackBlock 程序數(shù)據(jù)區(qū) 什么也不做
_NSConcreteStackBlock 什么也不做

Note: 但是 Block 對(duì)象的引用計(jì)數(shù)永遠(yuǎn)是 1袜硫,有可能沒(méi)有加入引用計(jì)數(shù)表氯葬,也可能堆上的 Block 做 copy 時(shí)什么也沒(méi)做。

當(dāng) Block 從棧復(fù)制到堆時(shí)對(duì) __block 變量的影響:

__block 變量的存儲(chǔ)區(qū) 副本源的存儲(chǔ)位置
從棧賦值到堆并被 Block 持有
被 Block 持有

Note:__block 變量實(shí)際上是 Objective-C 對(duì)象婉陷。 我們?cè)诔绦蛑胁荒茉L問(wèn) __block 對(duì)象帚称,因?yàn)樵创a都會(huì)被轉(zhuǎn)換成 val->__forwarding->val 。

到此秽澳,可以說(shuō)明 __block 結(jié)構(gòu)體的成員變量 __forwarding 的作用了——不管 __block 變量配置在棧上還是堆上世杀,都能正確地訪問(wèn)到該變量。代碼如下:

__block int val = 0; // val 變量在棧上
void (^blk)(void) = [^{val++;} copy]; 
// blk 變量在堆上了(__block 變量也被 copy 到堆上)肝集,val->__forwarding 指向堆上的 __block 對(duì)象
++val; // ++(val->__forwarding->val)
blk(); // ++(val->__forwarding->val) 它的 val 在堆上瞻坝,__forwarding 指向自己
NSLog(@"%d", val); // 輸出為2

循環(huán)引用

typedef void(^blk_t)(void);
@interface MyObject : NSObject {
    blk_t blk_;
    id obj_;
}
@end

@implementation MyObject {
-(id)init{
    self = [super init];
    blk_ = ^{ NSLog(@"obj_ = %@", obj_); }
    // 發(fā)生循環(huán)引用,因?yàn)?Block 捕獲到的對(duì)象實(shí)際上是 self,即 obj_ 被編譯為 self->obj_
    // 使用 id __weak obj = obj_; 避免循環(huán)引用
    
    return self;
}
}

通過(guò)使用 __block 變量可以避免循環(huán)引用所刀,在非 ARC 時(shí)候經(jīng)常被這樣用衙荐,好吧,其實(shí)我并不想管非 ARC 是什么情況浮创,只是它真的可以避免循環(huán)引用忧吟。

typedef void(^blk_t)(void);
@interface MyObject : NSObject {
    blk_t blk_;
}
@end

@implementation MyObject {
-(id)init{
    self = [super init];
    __block MyObject * tmp = self; // 使用 __block 變量避免循環(huán)應(yīng)用,這里 self 的引用計(jì)數(shù)加 1
    blk_ = ^{ NSLog(@"self = %@", tmp); }
    // blk_ 持有了含有 self 的 __block 變量斩披;self 又持有了 blk_ 循環(huán)引用了溜族。繼續(xù)看下面~
    
    return self;
} // __block 無(wú)論是在堆上還是在棧上都不會(huì)持有 val->forwarding->val 的對(duì)象。
// tmp 在棧上且超出作用域垦沉,即被銷毀』褪悖現(xiàn)在 blk_ 仍然持有 tmp 對(duì)應(yīng)的堆上 __block 對(duì)象,
// 但堆上的 __block 對(duì)象并不持有 self厕倍,即不再是循環(huán)引用
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寡壮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子讹弯,更是在濱河造成了極大的恐慌况既,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件组民,死亡現(xiàn)場(chǎng)離奇詭異棒仍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)臭胜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門莫其,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人庇楞,你說(shuō)我怎么就攤上這事榜配。” “怎么了吕晌?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蛋褥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我睛驳,道長(zhǎng)烙心,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任乏沸,我火速辦了婚禮淫茵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹬跃。我一直安慰自己匙瘪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丹喻,像睡著了一般薄货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碍论,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天谅猾,我揣著相機(jī)與錄音,去河邊找鬼鳍悠。 笑死税娜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藏研。 我是一名探鬼主播敬矩,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼遥倦!你這毒婦竟也來(lái)了谤绳?” 一聲冷哼從身側(cè)響起占锯,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤袒哥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后消略,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堡称,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年艺演,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了却紧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胎撤,死狀恐怖晓殊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伤提,我是刑警寧澤巫俺,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站肿男,受9級(jí)特大地震影響介汹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舶沛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一嘹承、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧如庭,春花似錦叹卷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)餐胀。三九已至,卻和暖如春瘤载,著一層夾襖步出監(jiān)牢的瞬間否灾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鸣奔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墨技,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓挎狸,卻偏偏與公主長(zhǎng)得像扣汪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锨匆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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