OC高級(jí)-autoreleasepool的實(shí)現(xiàn)原理

目錄

  • autorelease的本質(zhì)
  • autorelease對(duì)象什么時(shí)候釋放托修?
  • autoreleasepool的工作原理
  • autoreleasepool的內(nèi)部結(jié)構(gòu)
  • autoreleasepool的嵌套
  • autoreleasePoolPage
  • NSThread梁棠、NSRunLoop 和 NSAutoreleasePool三者之間的關(guān)系
  • 其他autorelease相關(guān)知識(shí)點(diǎn)
  • 面試題
  • 參考文章

autorelease的本質(zhì)

  • autorelease本質(zhì)上就是延遲調(diào)用release方法
  • MRC環(huán)境疑故,通過(guò)調(diào)用[obj autorelease]來(lái)延遲內(nèi)存的釋放
  • ARC環(huán)境轨功,甚至可以完全不知道autorelease也能管理好內(nèi)存

autorelease對(duì)象什么時(shí)候釋放汗茄?

實(shí)驗(yàn)環(huán)境

  • ARC
  • 測(cè)試機(jī)型:iPhone 4S模擬器
  • 注意:在蘋(píng)果一些新的硬件設(shè)備上医咨,本實(shí)驗(yàn)的結(jié)果已經(jīng)不再成立
__weak NSString *_weakStr = nil;
- (void)viewDidLoad
{
    [super viewDidLoad];

    // 場(chǎng)景 1
    NSString *string = [NSString stringWithFormat:@"yanhoo"];
    _weakStr = string;

    // 場(chǎng)景 2
//    @autoreleasepool {
//        NSString *string = [NSString stringWithFormat:@"yanhoo"];
//        _weakStr = string;
//    }

    // 場(chǎng)景 3
//    NSString *string = nil;
//    @autoreleasepool {
//        string = [NSString stringWithFormat:@"yanhoo"];
//        _weakStr = string;
//    }

    NSLog(@"string1: %@", _weakStr);
}

- (void)viewWillAppear:(BOOL)animated 
{
    [super viewWillAppear:animated];

    NSLog(@"string2: %@", _weakStr);
}

- (void)viewDidAppear:(BOOL)animated 
{
    [super viewDidAppear:animated];

    NSLog(@"string3: %@", _weakStr);
}

測(cè)試結(jié)果

場(chǎng)景1
2016-08-19 01:30:01.686 test[8866:554553] string1: yanhoo
2016-08-19 01:30:01.687 test[8866:554553] string2: yanhoo
2016-08-19 01:30:01.695 test[8866:554553] string3: (null)

場(chǎng)景2
2016-08-19 01:32:07.020 test[8886:556042] string1: (null)
2016-08-19 01:32:07.021 test[8886:556042] string2: (null)
2016-08-19 01:32:07.032 test[8886:556042] string3: (null)

場(chǎng)景3
2016-08-19 01:32:57.038 test[8900:557349] string1: yanhoo
2016-08-19 01:32:57.038 test[8900:557349] string2: (null)
2016-08-19 01:32:57.048 test[8900:557349] string3: (null)

分析

  • 首先來(lái)了解下__weak修飾符

    • 不會(huì)影響所指向?qū)ο蟮纳芷冢矗菏褂?code>__weak修飾的變量不會(huì)導(dǎo)致所引用的對(duì)象的引用計(jì)數(shù)+1
    • 當(dāng)__weak修飾的變量所指向的對(duì)象被釋放時(shí)架诞,__weak修飾的變量的值會(huì)被置為nil拟淮,不存在野指針問(wèn)題
  • 場(chǎng)景1分析

    • 當(dāng)使用[NSString stringWithFormat:@"yanhoo"]創(chuàng)建一個(gè)autorelease對(duì)象時(shí),這個(gè)對(duì)象的引用計(jì)數(shù)為1谴忧,并且這個(gè)對(duì)象被系統(tǒng)自動(dòng)添加到了最近autoreleasepool
    • 當(dāng)使用局部變量string(在 ARC 下不指定變量所有權(quán)修飾符的情況下很泊,默認(rèn)為__strong)指向這個(gè)對(duì)象時(shí),這個(gè)對(duì)象的引用計(jì)數(shù) +1 沾谓,變成了 2
    • 所以在viewDidLoad方法返回之前委造,這個(gè)對(duì)象是一直存在的,且引用計(jì)數(shù)為2
    • 當(dāng)viewDidLoad方法返回之后均驶,局部變量 string被回收昏兆,其所指向?qū)ο蟮囊糜?jì)數(shù) -1 ,變成了1
    • viewWillAppear方法中辣恋,我們?nèi)匀豢梢源蛴〕鲞@個(gè)對(duì)象的值亮垫,說(shuō)明這個(gè)對(duì)象并沒(méi)有被釋放,說(shuō)明到此autoreleasepool還未釋放伟骨,從而導(dǎo)致autorelease對(duì)象未釋放饮潦,因?yàn)橹挥挟?dāng)這個(gè)autoreleasepool自身被drain的時(shí)候,autoreleasepool中的 autoreleased 對(duì)象才會(huì)被 release
    • viewDidAppear中再打印這個(gè)對(duì)象的時(shí)候携狭,對(duì)象的值變成了 nil继蜡,說(shuō)明此時(shí)autoreleased對(duì)象已經(jīng)被釋放了,可以大膽猜測(cè)autoreleasepool一定在viewWillAppearviewDidAppear方法之間的某個(gè)時(shí)候被drain
  • 場(chǎng)景2和場(chǎng)景3請(qǐng)讀者自行分析逛腿,這里就不再啰嗦了

  • 可以通過(guò)lldbwatchpoint命令來(lái)觀察稀并,具體參考這篇文章

  • 總結(jié)

    • 場(chǎng)景1出現(xiàn)得最多,就是不需要我們手動(dòng)添加@autoreleasepool {}的情況单默,直接使用系統(tǒng)維護(hù)autoreleasepool碘举;
    • 場(chǎng)景2就是需要我們手動(dòng)添加@autoreleasepool {}的情況,手動(dòng)干預(yù) autoreleased對(duì)象的釋放時(shí)機(jī)搁廓,在一些很耗內(nèi)存的循環(huán)調(diào)用的場(chǎng)景下有時(shí)需要手動(dòng)干預(yù)autoreleased 對(duì)象的釋放時(shí)機(jī)引颈,不然會(huì)導(dǎo)致內(nèi)存暴增,最終導(dǎo)致程序奔潰境蜕;
    • 場(chǎng)景3是為了區(qū)別于場(chǎng)景2而引入的蝙场,在這種場(chǎng)景下并不能達(dá)到出了@autoreleasepool {}的作用域時(shí)autoreleased 對(duì)象被釋放的目的

autoreleasepool的工作原理

  • ARC環(huán)境下,@autoreleasepool{ }被編譯器編譯后粱年,生成如下代碼(以下代碼是簡(jiǎn)化版
// push
void *poolToken = objc_autoreleasePoolPush();

// 這中間為寫(xiě)在{...}中的代碼

// pop
objc_autoreleasePoolPop(poolToken);
  • 在運(yùn)行循環(huán)開(kāi)始前售滤,系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)autoreleasepool(一個(gè)autoreleasepool會(huì)存在多個(gè)AutoreleasePoolPage),此時(shí)會(huì)調(diào)用一次objc_autoreleasePoolPush函數(shù),runtime會(huì)向當(dāng)前的AutoreleasePoolPage中add進(jìn)一個(gè)POOL_SENTINEL哨兵對(duì)象完箩,值為0赐俗,也就是個(gè)nil,代表autoreleasepool的起始邊界)嗜憔,并返回此哨兵對(duì)象的內(nèi)存地址poolToken
  • 在運(yùn)行循環(huán)結(jié)束時(shí)秃励,autoreleasepool會(huì)被drain掉,此時(shí)會(huì)調(diào)用objc_autoreleasePoolPop(poolToken)函數(shù)吉捶,入?yún)⑹侵爱a(chǎn)生的POOL_SENTINEL的內(nèi)存地址poolToken,對(duì)在POOL_SENTINEL之后添加的所有autoreleased對(duì)象調(diào)用一次release皆尔,可以向前跨越若干個(gè)page呐舔,直到哨兵對(duì)象所在的page,并向回移動(dòng)next指針哨兵對(duì)象所在位置
  • 中間{...}所產(chǎn)生的autoreleased對(duì)象都會(huì)被插入到最近的autoreleasepool中(因?yàn)閍utoreleasepool存在嵌套的情況)
  • 單個(gè)autoreleasepool的運(yùn)行過(guò)程可以簡(jiǎn)單地理解為以下三個(gè)過(guò)程
    • objc_autoreleasePoolPush()
      • objc_autoreleasePoolPush()本質(zhì)上就是調(diào)用的 AutoreleasePoolPage的push函數(shù)慷蠕,如下所示
      void *
      objc_autoreleasePoolPush(void)
      {
          if (UseGC) return nil;
          return AutoreleasePoolPage::push();
      }
      
      • 每執(zhí)行一次push操作就會(huì)新建一個(gè)autoreleasepool珊拼,對(duì)應(yīng)的具體實(shí)現(xiàn)就是往AutoreleasePoolPage中的next位置插入一個(gè)POOL_SENTINEL,并且返回插入的POOL_SENTINEL的內(nèi)存地址poolToken流炕,這個(gè)地址在執(zhí)行pop操作的時(shí)候作為函數(shù)的入?yún)⑴煜郑旅媸茿utoreleasePoolPage的push函數(shù)代碼
      static inline void *push()
      {
          id *dest = autoreleaseFast(POOL_SENTINEL);
          assert(*dest == POOL_SENTINEL);
          return dest;
      }
      
      • push 函數(shù)通過(guò)調(diào)autoreleaseFast函數(shù)來(lái)執(zhí)行具體的插入操作
      static inline id *autoreleaseFast(id obj)
      {
          AutoreleasePoolPage *page = hotPage();
          if (page && !page->full()) {// 當(dāng)前 page 存在且沒(méi)有滿(mǎn)時(shí),直接將對(duì)象添加到當(dāng)前 page 中每辟,即 next 指向的位置
              return page->add(obj);
          } else if (page) {// 當(dāng)前 page 存在且已滿(mǎn)時(shí)剑辫,創(chuàng)建一個(gè)新的 page ,并將對(duì)象添加到新創(chuàng)建的 page 中
              return autoreleaseFullPage(obj, page);
          } else {// 當(dāng)前 page 不存在時(shí)渠欺,即還沒(méi)有 page 時(shí)妹蔽,創(chuàng)建第一個(gè) page ,并將對(duì)象添加到新創(chuàng)建的 page 中
              return autoreleaseNoPage(obj);
          }
      }
      
    • [對(duì)象 autorelease]
      • 本質(zhì)上就是調(diào)用AutoreleasePoolPage的autorelease函數(shù)
      __attribute__((noinline,used))
      id
      objc_object::rootAutorelease2()
      {
          assert(!isTaggedPointer());
          return AutoreleasePoolPage::autorelease((id)this);
      }
      
      • AutoreleasePoolPage 的 autorelease 函數(shù)的實(shí)現(xiàn)對(duì)我們來(lái)說(shuō)就比較好理解了挠将,它跟 push 操作的實(shí)現(xiàn)非常相似胳岂。只不過(guò) push 操作插入的是一個(gè) POOL_SENTINEL ,而 autorelease 操作插入的是一個(gè)具體的 autoreleased 對(duì)象
      static inline id autorelease(id obj)
      {
          assert(obj);
          assert(!obj->isTaggedPointer());
          id *dest __unused = autoreleaseFast(obj);
          assert(!dest  ||  *dest == obj);
          return obj;
      }
      
    • objc_autoreleasePoolPop(poolToken)
      • objc_autoreleasePoolPop(poolToken) 函數(shù)本質(zhì)上也是調(diào)用的AutoreleasePoolPage的 pop 函數(shù)
      void
      objc_autoreleasePoolPop(void *ctxt)
      {
          if (UseGC) return;
      
          // fixme rdar://9167170
          if (!ctxt) return;
      
          AutoreleasePoolPage::pop(ctxt);
      }
      
      • pop 函數(shù)的入?yún)⒕褪?push 函數(shù)的返回值舔稀,也就是 POOL_SENTINEL 的內(nèi)存地址poolToken乳丰。當(dāng)執(zhí)行pop操作時(shí),在POOL_SENTINEL內(nèi)存地址在之后添加的所有autoreleased 對(duì)象都會(huì)被release内贮,可以向前跨越若干個(gè)page产园,直到哨兵對(duì)象所在的page,并向回移動(dòng)next指針哨兵對(duì)象所在位置

autoreleasepool的內(nèi)部結(jié)構(gòu)

  • autoreleasepool本質(zhì)上就是一個(gè)指針堆棧
  • 指針堆棧中存放的是autoreleased對(duì)象的內(nèi)存地址 或者 POOL_SENTINEL的內(nèi)存地址
  • 內(nèi)部結(jié)構(gòu)是由若干個(gè)以page為結(jié)點(diǎn)的雙向鏈表組成贺归,系統(tǒng)會(huì)在需要的時(shí)候動(dòng)態(tài)地增加或刪除page節(jié)點(diǎn)淆两,這里說(shuō)的page就是下面即將說(shuō)到的AutoreleasePoolPage對(duì)象

autoreleasepool的嵌套

  • 每產(chǎn)生一個(gè)autoreleasePool,就會(huì)產(chǎn)生一個(gè)哨兵對(duì)象拂酣,作為pool的邊界
  • pool的嵌套其實(shí)就是產(chǎn)生多個(gè)哨兵對(duì)象而已
  • pop的時(shí)候可以向前跨越若干個(gè)page秋冰,直到指定哨兵對(duì)象所在的page為止

AutoreleasePoolPage

  • 一個(gè)空的 AutoreleasePoolPage 的內(nèi)存結(jié)構(gòu)如下圖所示:

    AutoreleasePoolPage.png

    • magic用來(lái)校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
    • next指向下一個(gè)即將產(chǎn)生的autoreleased對(duì)象的存放位置(當(dāng)next == begin()時(shí)婶熬,表示AutoreleasePoolPage為空剑勾;當(dāng)next == end()時(shí)埃撵,表示AutoreleasePoolPage已滿(mǎn)
    • thread指向當(dāng)前線程一個(gè)AutoreleasePoolPage只會(huì)對(duì)應(yīng)一個(gè)線程虽另,但一個(gè)線程可以對(duì)應(yīng)多個(gè)AutoreleasePoolPage暂刘;
    • parent指向父結(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil捂刺;
    • child指向子結(jié)點(diǎn)谣拣,最后一個(gè)結(jié)點(diǎn)的 child 值為 nil;
    • depth代表深度族展,第一個(gè)page的depth為0森缠,往后每遞增一個(gè)page,depth會(huì)加1仪缸;
    • hiwat代表 high water mark
  • 前面所說(shuō)的autoreleasepool的內(nèi)部結(jié)構(gòu)是由若干個(gè)AutoreleasePoolPage為結(jié)點(diǎn)的雙向鏈表組成贵涵,這個(gè)雙向鏈表就是通過(guò)上述結(jié)構(gòu)中的parent指針child指針連接起來(lái)的

  • 每個(gè)AutoreleasePoolPage對(duì)象會(huì)開(kāi)辟4KB內(nèi)存(也就是虛擬內(nèi)存一頁(yè)的大小)恰画,除了上面的實(shí)例變量所占空間宾茂,剩下的空間全部用來(lái)儲(chǔ)存autoreleased對(duì)象的內(nèi)存地址

  • 一個(gè)page的空間被占滿(mǎn)時(shí),會(huì)新建一個(gè)page拴还,通過(guò)parent指針child指針連接鏈表跨晴,之后的autoreleased對(duì)象會(huì)加入到新建的page中

NSThread、NSRunLoop 和 NSAutoreleasePool三者之間的關(guān)系

  • NSThread 和 NSRunLoop是一一對(duì)應(yīng)的關(guān)系
  • 在NSRunLoop對(duì)象的每個(gè)運(yùn)行循環(huán)(event loop)開(kāi)始前自沧,系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)autoreleasepool坟奥,并在運(yùn)行循環(huán)(event loop)結(jié)束時(shí)drain掉這個(gè)pool,同時(shí)釋放所有autoreleased對(duì)象
  • autoreleasepool只會(huì)對(duì)應(yīng)一個(gè)線程拇厢,每個(gè)線程可能會(huì)對(duì)應(yīng)多個(gè)autoreleasepool爱谁,比如autoreleasepool嵌套的情況

Autorelease返回值的快速釋放機(jī)制

  • ARC下,runtime有一套對(duì)autorelease返回值的優(yōu)化策略
  • 通過(guò)objc_autoreleaseReturnValueobjc_retainAutoreleasedReturnValue的配合使用可以達(dá)到最優(yōu)化程序運(yùn)行的目的
  • Thread Local Storage(TLS)線程局部存儲(chǔ)孝偎,在返回值返回前調(diào)用objc_autoreleaseReturnValue方法時(shí)访敌,runtime會(huì)將這個(gè)返回值儲(chǔ)存在TLS中,然后直接返回返回值不調(diào)用autorelease)衣盾,
  • 外部接收這個(gè)返回值時(shí)通過(guò)調(diào)用objc_retainAutoreleasedReturnValue發(fā)現(xiàn)TLS中已存在這個(gè)返回值寺旺,就直接返回(不調(diào)用retain),免去了對(duì)返回值的內(nèi)存管理势决,達(dá)到優(yōu)化目的

其他Autorelease相關(guān)知識(shí)點(diǎn)

  • 使用容器的block版本的枚舉器時(shí)阻塑,內(nèi)部會(huì)自動(dòng)添加一個(gè)autoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
{ 
      // 這里被一個(gè)局部@autoreleasepool{ }包圍著
}];
  • 普通for循環(huán)for in循環(huán)沒(méi)有,所以果复,還是新版的block版本枚舉器更加方便陈莽,但是性能還是for in循環(huán)最高

  • 下面三種情況是需要我們手動(dòng)添加autoreleasepool

    • 如果你編寫(xiě)的程序不是基于 UI 框架的,比如:命令行工具;
    • for循環(huán)中遍歷產(chǎn)生大量autorelease變量時(shí)走搁,就需要手動(dòng)添加加局部autoreleasePool來(lái)進(jìn)行手動(dòng)干預(yù)
    • 如果你創(chuàng)建了一個(gè)子線程独柑,一般會(huì)自定義繼承自NSOperation的操作,在main方法中要加上@autoreleasepool{...}私植,這段代碼是在子線程上執(zhí)行是無(wú)法訪問(wèn)主線程的自動(dòng)釋放池的忌栅,所以得自己創(chuàng)建
    - (void)main
    {
        // 自己創(chuàng)建自動(dòng)釋放池,如果這段代碼是在子線程上執(zhí)行是無(wú)法訪問(wèn)主線程的自動(dòng)釋放池的曲稼,所以得自己創(chuàng)建
        @autoreleasepool
        {
            // 代碼邏輯
        }
    }
    

面試題

  • autoreleasepool的實(shí)現(xiàn)原理

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末索绪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躯肌,更是在濱河造成了極大的恐慌者春,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件清女,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晰筛,警方通過(guò)查閱死者的電腦和手機(jī)嫡丙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)读第,“玉大人曙博,你說(shuō)我怎么就攤上這事×鳎” “怎么了父泳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吴汪。 經(jīng)常有香客問(wèn)我惠窄,道長(zhǎng),這世上最難降的妖魔是什么漾橙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任杆融,我火速辦了婚禮,結(jié)果婚禮上霜运,老公的妹妹穿的比我還像新娘脾歇。我一直安慰自己,他們只是感情好淘捡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布藕各。 她就那樣靜靜地躺著,像睡著了一般焦除。 火紅的嫁衣襯著肌膚如雪激况。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音誉碴,去河邊找鬼宦棺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛黔帕,可吹牛的內(nèi)容都是我干的代咸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼成黄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呐芥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起奋岁,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤思瘟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后闻伶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體滨攻,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蓝翰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了光绕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畜份,死狀恐怖诞帐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爆雹,我是刑警寧澤停蕉,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站钙态,受9級(jí)特大地震影響慧起,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驯绎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一完慧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剩失,春花似錦屈尼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至演熟,卻和暖如春鞭执,著一層夾襖步出監(jiān)牢的瞬間司顿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工兄纺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留大溜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓估脆,卻偏偏與公主長(zhǎng)得像钦奋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疙赠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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