Block 底層原理總結(jié)

1. Block 本質(zhì)

現(xiàn)在我們來(lái)實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的BlockA

 void (^BlockA)(void) = ^{
      NSLog(@"block A");
 };
 BlockA();

通過(guò)clang命名轉(zhuǎn)化成C++源碼:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

查看源碼會(huì)得到 BlockA 會(huì)生成以下幾個(gè)結(jié)構(gòu)體:

// 函數(shù)棧里面定義的 BlockA
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// __main_block_impl_0 的第一個(gè)結(jié)構(gòu)體成員
// 里面包含有 isa 指針
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 里面有個(gè)主要的變量 Block_size ,通過(guò)sizeof(struct __main_block_impl_0)賦值苟穆,實(shí)際上是BlockA的內(nèi)存大小
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// blockA {} 花括號(hào)里面的代碼,需要傳遞 __main_block_impl_0 參數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_50_s42jfyxs01s8t4z0c1q2p2c00000gn_T_main_d4d224_mi_1);
 }

根據(jù)BlockA的定義可以看出虏两,Block和對(duì)象一樣擁有isa指針,且有Block_size來(lái)計(jì)算分配內(nèi)存空間的大小的屬性世剖,所以Block也是一個(gè)對(duì)象定罢,對(duì)象就能調(diào)用Class方法(通過(guò)block能夠調(diào)用Class方法也能反推block是一個(gè)對(duì)象)。

接下來(lái)看 BlockA(); 轉(zhuǎn)化成的源碼

void (*BlockA)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 初始化BlockA旁瘫,傳入 __main_block_func_0 執(zhí)行的函數(shù)地址祖凫,
// 通過(guò) __main_block_impl_0 構(gòu)造函數(shù)可知 __main_block_func_0 會(huì)通過(guò) impl.FuncPtr = fp; 賦值給 FuncPtr

//  BlockA() 源碼
((void (*)(__block_impl *))((__block_impl *)BlockA)->FuncPtr)((__block_impl *)BlockA);
// 去掉強(qiáng)制類型轉(zhuǎn)換
BlockA->FuncPtr(BlockA);
// 其實(shí)BlockA()就是通過(guò)函數(shù)指針直接調(diào)用了函數(shù)并且傳入了BlockA對(duì)象

所以Block可以簡(jiǎn)單總結(jié):

  1. block本質(zhì)上也是一個(gè)OC對(duì)象,它內(nèi)部也有個(gè)isa指針;
  2. block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象.

由上可得Block的內(nèi)存結(jié)構(gòu)圖如下:


image.png

2酬凳,Block類型

Block 有三種類型:
1惠况,NSGlobalBlock 全局區(qū)的Block
2,NSStackBlock 棧區(qū)的Block
3宁仔,NSMallocBlock 堆區(qū)的Block

接下來(lái)我們通過(guò)定義幾個(gè)Block來(lái)訪問(wèn)外部變量稠屠,看看他們有什么區(qū)別:

int G = 100;
// 全局區(qū)的block
void (^Block_G)(void) = ^{
    NSLog(@"block G %d", G);
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {        
        // 不訪問(wèn)任何變量
        void (^BlockA)(void) = ^{
            NSLog(@"block A");
        };
        BlockA();
        
        // 訪問(wèn)auto 局部變量
        int b = 10;
        void (^BlockB)(void) = ^{
            NSLog(@"block B %d", b);
        };
        b = 20;
        BlockB();
        
        // weak block,讓編譯器不進(jìn)行 copy 操作
        __weak void (^BlockC)(void) = ^{
            NSLog(@"block D %d", b);
        };
        BlockC();
        
        static int d = 25;
        // 訪問(wèn)全局變量
        void (^BlockD)(void) = ^{
            NSLog(@"block D %d", d);
        };
       d = 30;
        BlockD();
        
        NSLog(@"BlockA class -> %@", [BlockA class]);
        NSLog(@"BlockB class -> %@", [BlockB class]);
        NSLog(@"BlockC class -> %@", [BlockC class]);
        NSLog(@"BlockD class -> %@", [BlockD class]);
        NSLog(@"BlockG class -> %@", [Block_G class]);

    }
    return 0;
}

打印結(jié)果【注arc模式下】:


image.png

根據(jù)以上輸出我們帶著幾個(gè)疑問(wèn)來(lái)探尋下Block的實(shí)現(xiàn)原理:

1翎苫,為什么Block能調(diào)用class方法权埠?
2,b煎谍,d變量被修改后攘蔽,為什么Block B里面的輸出值不是20?而BlockD輸出的是30
3呐粘,同樣在main函數(shù)里面定義的BlockA/B/C秩彤,為什么Class類型不一樣?
4事哭,同樣是NSGlobalBlock類型的Block_G與BlockA定義是一樣的嗎?

通過(guò)前面講的瓜富,我們知道了 BlockA的內(nèi)存結(jié)構(gòu)鳍咱,接下來(lái)我們看下 BlockB 和BlockD的內(nèi)存結(jié)構(gòu):

/* OC代碼
   // 訪問(wèn)局部變量
   void (^BlockB)(void) = ^{
        NSLog(@"block B %d", b);
    };
*/
// BlockB 定義
struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int b;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _b, int flags=0) : b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/* OC代碼
   static int d = 30;
   // 訪問(wèn)全局變量
   void (^BlockD)(void) = ^{
        NSLog(@"block D %d", d);
    };
*/
// BlockD 源碼定義
struct __main_block_impl_3 {
  struct __block_impl impl;
  struct __main_block_desc_3* Desc;
  int *d;
  __main_block_impl_3(void *fp, struct __main_block_desc_3 *desc, int *_d, int flags=0) : d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

從上面BlockB和BlockD的內(nèi)存結(jié)構(gòu)可以看出,他們比BlockA內(nèi)部多一個(gè)變量与柑,多出來(lái)的變量其實(shí)Block捕獲外部的變量谤辜,捕獲的變量可以得出:

  1. BlockB 里面捕獲的是int b 是一個(gè)值類型的int變量蓄坏,所以后面b值修改后,BlockB里面的b不會(huì)變丑念;
  2. BlockD 里面捕獲的是int *d 是一個(gè)指針變量涡戳,所以后面d值修改后,BlockD通過(guò)指針訪問(wèn)的d還是BlockD外面的變量d脯倚。

以下是Block捕獲變量的規(guī)則:


Block捕獲外部變量機(jī)制

再來(lái)看下Block_G 的定義如下:

struct __Block_G_block_impl_0 {
  struct __block_impl impl;
  struct __Block_G_block_desc_0* Desc;
  __Block_G_block_impl_0(void *fp, struct __Block_G_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

BlockA 和 Block_G 的區(qū)別是 isa 指針定義類型不一樣

BlockA:_NSConcreteStackBlock 在函數(shù)棧里面定義的block
Block_G:_NSConcreteGlobalBlock 在全局區(qū)定義的block

其實(shí)在Clang的文檔中渔彰,只定義了兩個(gè)Block類型: _NSConcreteGlobalBlock 和 _NSConcreteStackBlock 。而在Console中的Log我們看到的3個(gè)類型應(yīng)該是處理過(guò)的顯示推正,這些字樣在蘋果的文檔和Clang/LLVM的文檔中實(shí)難找到恍涂。

Console中輸出的的class類型是根據(jù)訪問(wèn)外部變量來(lái)確定的,其規(guī)則如下:


image.png

根據(jù)Block類型及捕獲變量規(guī)則我們就能知道為什么BlockA/B/C的類型為什么不一樣了:

  1. BlockA沒(méi)有訪問(wèn)任何變量植榕,所以它是NSGlobalBlock類型
  2. BlockC訪問(wèn)了局部auto變量再沧,所以它是NSStackBlock類型
  3. BlockB訪問(wèn)了局部auto變量,arc自動(dòng)給他進(jìn)行了copy操作尊残,所以它是NSMallocBlock類型

在ARC環(huán)境下炒瘸,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況:

  1. block作為函數(shù)返回值時(shí)
  2. 將block賦值給__strong指針時(shí)
  3. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
  4. block作為GCD API的方法參數(shù)時(shí)

MRC下block屬性的建議寫法

@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法寝衫,ARC下stong和copy沒(méi)有區(qū)別

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

3顷扩,Block內(nèi)存管理

Block訪問(wèn)對(duì)象

  FRFruit *fruit = [[FRFruit alloc] init];
  FRFruit *fruit1 = [[FRFruit alloc] init];
  __weak FRFruit *weakfruit = fruit1;

  void (^Block_Objct)(void) = ^{
        NSLog(@"block %@", fruit);
        NSLog(@"block %@", weakfruit);
  };
        
  Block_Objct();

轉(zhuǎn)化成C++源碼

  struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      FRFruit *__strong fruit;
      FRFruit *__weak weakfruit;
      ....
  };

  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};

可以看出Block里面默認(rèn)捕獲外面的對(duì)象為strong屬性修飾,如果外部是weak屬性的竞端,其內(nèi)部也會(huì)是相應(yīng)的weak屬性修飾屎即;
__main_block_desc_0 結(jié)構(gòu)體里面多了copy和dispose兩個(gè)函數(shù),它們是Block用來(lái)管理內(nèi)存的
來(lái)看下它們的定義:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->fruit, (void*)src->fruit, 3);
    _Block_object_assign((void*)&dst->weakfruit, (void*)src->weakfruit, 3);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->fruit, 3);
    _Block_object_dispose((void*)src->weakfruit, 3)
;}

里面主要有 _Block_object_assign() 和 _Block_object_dispose() 兩個(gè)函數(shù)

  1. 當(dāng)block被copy到堆時(shí)事富;
    ·會(huì)調(diào)用block內(nèi)部的copy函數(shù)技俐;
    ·copy函數(shù)內(nèi)部會(huì)調(diào)用 Block_object_assign 函數(shù);
    ·Block_object_assign 當(dāng)傳入的參數(shù)是stong類型時(shí)會(huì)進(jìn)行retain操作统台,如果是weak指針不會(huì)進(jìn)行retain操作雕擂;
  2. main_block_dispose_0 方法類似C++里面對(duì)析構(gòu)函數(shù),Block釋放時(shí)會(huì)調(diào)用贱勃,Block_object_dispose 函數(shù)則是對(duì)傳入?yún)?shù)進(jìn)行release釋放井赌。

所以當(dāng)block對(duì)象未釋放時(shí),它里面如果是strong修飾的對(duì)象也不會(huì)被釋放贵扰,正因?yàn)槿绱顺鹚耄訠lock常常伴隨著會(huì)出現(xiàn)循環(huán)引用問(wèn)題。
比如我們下面這種用法:

FRFruit *fruit = [[FRFruit alloc] init];        
fruit.block_Objct = ^{
     NSLog(@"block %@", fruit);
};
        
fruit.block_Objct();
// fruit 對(duì)象里面有一個(gè)copy修飾的 block_Objct 屬性

Block循環(huán)引用的原理:


image.png

我們根據(jù)OC的內(nèi)存管理機(jī)制知道戚绕,當(dāng)對(duì)象需要被釋放時(shí)必須先釋放所有指向它的指針纹坐。
所以如果要先釋放fruit對(duì)象,需要釋放block_Objct里面的變量舞丛,如果釋放block_Objct里面的變量需要先釋放block_Objct耘子,而block_Objct又被fruit強(qiáng)引用果漾,這樣就出現(xiàn)了循環(huán)引用的問(wèn)題。

那么如何解決這個(gè)問(wèn)題呢谷誓?
通常我們ARC環(huán)境下面的解決辦法是通過(guò)__weak指針來(lái)解決這個(gè)問(wèn)題绒障,通過(guò)上面講的Block里面的變量是通過(guò)訪問(wèn)的外部變量是否是strongweak指針來(lái)進(jìn)行內(nèi)部對(duì)象進(jìn)行相應(yīng)修飾的,所以如果訪問(wèn)的外部對(duì)象是weak指針時(shí)捍歪,他們的引用關(guān)系就會(huì)如下圖:

image.png

weak指針解決循環(huán)引用代碼如下:

FRFruit *fruit = [[FRFruit alloc] init];    
__weak FRFruit *weakfruit = fruit;    
fruit.block_Objct = ^{
     NSLog(@"block %@", weakfruit);
};
        
fruit.block_Objct();

其實(shí)除了weak還有__unsafe_unretain__block户辱,其實(shí)現(xiàn)如下:

// __unsafe_unretain 用法
FRFruit *fruit = [[FRFruit alloc] init];    
__unsafe_unretained FRFruit *weakfruit = fruit;    
fruit.block_Objct = ^{
     NSLog(@"block %@", weakfruit);
};

// __block 用法,主意ARC環(huán)境下 block中需要將變量只為nil费封,且必須調(diào)用block焕妙,才能打破循環(huán)
FRFruit *fruit = [[FRFruit alloc] init];    
__block FRFruit *fruit = fruit;    
fruit.block_Objct = ^{
     NSLog(@"block %@", fruit);
     fruit = nil; // MRC 不需要置為nil
};
fruit.block_Objct();

鑒于ARC環(huán)境下weak指針的底層實(shí)現(xiàn)原理(對(duì)象釋放時(shí)會(huì)自動(dòng)指針會(huì)自動(dòng)置為nil),所以推薦使用weak來(lái)解決循環(huán)引用問(wèn)題弓摘。
MRC環(huán)境下推薦使用__unsafe_unretained__block

4焚鹊,__block 修改局部變量原理

我們知道block是不能直接修改局部auto變量的,比如以下代碼編譯時(shí)會(huì)直接報(bào)錯(cuò):

int a = 10;
void (^Block)(void) = ^{
     a = 20; // 不能修改a變量
};
Block();

因?yàn)楦鶕?jù)計(jì)算機(jī)內(nèi)存分配原理可知韧献,a變量是在棧上的末患,它的內(nèi)存空間在函數(shù)結(jié)束時(shí)就會(huì)被回收,而Block有可能被復(fù)制到堆空間上锤窑,堆上空間的釋放由開發(fā)者控制的璧针,所以函數(shù)結(jié)束時(shí)Block有可能還會(huì)被執(zhí)行,而這時(shí)變量a已經(jīng)被釋放了渊啰,Block就無(wú)法找到變量a的內(nèi)存進(jìn)行賦值探橱,所以這種操作是被禁止的。
如果要修改局部變量绘证,OC提供__block 修飾來(lái)修改隧膏,其用法如下:

int a = 10;
void (^Block)(void) = ^{
     a = 20; 
};
Block();

那它的實(shí)現(xiàn)原理又是怎樣的呢?接下來(lái)我們看下源碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
// 不加__block 時(shí)是 int a 嚷那,
// 加上__block 變成了__Block_byref_a_0 *a
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
// block里面的訪問(wèn)__block 修飾的變量時(shí)都會(huì)通過(guò)結(jié)構(gòu)體中的 forwarding 指針來(lái)訪問(wèn)
  (a->__forwarding->a) = 20;
}

__block 實(shí)際上是將局部變量放在 __Block_byref_a_0 對(duì)象里面胞枕,該對(duì)象里面有一個(gè) __forwarding 指針,最開始__Block_byref_a_0 在棧上時(shí)魏宽, __forwarding 屬性會(huì)指向它自己腐泻,當(dāng)Block復(fù)制到堆上時(shí),__Block_byref_a_0 對(duì)象也會(huì)復(fù)制一份到堆上队询,此時(shí) __forwarding 指針指向的是堆上的那塊內(nèi)存派桩,所以Block實(shí)際上訪問(wèn)的a變量不再是棧上的變量,而是__Block_byref_a_0對(duì)象中堆內(nèi)存的那個(gè)a

forwarding 指針實(shí)現(xiàn)原理:


image.png

全文純手寫總結(jié)蚌斩,有些地方總結(jié)的不仔細(xì)铆惑,邏輯也不太清楚,等有時(shí)間會(huì)再修改梳理一下。
如有錯(cuò)誤請(qǐng)指正鸭津,感謝閱讀,謝謝大家肠缨!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逆趋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晒奕,更是在濱河造成了極大的恐慌闻书,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脑慧,死亡現(xiàn)場(chǎng)離奇詭異魄眉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)闷袒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門坑律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人囊骤,你說(shuō)我怎么就攤上這事晃择。” “怎么了也物?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵宫屠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我滑蚯,道長(zhǎng)浪蹂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任告材,我火速辦了婚禮坤次,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘创葡。我一直安慰自己浙踢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布灿渴。 她就那樣靜靜地躺著洛波,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骚露。 梳的紋絲不亂的頭發(fā)上蹬挤,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音棘幸,去河邊找鬼焰扳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吨悍。 我是一名探鬼主播扫茅,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼育瓜!你這毒婦竟也來(lái)了葫隙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤躏仇,失蹤者是張志新(化名)和其女友劉穎恋脚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焰手,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糟描,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了书妻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片船响。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驻子,靈堂內(nèi)的尸體忽然破棺而出灿意,到底是詐尸還是另有隱情,我是刑警寧澤崇呵,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布缤剧,位于F島的核電站,受9級(jí)特大地震影響域慷,放射性物質(zhì)發(fā)生泄漏荒辕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一犹褒、第九天 我趴在偏房一處隱蔽的房頂上張望抵窒。 院中可真熱鬧,春花似錦叠骑、人聲如沸李皇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)掉房。三九已至,卻和暖如春慰丛,著一層夾襖步出監(jiān)牢的瞬間卓囚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工诅病, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哪亿,地道東北人粥烁。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蝇棉,于是被迫代替她去往敵國(guó)和親讨阻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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