IOS開發(fā)需要知道的知識-RunLoops

什么是Run Loops

Run Loops是與線程想關(guān)聯(lián)的基礎(chǔ)部分。一個Run Loop就是事件處理循環(huán),它是用來調(diào)度和協(xié)調(diào)接收到的事件處理。使用Run Loop的目的双吆,就是使得線程有工作需要做時可以忙碌起來,而當(dāng)沒有事可做時会前,又可以使得線程睡眠好乐。
Run Loop管理不都是自動的。我們必須手動設(shè)計線程代碼瓦宜,在合適的時候來啟動Run Loop蔚万,并回應(yīng)到來的事件。Cocoa和Core Foundation都提供了run loop對象來幫助我們配置和管理線程的run loop临庇。我們的應(yīng)用沒有必要顯式地創(chuàng)建這些對象反璃;每個線程,包括應(yīng)用程序的主線程假夺,都有一個與之關(guān)聯(lián)的run loop淮蜈。只有子線程才需要顯式地運行其run loop。App會將自動配置和來運行主線程的run loop的任務(wù)作為應(yīng)用程序啟動處理的一部分已卷。
對于想要更深入地了解Run Loop Objects梧田,閱讀NSRunLoop Class ReferenceCFRunLoop Reference


一個Run Loop的結(jié)構(gòu)

Run Loop就像它的名字一樣,它使得線程進(jìn)入事件循環(huán)裁眯,能對到來的事件啟動事件處理鹉梨。你的代碼中提供了流程控制說一句來實現(xiàn)run loop實實在在的循環(huán)部分,換句話說穿稳,你的代碼提供了while或者for循環(huán)來驅(qū)動run loop存皂。在你的循環(huán)中,你使用run loop對象在事件到達(dá)時逢艘,運行事件處理的代碼并調(diào)起已安裝的處理程序旦袋。
Run Loop接收來自兩種不同類型的源(sources)的事件
輸入源:異步傳遞事件,通常是來自不同的線程或不同的應(yīng)用的消息它改。輸入源異步傳遞事件到對應(yīng)的處理程序和在線程關(guān)聯(lián)的NSRunLoop對象調(diào)起runUntilDate:方法來退出事件處理猜憎。
Timer源:同步地傳遞事件,發(fā)生在每個定時器調(diào)用或周期性地調(diào)用搔课。Timer源傳遞事件到他們的處理程序,但是不會調(diào)用run loop來退出處理截亦。

這兩種源在事件到達(dá)時都使用應(yīng)用程序特定的處理程序來處理事件爬泥。
如下圖所示,展示了run loop和不同的源的概述結(jié)構(gòu)崩瓤。


image

除了處理輸入源之外袍啡,run loops還發(fā)出關(guān)于run loop行為的通知。我們可以注冊成為run loop的觀察者却桶,就可以接收這些通知和使用它在線程上做一些額外處理境输。我們可以使用Core Foundation在對應(yīng)的線程上注冊成為run loop的觀察者。


Run Loop Modes

Run Loop模式是一個監(jiān)視輸入源和定時器的集合和注冊成為run loop的觀察者的集合颖系。每次要運行run loop嗅剖,都需要顯示或隱式地指定某種運行的mode。只有與這種指定的mode關(guān)聯(lián)的源才會被監(jiān)視和允許傳遞他們的事件嘁扼,同樣地信粮,只有與這種模式關(guān)聯(lián)的觀察者都會收到run loop行為變化的通知。與其它模式想關(guān)聯(lián)的源趁啸,直到隨后在合適的模式通過循環(huán)后强缘,都會接收到新的事件(比如,將timer加入run loop default模式下不傅,當(dāng)滾動時旅掂,timer不會收到回調(diào),直到停止?jié)L動回到default模式下)访娶。
在我們的代碼中商虐,我們通過名稱來唯一標(biāo)識mode。在Cocoa和Core Foundation中都定義了default模式和幾個常用的模式,都是通過字符串名稱來指定称龙。我們也可以自定義模式留拾,但是我們需要手動添加至少一個input source/timers/observers。
我們可以通過使用mode來過濾掉我們不希望接收到來自不想要的通過run loop的源鲫尊。大部分情況下痴柔,我們都是使用系統(tǒng)定義的default模式。對于子線程疫向,我們可以使用自定義模式在關(guān)鍵性操作時阻止低優(yōu)先級的源傳遞事件咳蔚。
注意:Modes是通過事件源來區(qū)分,而不是事件類型來區(qū)分搔驼。比如說谈火,我們不能使用mode來匹配只有mouse-down事件或者只有鍵盤事件。我們可以使用modes來監(jiān)聽不同系統(tǒng)的端口舌涨,臨時掛起定時器糯耍,甚至改變正在被監(jiān)視的sources和run loop觀察者。

表格.png


Input Sources

輸入源異步傳遞事件到你的線程囊嘉。事件的源由輸入源的類型來決定温技,也就是兩種源中的其中一種:
Port-based:基于端口號的輸入源監(jiān)聽?wèi)?yīng)用程序的Mach端口。
Custom Input Sources:自定義輸入源監(jiān)聽自定義的事件源扭粱。

系統(tǒng)通常實現(xiàn)了這兩種輸入源舵鳞。唯一的不同點是它們是如何被發(fā)出信號的。port-based源是由內(nèi)核(kernel)自動發(fā)出信號琢蛤,而custom sources必須手動從其它線程發(fā)出信號蜓堕。
當(dāng)我們創(chuàng)建輸入源時,可以指定mode博其。Modes會影響任何時刻被監(jiān)視的輸入源套才。大部分情況下,我們都讓run loop在default mode下運行贺奠,但是也可以指定自定義的mode霜旧。如果一個輸入源不是當(dāng)前所監(jiān)視的model,它所產(chǎn)生的任何事件都會被保留直接進(jìn)入正常的mode儡率。


Port-Based Sources

Cocoa和Core Foundation提供了內(nèi)建支持挂据,可以使用與port相關(guān)的對象和函數(shù)來創(chuàng)建基于端口的輸入源。舉個例子儿普,在Cocoa中永遠(yuǎn)不需要手動創(chuàng)建輸入源崎逃。我們只需要簡單地創(chuàng)建一個port對象和使用NSPort的方法。port對象為我們處理所需要的輸入源的創(chuàng)建和配置眉孩。
在Core Foundation中个绍,我們必須手動創(chuàng)建port和source痴颊。在這兩種情況下币喧,我們可以使用與port opaque type關(guān)聯(lián)的函數(shù)(CFMessagePortRef, or CFSocketRef) 來創(chuàng)建合適的對象判没。


Custom Input Sources

在Core Foundation中啊送,要創(chuàng)建自定義輸入源,我們必須使用與CFRunLoopSourceRef關(guān)聯(lián)的函數(shù)广恢。我們配置自定義輸入源可以使用幾個回調(diào)函數(shù)凯旋。Core Foundation會在不同點回調(diào)這些函數(shù)來配置source,處理任何到達(dá)的事件和銷毀已從run loop移除的source钉迷。
除了定義在事件到達(dá)時自定義源的行為之外至非,我們也必須定義事件傳遞機制。這部分源運行在單獨的線程糠聪,負(fù)責(zé)提供輸入源的數(shù)據(jù)荒椭,當(dāng)數(shù)據(jù)準(zhǔn)備好可以處理時,signaling(通知相關(guān)線程)這個消息舰蟆。事件傳遞機制是我們自己來決定趣惠,但是不需要過于復(fù)雜。


Cocoa Perform Selector Source

除了基于端口的源之外身害,Cocoa還定義了自定義輸入源允許我們在任意線程上執(zhí)行selector信卡。就像port-based源一樣,執(zhí)行selector請求會在目標(biāo)線程上序列化题造,以減少在同一個線程中出現(xiàn)多個方法同步執(zhí)行的問題。與port-based源不同的是猾瘸,執(zhí)行selector源在執(zhí)行完畢后會自動將自己從run loop中移除界赔。
當(dāng)執(zhí)行在其它線程執(zhí)行selector時,目標(biāo)線程必須要有運行的run loop牵触。當(dāng)我們創(chuàng)建線程時淮悼,這意味著直到啟動了run loop都會顯式地執(zhí)行selector代碼。
Run Loop每次經(jīng)過一個循環(huán)揽思,就會處理隊列中所有的selector袜腥,而不僅僅是處理一個。


方法和說明钉汗。

Timer Sources

Timer源在未來設(shè)定的時間會同步地傳遞事件到你的線程羹令。Timers是線程通知自己去做一些事情的一種方式。比如說损痰,搜索框可以使用定時器來初始化在一定時間就自動搜索福侈,以便提供更多地聯(lián)想詞給用戶。
盡管它發(fā)送基于時間的通知卢未,但定時器并不是一種實時的機制肪凛。像輸入源一樣堰汉,定時器只有與run loop的mode一樣才會發(fā)送通知。如果timer在run loop中并不是所被監(jiān)視的mode伟墙,它不會觸發(fā)定時器翘鸭,直到run loop的mode與timer所支持的mode一樣。
同樣地戳葵,如果run loop正在處理中就乓,timer已經(jīng)fire了,這時候會被中斷譬淳,直到下一次通過run loop才會調(diào)志處理程序档址。如果run loop已經(jīng)不再運行了,則timer永遠(yuǎn)不會再fire邻梆。
我們可以配置timer只產(chǎn)生事件一次或者重復(fù)產(chǎn)生守伸。重復(fù)的timer會自動根據(jù)調(diào)度的firing time自動調(diào)度,而不是真實的firing time浦妄。比如說尼摹,如果一個timer在特定的時間調(diào)度,然后每5秒重復(fù)一次剂娄。如果firing time被延遲導(dǎo)致缺少一或多次調(diào)用蠢涝,那么timer在缺失的周期中只會調(diào)用一次。


Run Loop Observers

與sources在適當(dāng)時機異步或同步發(fā)出事件不同阅懦,observers在run loop本身執(zhí)行期間和二,會在特定的地方發(fā)出。你可能需要到run loop observers去準(zhǔn)備線程處理特定的事件或者在進(jìn)入睡眠之前耳胎。我們可以通過以下事件來關(guān)聯(lián)run loop observers:

進(jìn)入run loop
run loop將要處理timer
run loop將要處理輸入源
run loop將要進(jìn)入睡眠
run loop被喚醒惯吕,但是還沒有處理事件
退出run loop

我們可以通過Core Foundation來添加run loop observers。要創(chuàng)建run loop observer,可以通過CFRunLoopObserverRef來創(chuàng)建新的實例怕午。這個類型會跟蹤你所定義的回調(diào)函數(shù)和所感興趣的活動废登。
與timers類型,run-loop observers可以使用一次或者重復(fù)多次郁惜。一次性的observer會在fire之后自動從run loop移除堡距,而重復(fù)性的observer會繼續(xù)持有。


The Run Loop Sequence Of Events

本小節(jié)講的是RunLoop事件順序兆蕉。每次運行它羽戒,你的線程的run loop處理待處理的事件和給所有attached observers發(fā)出通知。處理的順序如下:

1.通知observers run loop已經(jīng)進(jìn)入
2.通知observers timers準(zhǔn)備要fire
3.通知observers有不是基于port-based的輸入源即將要fire
4.fire任何已經(jīng)準(zhǔn)備好的non-port-based輸入源
5.如果port-based輸入源準(zhǔn)備好且等待fire虎韵,則立即處理這個事件半醉。然后進(jìn)入步驟9
6.通知observers線程即將進(jìn)入睡眠
7.讓線程進(jìn)入睡眠,直到以下任何一種事件到達(dá):
        * port-based輸入源有事件到達(dá)
        * timer fire
        * run loop超時
        * run loop被顯式喚醒
8.通知observers線程被喚醒
9.處理待處理的事件:
       * 如果用戶定義的timer fired了劝术,處理timer事件并重新啟動循環(huán)缩多。進(jìn)入步驟2
       * 如果輸入源fired了呆奕,則傳遞事件
       * 如果run loop被顯式喚醒,但是又未超時衬吆,則重啟循環(huán)梁钾,進(jìn)入步驟2

10.通知observers run loop退出

由于observer對timer和輸入源的通知會在事件真正發(fā)生之前被傳遞,這樣就產(chǎn)生了間隙逊抡。如果這個間隙是很關(guān)鍵的姆泻,那么我們可以通過使用sleep和awake-from-sleep通知來幫助我們糾正這個時間間隔問題。


When Would You Use A Run Loop?

什么時候應(yīng)該使用run loop呢冒嫡?
只有當(dāng)我們需要創(chuàng)建子線程的時候拇勃,才會需要到顯示地運行run loop。應(yīng)用程序的主線程的run loop是應(yīng)用啟動的基礎(chǔ)任務(wù)孝凌,在啟動時就會自動啟動run loop方咆。所以我們不需要手動啟動主線程的run loop。

對于子線程蟀架,我們需要確定線程是否需要run loop瓣赂,如果需要,則配置它并啟動它片拍。我們并不問題需要啟動run loop的煌集。比如說,如果我們開一個子線程去執(zhí)行一些長時間的和預(yù)先決定的任務(wù)捌省,我們可能不需要啟動run loop苫纤。Run loop是用于那么需要在線程中有更多地交互的場景。比如說纲缓,我們會在下面的任何一種場景中需要開啟run loop:

使用端口源或者自定義輸入源與其它線程通信
在線程中使用定時器
使用Cocoa中的任何performSelector…方法
保持線程來執(zhí)行周期性的任務(wù)

Using Run Loop Objects

Run Loop對象給添加輸入源方面、定時器和觀察者到run loop提供了主接口。每個線程都有一個單獨的run loop與之關(guān)聯(lián)(對于子線程色徘,若沒有調(diào)用過任何獲取run loop的方法是不會有run loop的,只有調(diào)用過操禀,才會創(chuàng)建或者直接使用)褂策。
在Cocoa中,通過NSRunLoop來創(chuàng)建實例颓屑,在low-level應(yīng)用中斤寂,可以使用CFRunLoopRef類型,它是指針揪惦。


Getting A Run Loop Object

通過以下兩種方式來獲取run loop對象:
在Cocoa中遍搞,使用[NSRunLoop currentRunLoop]獲取
使用CFRunLoopGetCurrent()函數(shù)獲取


配置RunLoop

在子線程運行run loop之前,你必須至少添加一種輸入源或者定時器器腋。如果run loop沒有任何的源需要監(jiān)視溪猿,它就會立刻退出钩杰。
除了添加sources之外,你還可以添加觀察者來檢測runloop不同的執(zhí)行狀態(tài)诊县。要添加觀察者讲弄,可以使用CFRunLoopObserverRef指針類型和使用CFRunLoopAddObserver函數(shù)來添加到run loop中。我們只能通過Core Foundation來創(chuàng)建run loop觀察者依痊,即使是Cocoa應(yīng)用避除。
下面這段代碼展示主線程如何添加觀察者到run loop以及如何創(chuàng)建run loop觀察者:

- (void)threadMain {
    // 應(yīng)用程序使用垃圾收集,因此不需要autorelease池。
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // 創(chuàng)建一個運行循環(huán)觀察者并將它附加到運行循環(huán)胸嘁。
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer) {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    //創(chuàng)建和安排計時器瓶摆。
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do {
        // 運行運行循環(huán)10次讓計時器。
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    } while (loopCount);
}

在為長時間存在的線程配置run-loop時性宏,最好添加至少一個輸入源來接收事件群井。盡管我們可以只用timer源,但是一旦timer調(diào)用后衔沼,經(jīng)常會被invalidate蝌借,這會導(dǎo)致run loop退出。


Starting the Run Loop

只有子線程才有可能需要啟動run loop指蚁。Run loop必須至少有一種輸入源或者timer源來監(jiān)視菩佑。如果沒有任何源,則run loop會退出凝化。
下面的幾種方式可以啟動run loop:
無條件地:無條件進(jìn)入run loop是最簡單的方式稍坯,但也是最不希望這么做的,因為這樣會導(dǎo)致run loop會進(jìn)入永久地循環(huán)搓劫∏朴矗可以添加、刪除輸入源和timer源枪向,但是只能通過kill掉run loop才能停止勤揩。而且還不能使用自定義mode。
限時:與無條件運行run loop不同秘蛔,最好是給run loop添加一個超時時間陨亡。
在特定的mode:除了添加超時時間,還可以指定mode深员。

下面是運行run loop的一段代碼:

- (void)skeletonThreadMain {
   //建立一個autorelease如果不使用垃圾收集池负蠕。
   BOOL done = NO;

   //添加你的來源或計時器運行循環(huán),做其他任何設(shè)置。

   do {/ /啟動運行循環(huán)但每個源處理后返回倦畅。
       SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);

     / /如果源明確停止運行循環(huán),或如果沒有源或計時器,然后退出遮糖。
       if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
           done = YES;

/ /檢查任何其他退出條件和設(shè)置完成所需的變量。    } while (!done);

   / /清理代碼叠赐。一定要釋放任何生成自動分配池欲账。
}

Exiting the Run Loop

有兩種方法使run loop在處理事件之前屡江,退出run loop:

給run loop設(shè)定超時時間
告訴run loop要stop

設(shè)定超時時間是比較推薦的。我們可以通過CFRunLoopStop函數(shù)來停止run loop敬惦。


Thread Safety and Run Loop Objects

Core Foundation中的Run Loop API是線程安全的(以CF開頭的API)盼理,而Cocoa中的NSRunLoop不是線程安全的。

Configuring Run Loop Sources

下面是展示如何配置不同類型的輸入源俄删。

Defining a Custom Input Source

創(chuàng)建自定義輸入源涉及到以下部分:
想要處理的輸入源的信息
讓感興趣的客戶端知道如何聯(lián)系輸入源的調(diào)度程序
執(zhí)行任何客戶端發(fā)送的請求處理程序
使輸入源失效的取消程序


Timer Source

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
/ /創(chuàng)建和安排第一個定時器宏怔。
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];       
 

或者使用Core Foundation:

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

最后
本篇文章主要是官方文檔的部分翻譯版本,不過有很多無關(guān)的都省略了畴椰,而且有都轉(zhuǎn)換成筆者的語言來表達(dá)出來臊诊,如果讀不懂,最好還是去看官方文檔吧斜脂。畢竟抓艳,英文與中文翻譯不管怎么翻譯都存在很大的問題。
疑問
官方文檔中提到帚戳,每個線程都有一個run loop與之關(guān)聯(lián)玷或。但是實質(zhì)上子線程在沒有訪問過run loop時,是不存在的片任。當(dāng)訪問時偏友,若不存在則創(chuàng)建run loop并放到全局?jǐn)?shù)組中。筆者閱讀過CFRunLoop.c的源代碼对供,所以當(dāng)有面試官問到這個問題時位他,一定要注意哦!

本文轉(zhuǎn)發(fā)于標(biāo)哥的博客产场,收藏用鹅髓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市京景,隨后出現(xiàn)的幾起案子窿冯,更是在濱河造成了極大的恐慌,老刑警劉巖确徙,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醒串,死亡現(xiàn)場離奇詭異,居然都是意外死亡米愿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門鼻吮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來育苟,“玉大人,你說我怎么就攤上這事椎木∥グ兀” “怎么了博烂?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長漱竖。 經(jīng)常有香客問我禽篱,道長,這世上最難降的妖魔是什么馍惹? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任躺率,我火速辦了婚禮,結(jié)果婚禮上万矾,老公的妹妹穿的比我還像新娘悼吱。我一直安慰自己,他們只是感情好良狈,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布后添。 她就那樣靜靜地躺著,像睡著了一般薪丁。 火紅的嫁衣襯著肌膚如雪遇西。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天严嗜,我揣著相機與錄音粱檀,去河邊找鬼。 笑死阻问,一個胖子當(dāng)著我的面吹牛梧税,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播称近,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼第队,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刨秆?” 一聲冷哼從身側(cè)響起凳谦,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衡未,沒想到半個月后尸执,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡缓醋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年如失,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片送粱。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡褪贵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脆丁,我是刑警寧澤世舰,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站槽卫,受9級特大地震影響跟压,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歼培,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一震蒋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丐怯,春花似錦喷好、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至效览,卻和暖如春无切,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丐枉。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工哆键, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘦锹。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓籍嘹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弯院。 傳聞我的和親對象是個殘疾皇子辱士,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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