iOS-淺談OC中Block的變量捕獲和__block修飾符

目錄

  • 變量捕獲
  • 變量
    ---- 變量的基本知識
    ---- 為什么要捕獲變量?
    ---- 為什么局部變量需要捕獲钝吮?
    ---- 為什么全局變量不用捕獲奇瘦?
    ---- self會被捕獲嗎?
    ---- 成員變量會被捕獲嗎巧鸭?
  • 局部變量捕獲和數(shù)值修改探究
    ---- 局部自動auto變量
  • 靜態(tài)static變量捕獲和數(shù)值修改探究
  • 全局變量捕獲和數(shù)值修改探究
  • __block修飾符

變量捕獲

如果Block的執(zhí)行體使用了外界的局部變量纲仍,為了保證Block內(nèi)部能夠正常訪問外部的變量郑叠,Block有一個捕獲變量的機制明棍。
相當(dāng)于往Block結(jié)構(gòu)體里增加一個成員變量摊腋,把值傳遞給這個成員變量兴蒸,分為值傳遞指針傳遞
如果執(zhí)行體使用了外界的全局變量蕾殴,則不需要捕獲钓觉,直接使用即可荡灾。

變量

  • 變量的基本知識

首先變量可以分為兩種:局部變量和全局變量瞬铸。
局部變量分為:局部自動auto變量和局部靜態(tài)static變量赴捞。
全局變量分為:全局變量和全局靜態(tài)static變量赦政。
  • 為什么要捕獲變量耀怜?

因為變量有作用域的限制财破,在Block里面使用Block外聲明的局部變量左痢,相當(dāng)于跨函數(shù)使用這個局部變量系洛。
如果不存一份到Block里面描扯,是無法使用的绽诚,會造成訪問無效內(nèi)存,因為外面的局部變量有可能過了作用域就會自動被銷毀卒落。
  • 為什么局部變量需要捕獲儡毕?

因為局部變量只能在方法內(nèi)部訪問妥曲,離開作用域(大括號)就會自動銷毀钦购。
  • 為什么全局變量不用捕獲押桃?

因為作用域是全局唱凯,無論方法內(nèi)外都可以隨時訪問磕昼。
  • self會被捕獲嗎节猿?

會,因為self也是局部變量浸间,我們來回想一下魁蒜,在OC里調(diào)用方法實際上會傳遞self指針的參數(shù),而且捕獲的是指針吩翻,所以屬于引用傳遞狭瞎。
objc_msgSend(id self, SEL _cmd, ...)
所以我們之所以能在每一個方法中使用self脚作,就是因為默認傳入self變量球涛。
  • 成員變量會被捕獲嗎?

會捺典,因為訪問的成員變量也是局部變量襟己。

局部變量捕獲和數(shù)值修改探究

  • 局部變量只能在方法內(nèi)部訪問擎浴,離開作用域(大括號)就會自動銷毀。
  • Block內(nèi)訪無法修改局部(自動auto)變量科吭。
  • 局部自動auto變量

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 定義一個局部變量
        int global = 100;
        NSLog(@"global Memory address is :%p ",&global);
        // 定義一個Block
        void (^aBlock)(void);
        // 把Block指向一個代碼塊
        aBlock = ^{
            NSLog(@"in block global memory address is :%p ",&global);
            NSLog(@"global is :%d",global);
        };
        // 修改局部變量的值
        global = 101;
        //調(diào)用Block
        aBlock();
        
    }
    return 0;
}
 
global Memory address is :0x16fdff2bc
in block global memory address is :0x1011040a0
global is :100

從結(jié)果來看到在Block中不可以直接修改局部變量,且兩個global內(nèi)存首地址不同唤冈。

我們把OC代碼編譯成C/C++來看下底層實現(xiàn)你虹,僅截取部分如下:

#pragma clang assume_nonnull end

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// Block結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int global;  // Block捕獲變量后相當(dāng)于往Block結(jié)構(gòu)體里增加一個成員變量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// Block的具體代碼區(qū)執(zhí)行區(qū)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int global = __cself->global; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_1,&global);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_2,global);
}

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)};
int main(int argc, const char * argv[]) {
    
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
 
        int global = 100;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_0,&global);

        void (*aBlock)(void);
        aBlock = ((void (*)())&__main_block_impl_0(
                                                   (void *)__main_block_func_0,
                                                   &__main_block_desc_0_DATA,
                                                   global) );

        global = 101;

        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  • 我們看到首先在main函數(shù)中聲明并初始化局部變量int global = 100;看杭。
  • 初始化Block楼雹,給__main_block_impl_0這個結(jié)構(gòu)體傳入局部變量global也就是數(shù)值100贮缅,屬于值傳遞介却。
  • Block捕獲變量后齿坷,往Block結(jié)構(gòu)體中增加一個局部變量int global永淌,來接收這個數(shù)值100遂蛀。
  • 因為是值傳遞所以在main函數(shù)中int global的變化不會影響Blockint global的值,不存在關(guān)聯(lián)關(guān)系螃宙。
  • 具體細節(jié)可以看我上面的注釋谆扎。

靜態(tài)static變量捕獲和數(shù)值修改探究

靜態(tài)變量:用static修飾的變量燕酷,特點是在程序運行過程中,一直在內(nèi)存中存在饵蒂,且只能在方法內(nèi)部訪問退盯。

Block內(nèi)可以直接訪問和修改靜態(tài)變量。

    // 定義一個靜態(tài)變量
    static int global = 100;
    global Memory address is :0x102c68b20
    in block global memory address is :0x102c68b20
    global is :101 

編譯成C/C++代碼進行查看:

#pragma clang assume_nonnull end

// Block結(jié)構(gòu)體
struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 int *global;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};
// Block的具體代碼區(qū)執(zhí)行區(qū)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   int *global = __cself->global; // bound by copy
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_1,&(*global));
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_2,(*global));
}

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)};
int main(int argc, const char * argv[]) {
   
   /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

       static int global = 100;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_0,&global);

       void (*aBlock)(void);
       aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &global));

       global = 101;
       ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);

   }
   return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  • 我們看到首先在main函數(shù)中聲明并初始化靜態(tài)局部變量static int global = 100;毒租。
  • 初始化Block__main_block_impl_0這個結(jié)構(gòu)體傳入變量global的指針墅垮,屬于指針傳遞算色。
  • Block捕獲變量后相當(dāng)于往Block結(jié)構(gòu)體里增加一個局部變量int *global,來接收這個指針峡钓。
  • 因為是指針傳遞椒楣,所以后續(xù)以后續(xù)在__main_block_func_0具體執(zhí)行方法區(qū)中首先拿到global的指針地址牡肉。
  • 通過地址訪問和修改內(nèi)存上的內(nèi)容统锤。
  • 具體細節(jié)可以看我上面的注釋。

補充:static變量一直在內(nèi)存中存在煌寇,所以Block無論什么時候執(zhí)行阀溶,都可以訪問到static的指針鸦泳。

全局變量捕獲和數(shù)值修改探究

全局變量:在程序運行過程中做鹰,一只在內(nèi)存中存在,可以被所有方法訪問钾麸。

Block內(nèi)可以直接訪問和修改全局變量更振。

#import <Foundation/Foundation.h>

// 定義一個全局變量
int global = 100;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         
        NSLog(@"global Memory address is :%p ",&global);
        // 定義一個Block
        void (^aBlock)(void);
        // 把Block指向一個代碼塊
        aBlock = ^{
            NSLog(@"in block global memory address is :%p ",&global);
            NSLog(@"global is :%d",global);
        };
        // 修改全局變量的值
        global = 101;
        //調(diào)用Block
        aBlock();
        
    }
    return 0;
}
    global Memory address is :0x102c68b20
    in block global memory address is :0x102c68b20
    global is :101 

編譯成C/C++代碼進行查看:

#pragma clang assume_nonnull end

int global = 100;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_1,&global);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_2,global);
}

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_0,&global);

        void (*aBlock)(void);

        aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        global = 101;

        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  • Block沒有對全局變量進行捕獲炕桨。
  • 全局變量可以被程序內(nèi)所有方法直接訪問,也就不用block捕獲肯腕。
  • 剩下的這里不過多贅述了献宫。

__block修飾符

  • __block可以用于解決Block內(nèi)部無法修改auto變量值的問題。
  • __block不能修飾全局變量实撒、靜態(tài)static變量。

如果我們想在block中修改局部變量的值奈惑,那就需要在定義局部變量的時候增加修飾詞__block

    // 定義一個局部變量
    __block int global = 100;
    NSLog(@"global內(nèi)存首地址:%p ",&global);
    void(^dBlock)(void) = ^{
        NSLog(@"在Block內(nèi)global內(nèi)存首地址:%p ",&global);
        global  = 101;
        NSLog(@"在Block內(nèi)global的值為:%d",global);
    };
    //調(diào)用Block,觀察global的值是否會被修改
    dBlock();
    NSLog(@"在Block外global的值為:%d",global);
    global內(nèi)存首地址:0x101006308
    在Block中g(shù)lobal內(nèi)存首地址:0x101064bc8
    在Block內(nèi)global的值為:101
    在Block外global的值為:101

結(jié)果所示:在定義局部變量的時候增加修飾詞__block吭净,就可以在Block中修改局部變量的值。

這是為什么呢肴甸?我們接下來進行探究寂殉。把OC代碼編譯成C/C++來看下底層實現(xiàn)。部分代碼如下:


#pragma clang assume_nonnull end

// __Block修飾之后變成了一個結(jié)構(gòu)體
struct __Block_byref_global_0 {
  void *__isa;  // isa指針代表該結(jié)構(gòu)體也是一個OC對象
__Block_byref_global_0 *__forwarding; // 指針變量,指向結(jié)構(gòu)體自己的內(nèi)存首地址原在。
 int __flags;
 int __size;
 int global; // 我們聲明的int global最終存放的位置
};

// Block
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    // 一個__Block_byref_global_0結(jié)構(gòu)體的對象,可以通過指針變量找到
    __Block_byref_global_0 *global; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_global_0 *_global, int flags=0) : global(_global->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
// Block的代碼區(qū)執(zhí)行位置
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    __Block_byref_global_0 *global = __cself->global; // bound by ref
    // global->__forwarding->global
    // 通過__forwarding存放的指針地址找到我們初始化的那個__Block_byref_global_0的結(jié)構(gòu)體
    // 在找到__Block_byref_global_0結(jié)構(gòu)體下面的global變量
    // 最后進行賦值
    (global->__forwarding->global) = 101;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_2,(global->__forwarding->global));
          
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->global, (void*)src->global, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->global, 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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    // __block int global = 100 <==> 轉(zhuǎn)化成了一個__Block_byref_global_0結(jié)構(gòu)體對象
    // 并對該對象進行初始化友扰,
    // 其中__forwarding指針指向自己(&global代表了該指針對象的內(nèi)存首地址)
    __attribute__((__blocks__(byref))) __Block_byref_global_0 global = {
                                                                        (void*)0,
                                                                        (__Block_byref_global_0 *)&global,
                                                                        0,
                                                                        sizeof(__Block_byref_global_0),
                                                                        100};
        
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_0,&(global.__forwarding->global));
          
    // 初始化Block
    // 給__main_block_impl_0這個結(jié)構(gòu)體傳入 &global也就是global對象的指針地址
    void(*dBlock)(void) = ((void (*)())&__main_block_impl_0(
                                                            (void *)__main_block_func_0,
                                                            &__main_block_desc_0_DATA,
                                                            (__Block_byref_global_0 *)&global,
                                                            570425344));

        ((void (*)(__block_impl *))((__block_impl *)dBlock)->FuncPtr)((__block_impl *)dBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_3,(global.__forwarding->global));

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我們可以發(fā)現(xiàn):

  • 通過__block修飾后的局部變量global已經(jīng)不再是一個局部變量了,而是一個__Block_byref_global_0的結(jié)構(gòu)體對象庶柿。
  • 系統(tǒng)會把局部變量作為一個成員變量包裝進它體內(nèi)村怪。
  • 會把__forwarding指針指向自己。

所以不能再把它當(dāng)成一個局部變量來分析了浮庐。

  • Block是不可以直接捕獲這個__block變量global的甚负。
  • Block在初始化的時候會捕獲__block變量global的的指針地址,屬于指針傳遞审残。
  • Block代碼塊中我們看到(global->__forwarding->global) = 101;代表著會通過內(nèi)存直接訪問的形式找到int global并修改值梭域。
  • 具體細節(jié)可以看我上面的注釋。
  • 這里就思考了:

為什么不直接在Block中存儲global呢搅轿,就類似靜態(tài)static變量那樣病涨,偏偏搞個結(jié)構(gòu)體來存放global呢?

  • 我的想法:

結(jié)合Block三種類型的知識我們知道:

  • 當(dāng)了一個Block訪問了auto變量璧坟,那Block的類型就是是__NSStackBlock__類型既穆,存放在棧中。內(nèi)存由系統(tǒng)控制雀鹃,如果超過變量作用域就會被系統(tǒng)自動銷毀幻工。
  • ARC環(huán)境下如果block訪問了auto變量,編譯器會根據(jù)情況自動執(zhí)行copy褐澎,變成__NSMallocBlock__類型会钝,然后將棧上的block復(fù)制到堆上。

當(dāng)Block__NSStackBlock__類型轉(zhuǎn)換成__NSMallocBlock__類型的時候:
Block會從棧中copy到堆中工三,那block在棧中的變量也會跟著copy到堆中迁酸,讓堆Block持有它。
并且讓棧__block變量__forwarding指針指向堆上面的__block變量俭正。
這樣奸鬓,無論是在Block語法內(nèi)外使用__block變量,還是__block變量配置在棧上或堆上掸读,都可以順利地訪問同一個__block變量串远。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市儿惫,隨后出現(xiàn)的幾起案子澡罚,更是在濱河造成了極大的恐慌,老刑警劉巖肾请,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留搔,死亡現(xiàn)場離奇詭異,居然都是意外死亡铛铁,警方通過查閱死者的電腦和手機隔显,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饵逐,“玉大人括眠,你說我怎么就攤上這事”度ǎ” “怎么了掷豺?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長薄声。 經(jīng)常有香客問我萌业,道長,這世上最難降的妖魔是什么奸柬? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任生年,我火速辦了婚禮,結(jié)果婚禮上廓奕,老公的妹妹穿的比我還像新娘抱婉。我一直安慰自己,他們只是感情好桌粉,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布蒸绩。 她就那樣靜靜地躺著,像睡著了一般铃肯。 火紅的嫁衣襯著肌膚如雪患亿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音步藕,去河邊找鬼惦界。 笑死,一個胖子當(dāng)著我的面吹牛咙冗,可吹牛的內(nèi)容都是我干的沾歪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼雾消,長吁一口氣:“原來是場噩夢啊……” “哼灾搏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起立润,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狂窑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后桑腮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泉哈,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡到旦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年旨巷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片添忘。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡采呐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搁骑,到底是詐尸還是另有隱情斧吐,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布仲器,位于F島的核電站煤率,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏乏冀。R本人自食惡果不足惜蝶糯,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辆沦。 院中可真熱鬧昼捍,春花似錦、人聲如沸肢扯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔚晨。三九已至乍钻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背银择。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工多糠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人欢摄。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓熬丧,卻偏偏與公主長得像笋粟,于是被迫代替她去往敵國和親怀挠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

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