【OC語法】block捕獲變量

目錄
一庆锦、block捕獲普通變量
? 1、block會捕獲局部變量轧葛,普通則值傳遞搂抒,靜態(tài)則指針傳遞
? 2、block不會捕獲全局變量
二尿扯、block捕獲指針變量
? 1燕耿、block會捕獲局部對象類型的指針變量,強指針(__strong)則持有對象姜胖、弱指針(__weak)則不持有對象
? 2誉帅、block會捕獲self,強指針(__strong)則持有對象右莱、弱指針(__weak)則不持有對象

這個一定要記住蚜锨,拿著這個分析面試題很穩(wěn):block捕獲變量是指,如果block的執(zhí)行體里使用了外界的局部變量慢蜓,那么block內(nèi)部就會生成一個與局部變量同名的成員變量亚再,并且局部變量還會把值傳遞給這個成員變量,當然可能是值傳遞——使用了外界的普通局部變量時晨抡,也有可能是指針傳遞——使用了外界的靜態(tài)局部變量時氛悬。那么接下來block執(zhí)行體里使用的這個變量就不是外界的局部變量了,而是block體內(nèi)的成員變量耘柱。而如果block的執(zhí)行體里使用了外界的全局變量如捅,那block是不會捕獲它們的,會直接使用它們调煎。所以要想知道一個變量會不會被block捕獲镜遣,你只需要搞清變量是個局部變量還是個全局變量就行了,別去管block是什么類型的block士袄。

那為什么系統(tǒng)要給block添加捕獲變量機制呢悲关?又為什么只捕獲局部變量而不捕獲全局變量呢?實際開發(fā)中娄柳,我們難免要在block的執(zhí)行體里使用外界的局部變量寓辱,我們知道block其實是把block的參數(shù)、返回值赤拒、執(zhí)行體封裝成一個函數(shù)秫筏,而這個函數(shù)在調(diào)用時卻僅僅接收了block本身作為參數(shù)诱鞠,

來自上一篇

// 創(chuàng)建一個block
void (*block)(void) = &__block_impl_0(
                                      __block_func_0,// 把函數(shù)的地址傳進去
                                      &__block_desc_0_DATA // 把結構體的地址傳進去
                                      );

// 調(diào)用block
block->impl.FuncPtr(block);

并沒有接收額外的參數(shù),所以一個函數(shù)怎么可能無緣無故就訪問到函數(shù)外部的變量呢跳昼。于是系統(tǒng)就為block設計了捕獲變量機制般甲,把局部變量捕獲到block體內(nèi)肋乍,以便函數(shù)僅僅接收block本身作為參數(shù)就能正常使用外界的局部變量鹅颊。而全局變量存儲在全局區(qū),block能直接訪問到墓造,所以不需要捕獲堪伍。


一、block捕獲普通變量


  • block會捕獲局部變量
  • block不會捕獲全局變量

1觅闽、block會捕獲局部變量帝雇,普通則值傳遞,靜態(tài)則指針傳遞

  • block會捕獲普通局部變量蛉拙,且局部變量與成員變量之間是值傳遞
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 普通局部變量
        int age = 25;
        
        void (^block)(void) = ^{ // ARC下這是個堆block尸闸,因為block賦值給強指針,系統(tǒng)會自動復制一份到堆區(qū)
            
            NSLog(@"%d", age); // 25
        };
        
        age = 26;
        
        block();
    }
    return 0;
}

按正常邏輯來說孕锄,上面的代碼應該打印“26”吮廉,因為在block調(diào)用之前age被改成“26”了,但實際上卻打印“25”畸肆,為什么宦芦?我們看看這段代碼的C/C++實現(xiàn)(偽代碼)。

// block對應的結構體
struct __block_impl_0 {
    struct __block_impl impl;
    struct __block_desc_0* Desc;
    
    int age; // 多了一個成員變量
    
    // : age(_age)轴脐,C++的語法调卑,意思是直接把_age參數(shù)的值賦值給age成員變量,相當于下面又多了一句賦值語句
    __block_impl_0(void *fp, struct __block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
        
//      age = _age;// 相當于這樣
    }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        int age = 25;
        
        // 創(chuàng)建block
        void (*block)(void) = &__block_impl_0(
                                              __block_func_0,
                                              &__block_desc_0_DATA,
                                              age // 多了一個參數(shù)
                                              );
        
        age = 26;
        
        // 調(diào)用block
        block->impl.FuncPtr)(block);
    }
    return 0;
}

void __block_func_0(struct __block_impl_0 *__cself) {
    
    int age = __cself->age; // 獲取age成員變量的值
    
    NSLog(age);
}

我們看到block內(nèi)部多了一個成員變量age大咱。

也看到在創(chuàng)建block的時候恬涧,block構造函數(shù)多了一個age參數(shù),直接把變量的值“25”給傳進去了碴巾,并賦值給block的成員變量age气破。

然后外界把變量age的值改為“26”。

調(diào)用block時餐抢,系統(tǒng)讀取的是block內(nèi)部那個成員變量的值现使,所以打印了“25”。

  • block會捕獲靜態(tài)局部變量旷痕,但局部變量與成員變量之間是指針傳遞
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 靜態(tài)局部變量
        static int height = 25;
        
        void (^block)(void) = ^{ // 這是個全局block
            
            NSLog(@"%d", height); // 26
        };
        
        height = 26;
        
        block();
    }
    return 0;
}

打印“25”紅還是“26”??碳锈?直接看C/C++實現(xiàn)吧(偽代碼)。

// block對應的結構體
struct __block_impl_0 {
    struct __block_impl impl;
    struct __block_desc_0* Desc;
    
    int *height; // 多了一個成員變量欺抗,注意是個指針
    
    // : height(_height)售碳,C++的語法,意思是直接把_height參數(shù)的值賦值給height成員變量,相當于下面又多了一句賦值語句
    __block_impl_0(void *fp, struct __block_desc_0 *desc, int *_height, int flags=0) : height(_height) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
        
//      height = _height; // 相當于這樣
    }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        static int height = 25;
        
        // 創(chuàng)建block
        void (*block)(void) = &__block_impl_0(
                                              __block_func_0,
                                              &__block_desc_0_DATA,
                                              &height // 多了一個參數(shù)贸人,注意是個地址
                                              );
        
        height = 26;
        
        // 調(diào)用block
        block->impl.FuncPtr)(block);
    }
    return 0;
}

void __block_func_0(struct __block_impl_0 *__cself) {
    
    int *height = __cself->height; // 獲取height成員變量的值
    
    NSLog(*height);
}

沒問題间景,我們看到block內(nèi)部多了一個成員變量height,但要注意它是個指針類型艺智。

也看到在創(chuàng)建block的時候倘要,block構造函數(shù)多了一個height參數(shù),但這里不是直接把變量的值傳進去十拣,而是把變量的地址給傳進去了封拧,并賦值給block的成員變量height

然后外界把變量height的值改為“26”夭问。

調(diào)用block時泽西,系統(tǒng)讀取的是block內(nèi)部那個成員變量的值沒問題,但因為它是個指針缰趋,指向外界的那個變量捧杉,所以打印了“26”。

1秘血、再加深一下印象:block會捕獲局部變量

  • block會捕獲普通局部變量味抖,局部變量與成員變量之間是值傳遞
  • block會捕獲靜態(tài)局部變量,局部變量與成員變量之間是指針傳遞

2直撤、那系統(tǒng)為什么要這樣設計呢非竿?同樣都是局部變量,為什么普通局部變量是值傳遞谋竖,而靜態(tài)局部變量是指針傳遞红柱?

void (^block)(void);
void test() {
    
    // 普通局部變量
    int age = 25;
    // 靜態(tài)局部變量
    static int height = 25;
    
    block = ^{ // ARC下這是個堆block,因為block賦值給強指針蓖乘,系統(tǒng)會自動復制一份到堆區(qū)
        
        NSLog(@"%d %d", age, height);
    };
        
    age = 26;
    height = 26;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
    }
    return 0;
}

一看上面這段代碼锤悄,你就明白了。

  • test函數(shù)執(zhí)行完嘉抒,也就是說出了test函數(shù)的作用域零聚,
  • 普通局部變量age就釋放了,也就是說它對應的那塊棧內(nèi)存就釋放了些侍,有可能被別人征用隶症,里面填充別的數(shù)據(jù),那內(nèi)存釋放后你再去訪問這塊內(nèi)存岗宣,訪問到不一定是原來的數(shù)據(jù)蚂会,所以普通局部變量采用指針傳遞根本沒有意義,因為它對應的那塊內(nèi)存說不定什么時候(即有可能在我們使用它之前)就釋放掉了耗式,所以還不如趁早把局部變量的值給存下來胁住。
  • 而靜態(tài)局部變量就不一樣了趁猴,出了test函數(shù)的作用域,height變量雖然也被釋放掉了彪见,但這僅僅是表明在代碼層我們無法再繼續(xù)通過height變量去訪問它對應的那塊內(nèi)存而已儡司,并不代表那塊內(nèi)存也釋放了,因為這塊內(nèi)存是靜態(tài)全局區(qū)的一塊內(nèi)存余指,所以我們只要用一個指針變量來記住這塊內(nèi)存的地址捕犬,那height變量釋放后,我們依舊可以通過自己的指針變量去訪問那塊內(nèi)存浪规。

2或听、block不會捕獲全局變量

// 普通全局變量
int age = 25;
// 靜態(tài)全局變量
static int height = 25;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{ // 這是個全局block
            
            NSLog(@"%d %d", age, height); // 26, 26
        };
        
        age = 26;
        height = 26;
        
        block();
    }
    return 0;
}

C/C++實現(xiàn)(偽代碼)探孝。

// block對應的結構體
struct __block_impl_0 {
    struct __block_impl impl;
    struct __block_desc_0* Desc;

    __block_impl_0(void *fp, struct __block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

int age = 25;
static int height = 25;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        // 創(chuàng)建block
        void (*block)(void) = &__block_impl_0(
                                              __block_func_0,
                                              &__block_desc_0_DATA,
                                              );
        
        age = 26;
        height = 26;
        
        // 調(diào)用block
        block->impl.FuncPtr)(block);
    }
    return 0;
}

void __block_func_0(struct __block_impl_0 *__cself) {
    NSLog(age, height); // 直接訪問全局變量
}

我們看到block內(nèi)部并不會多出成員變量笋婿,而且調(diào)用block時,是直接通過全局變量訪問對應內(nèi)存里的數(shù)據(jù)顿颅。


二缸濒、block捕獲指針變量


1、block會捕獲局部對象類型的指針變量粱腻,強指針(__strong)則持有對象庇配、弱指針(__weak)則不持有對象

block會捕獲局部對象類型的指針變量,而且捕獲后如果發(fā)現(xiàn)它是個強指針(即__strong修飾)绍些,block還會強引用(即持有)它指向的對象捞慌,如果發(fā)現(xiàn)它是個弱指針(即__weak修飾),block則會弱引用(即不持有)它指向的對象柬批。(如果更嚴謹一點的話啸澡,棧block永遠只是弱引用對象,只不過因為我們是ARC下氮帐,用的基本上都是堆block嗅虏,所以就故意忽略掉了這一點,免得大家混淆)

創(chuàng)建一個Person類上沐,簡單實現(xiàn)一下皮服,來驗證上面這條結論。

// INEPerson.h
@interface INEPerson : NSObject

@property (nonatomic, assign) NSInteger age;

@end


// INEPerson.m
@implementation INEPerson

- (void)dealloc {
    
    NSLog(@"INEPerson dealloc");
}

@end
  • block捕獲強指針
// main.m
typedef void (^INEBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool
    { // 作用域2起點
        
        INEBlock block;
        
        { // 作用域1起點
            
            INEPerson *person;
            
            person = [[INEPerson alloc] init];
            person.age = 25;
            
            block = ^{
                
                NSLog(@"%ld", person.age);
            };
        } // 作用域1終點
        
        NSLog(@"11");
    } // 作用域2終點
    
    return 0;
}

控制臺打硬瘟:

11
INEPerson dealloc

block的C/C++實現(xiàn)(偽代碼):

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    
  INEPerson *__strong person; // 確實捕獲了龄广,是個強指針
    
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, INEPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

我們知道person指針變量是個局部變量,所以它肯定會被block捕獲蕴侧,而且person指針變量默認是個強指針择同,所以block內(nèi)部生成的同名成員變量也是一個強指針,于是block就通過它內(nèi)部的那個強指針強引用了person指針變量指向的Person對象戈盈。

所以出了作用域1后奠衔,雖然person指針變量銷毀了谆刨,但此時block還沒銷毀,它還強引用著Person對象归斤,所以這個時候就不會走Person對象dealloc方法痊夭,而是繼續(xù)往下走,打印完“11”脏里、出了作用域2后她我,block銷毀,同時也就釋放了對Person對象的強引用迫横,所以此時才走Person對象的dealloc方法打印了“INEPerson dealloc”番舆。

  • block捕獲弱指針
// main.m
typedef void (^INEBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool
    { // 作用域2起點
        
        INEBlock block;
        
        { // 作用域1起點
            
            __weak INEPerson *person;
            
            person = [[INEPerson alloc] init];
            person.age = 25;
            
            block = ^{
                
                NSLog(@"%ld", person.age);
            };
        } // 作用域1終點
        
        NSLog(@"11");
    } // 作用域2終點
    
    return 0;
}

控制臺打印:

INEPerson dealloc
11

block的C/C++實現(xiàn)(偽代碼):

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    
  INEPerson *__weak person; // 確實捕獲了矾踱,是個弱指針
    
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, INEPerson *__weak _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

block確實會捕獲person指針變量恨狈,但因為它是個弱指針,所以block就通過它內(nèi)部的那個弱指針弱引用了person指針變量指向的Person對象呛讲。

所以出了作用域1后禾怠,person指針變量銷毀,Person對象身上就沒有強引用了贝搁,所以這個時候就走Person對象的dealloc方法打印了“INEPerson dealloc”吗氏,然后繼續(xù)往下走,打印完“11”雷逆、出了作用域2后弦讽,block銷毀。

此時膀哲,你可能會問:block捕獲指針倒是沒問題往产,但你憑什么說捕獲到強指針就持有對象,捕獲到弱指針就不持有對象等太,上面雖然通過代碼驗證了捂齐,但這底層是怎么實現(xiàn)的?

從上面的代碼中缩抡,我們可以看到只要是block捕獲了對象類型的指針變量奠宜,那它結構體內(nèi)第二個成員變量里就會多出兩個函數(shù),copy函數(shù)和dispose函數(shù)瞻想,這兩個函數(shù)是專門用來負責對象的內(nèi)存管理的压真,這也是為什么block捕獲基本數(shù)據(jù)類型的變量時,它內(nèi)部不會生成這兩個函數(shù)蘑险。

持有不持有主要靠的是block內(nèi)部的copy函數(shù)和dispose函數(shù)滴肿,當我們把block從棧區(qū)copy到堆區(qū)時,系統(tǒng)就會自動調(diào)用block內(nèi)部的copy函數(shù)佃迄,該函數(shù)內(nèi)部會根據(jù)捕獲到的是個強指針還是弱指針來決定要不要把對象的引用計數(shù)加1泼差,而當block銷毀的時候贵少,系統(tǒng)又會自動調(diào)用內(nèi)部的dispose函數(shù),來解除對對象的引用堆缘。

2滔灶、block會捕獲self(指針變量),強指針(__strong)則持有對象吼肥、弱指針(__weak)則不持有對象

創(chuàng)建一個Person類录平,簡單實現(xiàn)一下,來驗證上面這條結論缀皱。

// INEPerson.m
@implementation INEPerson

- (void)test {
    
    void (^block)(void) = ^{
        
        NSLog(@"%@", self);
    };
    block();
}

@end

block的C/C++實現(xiàn)(偽代碼)斗这。

struct __INEPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __INEPerson__test_block_desc_0* Desc;
    
  INEPerson *__strong self;
    
  __INEPerson__test_block_impl_0(void *fp, struct __INEPerson__test_block_desc_0 *desc, INEPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可見self被block捕獲了,那為什么會捕獲self呢啤斗?這是因為所有的OC方法其實都有兩個默認的參數(shù):self指針變量和_cmd selecotr表箭,即該方法調(diào)用者和該方法的selector,而方法的參數(shù)也是一種局部變量争占,所以self會被block捕獲燃逻。上面的test方法其實就是這樣(偽代碼):

- (void)test(id self, SEL _cmd) {
    
    void (^block)(void) = ^{
        
        NSLog(@"%@", self);
    };
    block();
}

self指針默認也是個強指針序目,所以block會持有它指向的對象臂痕,而如果把self指針變成弱指針,block就不會持有它指向的對象了猿涨。

// INEPerson.m
@implementation INEPerson

- (void)test {
    
    __weak INEPerson *weakSelf = self;
    void (^block)(void) = ^{
        
        NSLog(@"%@", weakSelf);
    };
    block();
}

@end

block的C/C++實現(xiàn)(偽代碼)握童。

struct __INEPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __INEPerson__test_block_desc_0* Desc;
    
  INEPerson *__weak weakSelf;
    
  __INEPerson__test_block_impl_0(void *fp, struct __INEPerson__test_block_desc_0 *desc, INEPerson *__weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者叛赚。
  • 序言:七十年代末澡绩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俺附,更是在濱河造成了極大的恐慌肥卡,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事镣,死亡現(xiàn)場離奇詭異步鉴,居然都是意外死亡,警方通過查閱死者的電腦和手機璃哟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門氛琢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人随闪,你說我怎么就攤上這事阳似。” “怎么了铐伴?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵撮奏,是天一觀的道長俏讹。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么测砂? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任飘哨,我火速辦了婚禮,結果婚禮上于微,老公的妹妹穿的比我還像新娘。我一直安慰自己青自,他們只是感情好株依,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著延窜,像睡著了一般恋腕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逆瑞,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天荠藤,我揣著相機與錄音,去河邊找鬼获高。 笑死哈肖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的念秧。 我是一名探鬼主播淤井,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摊趾!你這毒婦竟也來了币狠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤砾层,失蹤者是張志新(化名)和其女友劉穎漩绵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肛炮,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡止吐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铸董。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祟印。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粟害,靈堂內(nèi)的尸體忽然破棺而出蕴忆,到底是詐尸還是另有隱情,我是刑警寧澤悲幅,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布套鹅,位于F島的核電站站蝠,受9級特大地震影響,放射性物質發(fā)生泄漏卓鹿。R本人自食惡果不足惜菱魔,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吟孙。 院中可真熱鬧澜倦,春花似錦、人聲如沸杰妓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巷挥。三九已至桩卵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間倍宾,已是汗流浹背雏节。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留高职,地道東北人钩乍。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像初厚,于是被迫代替她去往敵國和親件蚕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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