iOS RunLoop

什么是RunLoop瘪弓?

  • 可以理解為字面意思:Run表示運(yùn)行劫映,Loop表示循環(huán)乓旗。結(jié)合在一起就是運(yùn)行的循環(huán)的意思府蛇。哈哈,我更愿意翻譯為『跑圈』屿愚。直觀理解就像是不停的跑圈
  • RunLoop實(shí)際上是一個(gè)對(duì)象汇跨,這個(gè)對(duì)象在循環(huán)中用來(lái)處理程序運(yùn)行過(guò)程中出現(xiàn)的各種事件(比如說(shuō)觸摸事件、UI刷新事件妆距、定時(shí)器事件穷遂、Selector事件),從而保持程序的持續(xù)運(yùn)行娱据;而且在沒(méi)有事件處理的時(shí)候蚪黑,會(huì)進(jìn)入睡眠模式,從而節(jié)省CPU資源中剩,提高程序性能
  • RunLoop保存在一個(gè)全局的字典里祠锣,線程作為key,RunLoop作為value

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

  • _modes:存放所有的模式
  • _pthread:存放的線程
  • _currentMode:當(dāng)前的模式


    c3d9bf03.jpg

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

  • _soutce0, _soutce1:存放在CFRunLoopSourceRef
  • _observers:存放在CFRunLoopObserverRef
  • _timers:存放在CFRunLoopTimerRef


    3df6dff2.jpg

RunLoop和線程

  • RunLoop和線程是息息相關(guān)的咽安,我們知道線程的作用是用來(lái)執(zhí)行特定的一個(gè)或多個(gè)任務(wù),但是在默認(rèn)情況下蓬推,線程執(zhí)行完之后就會(huì)退出妆棒,就不能再執(zhí)行任務(wù)了。這時(shí)我們就需要采用一種方式來(lái)讓線程能夠處理任務(wù)沸伏,并不退出糕珊。所以,我們就有了RunLoop
    • 一條線程對(duì)應(yīng)一個(gè)RunLoop對(duì)象毅糟,每條線程都有唯一一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象红选。
    • 我們只能在當(dāng)前線程中操作當(dāng)前線程的RunLoop,而不能去操作其他線程的RunLoop姆另。
    • RunLoop對(duì)象在第一次獲取RunLoop時(shí)創(chuàng)建喇肋,銷毀則是在線程結(jié)束的時(shí)候坟乾。
    • 主線程的RunLoop對(duì)象系統(tǒng)自動(dòng)幫助我們創(chuàng)建好了(原理如下),而子線程的RunLoop對(duì)象需要我們主動(dòng)創(chuàng)建蝶防。
    • 子線程的runLoop里面至少要有一個(gè)source或者是timer甚侣,observer是不行的。

默認(rèn)情況下主線程的RunLoop原理

  • 我們?cè)趩?dòng)一個(gè)iOS程序的時(shí)候间学,系統(tǒng)會(huì)調(diào)用創(chuàng)建項(xiàng)目時(shí)自動(dòng)生成的main.m的文件殷费。main.m文件如下所示:

    int main(int argc, char * argv[]) {
       @autoreleasepool {
           return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       }
    }
    
    
  • 其中UIApplicationMain函數(shù)內(nèi)部幫我們開(kāi)啟了主線程的RunLoop,UIApplicationMain內(nèi)部擁有一個(gè)無(wú)線循環(huán)的代碼低葫。上邊的代碼中開(kāi)啟RunLoop的過(guò)程可以簡(jiǎn)單的理解為如下代碼:

     int main(int argc, char * argv[]) {        
         BOOL running = YES;
         do {
             // 執(zhí)行各種任務(wù)详羡,處理各種事件
             // ......
         } while (running);
    
         return 0;
     }
    
    

    從上邊可看出,程序一直在do-while循環(huán)中執(zhí)行嘿悬,所以UIApplicationMain函數(shù)一直沒(méi)有返回实柠,我們?cè)谶\(yùn)行程序之后程序不會(huì)馬上退出,會(huì)保持持續(xù)運(yùn)行狀態(tài)鹊漠。

    下圖是蘋(píng)果官方給出的RunLoop模型圖主到。

    1877784-6ab632fc118e31f3.jpg

從上圖中可以看出,RunLoop就是線程中的一個(gè)循環(huán)躯概,RunLoop在循環(huán)中會(huì)不斷檢測(cè)登钥,通過(guò)Input sources(輸入源)和Timer sources(定時(shí)源)兩種來(lái)源等待接受事件;然后對(duì)接受到的事件通知線程進(jìn)行處理娶靡,并在沒(méi)有事件的時(shí)候進(jìn)行休息

RunLoop相關(guān)類

  • 下面我們來(lái)了解一下Core Foundation框架下關(guān)于RunLoop的5個(gè)類牧牢,只有弄懂這幾個(gè)類的含義,我們才能深入了解RunLoop運(yùn)行機(jī)制
    • CFRunLoopRef:代表RunLoop的對(duì)象
    • CFRunLoopModeRef:RunLoop的運(yùn)行模式
    • CFRunLoopSourceRef:就是RunLoop模型圖中提到的輸入源/事件源
      • source0:
        • 觸摸事件姿锭,
        • performSelector:onThread
      • source1:
        • 基于Port的線程間的通信
        • 系統(tǒng)事件的捕捉
    • CFRunLoopTimerRef:就是RunLoop模型圖中提到的定時(shí)源
      • NSTimer
      • performSelector:withObject:afterDelay
    • CFRunLoopObserverRef:觀察者塔鳍,能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變
      • 用于監(jiān)聽(tīng)RunLoop的狀態(tài)
      • UI刷新(BeforeWaiting)
      • AutoreleasePool


        1877784-2177aa2828b1ad34.png

CFRunLoopRef

  • CFRunLoopRef就是Core Foundation框架下RunLoop對(duì)象類。我們可通過(guò)以下方式來(lái)獲取RunLoop對(duì)象:

    • Core Foundation

      • CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
      • CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象

      當(dāng)然呻此,在Foundation框架下獲取RunLoop對(duì)象類的方法如下:

    • Foundation

      • [NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
      • [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象

CFRunLoopModeRef

  • CFRunLoopModeRef代表著RunLoop的運(yùn)行模式
  • 一個(gè)RunLoop有若干個(gè)Mode轮纫,每個(gè)模式又包含若干個(gè)Source0,Source1焚鲜,Timers掌唾,Observer
  • RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
  • 如果需要切換運(yùn)行模式忿磅,只能退出Loop糯彬,再重新選擇一個(gè)運(yùn)行模式進(jìn)入。
  • 這樣做主要是為了分隔開(kāi)不同組的Source0葱她,Source1撩扒,Timers,Observer吨些,讓其互不影響 搓谆。
  • 如果Mode里面沒(méi)有Source0炒辉,Source1,Timers挽拔,Observer辆脸,RunLoop會(huì)馬上退出

常見(jiàn)的幾種模式

  • kCFRunLoopDefaultMode:App的默認(rèn)運(yùn)行模式,通常主線程是在這個(gè)運(yùn)行模式下運(yùn)行
  • UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動(dòng)螃诅,保證界面滑動(dòng)時(shí)不受其他Mode影響)
  • UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè) Mode啡氢,啟動(dòng)完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件,通常用不到
  • kCFRunLoopCommonModes:偽模式术裸,不是一種真正的運(yùn)行模式(后邊會(huì)用到)

CFRunLoopObserverRef

  • 創(chuàng)建Observer
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
       kCFRunLoopEntry = (1UL << 0),               // 即將進(jìn)入Loop:1
       kCFRunLoopBeforeTimers = (1UL << 1),        // 即將處理Timer:2    
       kCFRunLoopBeforeSources = (1UL << 2),       // 即將處理Source:4
       kCFRunLoopBeforeWaiting = (1UL << 5),       // 即將進(jìn)入休眠:32
       kCFRunLoopAfterWaiting = (1UL << 6),        // 即將從休眠中喚醒:64
       kCFRunLoopExit = (1UL << 7),                // 即將從Loop中退出:128
       kCFRunLoopAllActivities = 0x0FFFFFFFU       // 監(jiān)聽(tīng)全部狀態(tài)改變  
    };
    
    
     //創(chuàng)建一個(gè)監(jiān)聽(tīng)對(duì)象
     //    第一個(gè)參數(shù):分配存儲(chǔ)空間
     //    第二個(gè)參數(shù):要監(jiān)聽(tīng)的狀態(tài)
     //    第三個(gè)參數(shù): 是否要持續(xù)監(jiān)聽(tīng)
     //    第四個(gè)參數(shù):優(yōu)先級(jí)
     //    第五個(gè)參數(shù):回調(diào)
      CFRunLoopObserverRef obser =  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
     //        狀態(tài)判斷
             switch (activity) {
                 case kCFRunLoopEntry:
    
                     break;
                 case kCFRunLoopBeforeTimers:
    
                     break;
                 case kCFRunLoopBeforeSources:
    
                     break;
                 case kCFRunLoopBeforeWaiting:
    
                     break;
                 case kCFRunLoopAfterWaiting:
    
                     break;
                 case kCFRunLoopExit:
    
                     break;
                 case kCFRunLoopAllActivities:
    
                     break;
                 default:
                     break;
             }
         });
    
     //    第一個(gè)參數(shù):要監(jiān)聽(tīng)哪個(gè)Loop
     //    第二個(gè)參數(shù):監(jiān)聽(tīng)者
     //    第三個(gè)參數(shù): 要監(jiān)聽(tīng)runloop在哪種運(yùn)行模式下的狀態(tài)
     CFRunLoopAddObserver(CFRunLoopGetCurrent(), obser, kCFRunLoopDefaultMode);
    
    
  • CFRunLoopTimerRef
    • 創(chuàng)建定時(shí)器
      NSTimer *time =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
      //設(shè)置runloop,添加到默認(rèn)模式下
      [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
      //設(shè)置runloop,添加到拖拽模式下
       [[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
      //NSRunLoopCommonModes標(biāo)記,在兩種模式下都會(huì)運(yùn)行
      [[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
      
      

RunLoop運(yùn)行邏輯

2dbc55cb.jpg
  • 具體的順序如下:
    • 1.通知觀察者RunLoop已經(jīng)啟動(dòng)
    • 2.通知觀察者即將要開(kāi)始的定時(shí)器
    • 3.通知觀察者任何即將啟動(dòng)的非基于端口的源
    • 4.啟動(dòng)任何準(zhǔn)備好的非基于端口的源
    • 5.如果基于端口的源準(zhǔn)備好并處于等待狀態(tài)倘是,立即啟動(dòng);并進(jìn)入步驟9
    • 6.通知觀察者線程進(jìn)入休眠狀態(tài)
    • 7.將線程置于休眠知道任一下面的事件發(fā)生:
      • 某一事件到達(dá)基于端口的源
      • 定時(shí)器啟動(dòng)
      • RunLoop設(shè)置的時(shí)間已經(jīng)超時(shí)
      • RunLoop被顯示喚醒
    • 8.通知觀察者線程將被喚醒
    • 9.處理未處理的事件
      • 如果用戶定義的定時(shí)器啟動(dòng)袭艺,處理定時(shí)器事件并重啟RunLoop搀崭。進(jìn)入步驟2
      • 如果輸入源啟動(dòng),傳遞相應(yīng)的消息
      • 如果RunLoop被顯示喚醒而且時(shí)間還沒(méi)超時(shí)猾编,重啟RunLoop瘤睹。進(jìn)入步驟2
    • 10.通知觀察者RunLoop結(jié)束

RunLoop運(yùn)用場(chǎng)景詳情

  • 4.1 NSTimer的使用

NSTimer的使用方法在講解CFRunLoopTimerRef類的時(shí)候詳細(xì)講解過(guò),具體參考上邊 2.3 CFRunLoopTimerRef答倡。

  • 4.2 ImageView推遲顯示

有時(shí)候轰传,我們會(huì)遇到這種情況:
當(dāng)界面中含有UITableView,而且每個(gè)UITableViewCell里邊都有圖片瘪撇。這時(shí)候當(dāng)我們滾動(dòng)UITableView的時(shí)候获茬,如果有一堆的圖片需要顯示,那么可能會(huì)出現(xiàn)卡頓的現(xiàn)象倔既。

怎么解決這個(gè)問(wèn)題呢恕曲?

這時(shí)候,我們應(yīng)該推遲圖片的顯示渤涌,也就是ImageView推遲顯示圖片佩谣。有兩種方法:

* 1. 監(jiān)聽(tīng)UIScrollView的滾動(dòng)
  因?yàn)閁ITableView繼承自UIScrollView,所以我們可以通過(guò)監(jiān)聽(tīng)UIScrollView的滾動(dòng)实蓬,實(shí)現(xiàn)UIScrollView相關(guān)delegate即可稿存。

* 2. 利用PerformSelector設(shè)置當(dāng)前線程的RunLoop的運(yùn)行模式

 利用performSelector方法為UIImageView調(diào)用setImage:方法,并利用inModes將其設(shè)置為RunLoop下NSDefaultRunLoopMode運(yùn)行模式瞳秽。代碼如下:
  • 4.3 后臺(tái)常駐線程(很常用)
    - (void)viewDidLoad {
       [super viewDidLoad];
    
       // 創(chuàng)建線程,并調(diào)用run1方法執(zhí)行任務(wù)
       self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
       // 開(kāi)啟線程
       [self.thread start];    
    }
    
    - (void) run1
    {
       // 這里寫(xiě)任務(wù)
       NSLog(@"----run1-----");
    
       // 添加下邊兩句代碼率翅,就可以開(kāi)啟RunLoop练俐,之后self.thread就變成了常駐線程,可隨時(shí)添加任務(wù)冕臭,并交于RunLoop處理
       [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
       [[NSRunLoop currentRunLoop] run];
    
       // 測(cè)試是否開(kāi)啟了RunLoop腺晾,如果開(kāi)啟RunLoop燕锥,則來(lái)不了這里,因?yàn)镽unLoop開(kāi)啟了循環(huán)悯蝉。
       NSLog(@"未開(kāi)啟RunLoop");
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {   
       // 利用performSelector归形,在self.thread的線程中調(diào)用run2方法執(zhí)行任務(wù)
       [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    
    - (void) run2
    {
       NSLog(@"----run2------");
    }
    
    
  • 4.3 封裝常駐線程
    //MJPermenantThread.h
    typedef void (^MJPermenantThreadTask)(void);
    
    @interface MJPermenantThread : NSObject
    
    /**
    開(kāi)啟線程
    */
    //- (void)run;
    
    /**
    在當(dāng)前子線程執(zhí)行一個(gè)任務(wù)
    */
    - (void)executeTask:(MJPermenantThreadTask)task;
    
    /**
    結(jié)束線程
    */
    - (void)stop;
    
    @end
    
    //MJPermenantThread.m
    
    /** MJThread **/
    @interface MJThread : NSThread
    @end
    @implementation MJThread
    - (void)dealloc
    {
      NSLog(@"%s", __func__);
    }
    @end
    
    /** MJPermenantThread **/
    @interface MJPermenantThread()
    @property (strong, nonatomic) MJThread *innerThread;
    @property (assign, nonatomic, getter=isStopped) BOOL stopped;
    @end
    
    @implementation MJPermenantThread
    #pragma mark - public methods
    - (instancetype)init
    {
      if (self = [super init]) {
          self.stopped = NO;
    
          __weak typeof(self) weakSelf = self;
    
          self.innerThread = [[MJThread alloc] initWithBlock:^{
              [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
              while (weakSelf && !weakSelf.isStopped) {
                  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
              }
          }];
    
          [self.innerThread start];
      }
      return self;
    }
    
    //- (void)run
    //{
    //    if (!self.innerThread) return;
    //
    //    [self.innerThread start];
    //}
    
    - (void)executeTask:(MJPermenantThreadTask)task
    {
      if (!self.innerThread || !task) return;
    
      [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
    }
    
    - (void)stop
    {
      if (!self.innerThread) return;
    
      [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
    }
    
    - (void)dealloc
    {
      NSLog(@"%s", __func__);
    
      [self stop];
    }
    
    #pragma mark - private methods
    - (void)__stop
    {
      self.stopped = YES;
      CFRunLoopStop(CFRunLoopGetCurrent());
      self.innerThread = nil;
    }
    
    - (void)__executeTask:(MJPermenantThreadTask)task
    {
      task();
    }
    
    @end
    
    //調(diào)用
    (void)viewDidLoad {
        [super viewDidLoad];
    
        self.thread = [[MJPermenantThread alloc] init];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        [self.thread executeTask:^{
            NSLog(@"執(zhí)行任務(wù) - %@", [NSThread currentThread]);
        }];
    }
    
    - (IBAction)stop {
        [self.thread stop];
    }
    
    - (void)dealloc
    {
        NSLog(@"%s", __func__);
    }
    
  • RunLoop休眠實(shí)現(xiàn)原理

b2b20b35.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鼻由,隨后出現(xiàn)的幾起案子暇榴,更是在濱河造成了極大的恐慌,老刑警劉巖蕉世,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔼紧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡狠轻,警方通過(guò)查閱死者的電腦和手機(jī)奸例,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)向楼,“玉大人查吊,你說(shuō)我怎么就攤上這事『桑” “怎么了逻卖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)重荠。 經(jīng)常有香客問(wèn)我箭阶,道長(zhǎng),這世上最難降的妖魔是什么戈鲁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任仇参,我火速辦了婚禮,結(jié)果婚禮上婆殿,老公的妹妹穿的比我還像新娘诈乒。我一直安慰自己,他們只是感情好婆芦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布怕磨。 她就那樣靜靜地躺著,像睡著了一般消约。 火紅的嫁衣襯著肌膚如雪肠鲫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,245評(píng)論 1 299
  • 那天或粮,我揣著相機(jī)與錄音导饲,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛渣锦,可吹牛的內(nèi)容都是我干的硝岗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼袋毙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼型檀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起听盖,我...
    開(kāi)封第一講書(shū)人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胀溺,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后媳溺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體月幌,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年悬蔽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扯躺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝎困,死狀恐怖录语,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情禾乘,我是刑警寧澤澎埠,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站始藕,受9級(jí)特大地震影響蒲稳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伍派,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一江耀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诉植,春花似錦祥国、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至灼擂,卻和暖如春壁查,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剔应。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工睡腿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留康谆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓嫉到,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親月洛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子何恶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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