Runtime 運(yùn)行時之二:方法調(diào)用流程與消息轉(zhuǎn)發(fā)

方法調(diào)用流程

在Objective-C中,消息直到運(yùn)行時才綁定到方法實現(xiàn)上磨总。編譯器會將消息表達(dá)式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用喘批,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù)椭更,如以下所示:


objc_msgSend(receiver, selector)

如果消息中還有其它參數(shù)哪审,則該方法的形式如下所示:


objc_msgSend(receiver, selector, arg1, arg2, ...)

這個函數(shù)完成了動態(tài)綁定的所有事情:

  1. 首先它找到selector對應(yīng)的方法實現(xiàn)。因為同一個方法可能在不同的類中有不同的實現(xiàn)虑瀑,所以我們需要依賴于接收者的類來找到的確切的實現(xiàn)湿滓。
  2. 它調(diào)用方法實現(xiàn),并將接收者對象及方法的所有參數(shù)傳給它舌狗。
  3. 最后叽奥,它將實現(xiàn)返回的值作為它自己的返回值。

消息的關(guān)鍵在于我們前面章節(jié)討論過的結(jié)構(gòu)體objc_class把夸,這個結(jié)構(gòu)體有兩個字段是我們在分發(fā)消息的關(guān)注的:

  1. 指向父類的指針
  2. 一個類的方法分發(fā)表而线,即methodLists

當(dāng)我們創(chuàng)建一個新對象時恋日,先為其分配內(nèi)存膀篮,并初始化其成員變量。其中isa指針也會被初始化岂膳,讓對象可以訪問類及類的繼承體系誓竿。

下圖演示了這樣一個消息的基本框架:

messaging1-1.gif

當(dāng)消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結(jié)構(gòu)體谈截,然后在方法分發(fā)表里面查找方法的selector筷屡。如果沒有找到selector,則通過objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類簸喂,并在父類的分發(fā)表里面查找方法的selector毙死。依此,會一直沿著類的繼承體系到達(dá)NSObject類喻鳄。一旦定位到selector扼倘,函數(shù)會就獲取到了實現(xiàn)的入口點,并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實現(xiàn)除呵。如果最后沒有定位到selector再菊,則會走消息轉(zhuǎn)發(fā)流程,這個我們在后面討論颜曾。

為了加速消息的處理纠拔,運(yùn)行時系統(tǒng)緩存使用過的selector及對應(yīng)的方法的地址。這點我們在前面討論過泛豪,不再重復(fù)稠诲。

獲取方法地址

Runtime中方法的動態(tài)綁定讓我們寫代碼時更具靈活性侦鹏,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對象,或者隨意交換一個方法的實現(xiàn)等臀叙。不過靈活性的提升也帶來了性能上的一些損耗种柑。畢竟我們需要去查找方法的實現(xiàn),而不像函數(shù)調(diào)用來得那么直接匹耕。當(dāng)然,方法的緩存一定程度上解決了這一問題荠雕。

我們上面提到過稳其,如果想要避開這種動態(tài)綁定方式,我們可以獲取方法實現(xiàn)的地址炸卑,然后像調(diào)用函數(shù)一樣來直接調(diào)用它既鞠。特別是當(dāng)我們需要在一個循環(huán)內(nèi)頻繁地調(diào)用一個特定的方法時,通過這種方式可以提高程序的性能盖文。

NSObject類提供了methodForSelector:方法嘱蛋,讓我們可以獲取到方法的指針,然后通過這個指針來調(diào)用實現(xiàn)代碼五续。我們需要將methodForSelector:返回的指針轉(zhuǎn)換為合適的函數(shù)類型洒敏,函數(shù)參數(shù)和返回值都需要匹配上。

我們通過以下代碼來看看methodForSelector:的使用:

@implementation ViewController
- (void)viewDidLoad{
  ///通過runtime獲取IMP
   Method nameLogMethod = class_getInstanceMethod([self class], sel1);
    IMP nameLogImp = method_getImplementation(nameLogMethod);
    nameLogImp();
    
    ///通過NSObject獲取IMP
    ///methodForSelector 如果接受者是一個類對象疙驾,則返回類對象的方法凶伙;如果接受者是一個實例對象,則返回實例對象的方法它碎;
    IMP nameLogImp2 = [self methodForSelector:sel1];
    nameLogImp2();
    ///instanceMethodForSelector 是通過遍歷自身中的函數(shù)列表換句話說函荣,如果調(diào)用的一個元類洋魂,那么返回的是一個類實例的函數(shù)灾票,如果調(diào)用的是一個類谅畅,那么返回的就是實例對象的函數(shù)痢毒。
    IMP nameLogImp3 = [ViewController instanceMethodForSelector:sel1];
    nameLogImp3();
   ///http://www.reibang.com/p/2007e03b6296
}
-(void)nameLog{
    
    NSLog(@"我的名字3--");
   
}

這里需要注意的就是函數(shù)指針的前兩個參數(shù)必須是idSEL邦尊。

當(dāng)然這種方式只適合于在類似于for循環(huán)這種情況下頻繁調(diào)用同一方法炉旷,以提高性能的情況募书。另外折剃,methodForSelector:是由Cocoa運(yùn)行時提供的旋讹;它不是Objective-C語言的特性殖蚕。

消息轉(zhuǎn)發(fā)

當(dāng)一個對象能接收一個消息時,就會走正常的方法調(diào)用流程沉迹。但如果一個對象無法接收指定消息時睦疫,又會發(fā)生什么事呢?默認(rèn)情況下鞭呕,如果是以[object message]的方式調(diào)用方法蛤育,如果object無法響應(yīng)message消息時,編譯器會報錯。但如果是以performSelector的形式來調(diào)用瓦糕,則需要等到運(yùn)行時才能確定object是否能接收message消息底洗。如果不能,則程序崩潰咕娄。

通常亥揖,當(dāng)我們不能確定一個對象是否能接收某個消息時,會先調(diào)用respondsToSelector:來判斷一下圣勒。如下代碼所示:


if ([self respondsToSelector:@selector(runtimeAction)]) {

    [self performSelector:@selector(runtimeAction)];

}

不過费变,我們這邊想討論下不使用respondsToSelector:判斷的情況。這才是我們這一節(jié)的重點圣贸。

當(dāng)一個對象無法接收某一消息時挚歧,就會啟動所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機(jī)制,通過這一機(jī)制吁峻,我們可以告訴對象如何處理未知的消息滑负。默認(rèn)情況下,對象接收到未知的消息用含,會導(dǎo)致程序崩潰矮慕,通過控制臺,我們可以看到以下異常信息:


libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[runtimeViewController runtimeAction]: unrecognized selector sent to instance 0x7fd89460dc30'
terminating with uncaught exception of type NSException

這段異常信息實際上是由NSObject的”doesNotRecognizeSelector“方法拋出的啄骇。不過凡傅,我們可以采取一些措施,讓我們的程序執(zhí)行特定的邏輯肠缔,而避免程序的崩潰夏跷。

消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個步驟:

  1. 動態(tài)方法解析
  2. 備用接收者\(yùn)替換消息接收者(快速轉(zhuǎn)發(fā))
  3. 完整轉(zhuǎn)發(fā)
2145446-c73a54d9b48e0417.png

下面我們詳細(xì)討論一下這三個步驟。

第一步明未、動態(tài)方法解析

對象在接收到未知的消息時槽华,首先會調(diào)用所屬類的類方法

///實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
///類方法
+ (BOOL)resolveClassMethod:(SEL)sel

在這方法中,我們有機(jī)會為該未知消息新增一個”處理方法””趟妥。不過使用該方法的前提是我們已經(jīng)實現(xiàn)了該”處理方法”猫态,只需要在運(yùn)行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了。如下代碼所示:

//第一步披摄、動態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
 //  SEL獲取IMP
//    [self instanceMethodForSelector:@selector(runtimeFuncAction)]
//  SEL轉(zhuǎn)字符串
//    NSStringFromSelector(sel)


   if (sel == @selector(runtimeAction)) {
        ///第一種方式
        ///class_addMethod(self, sel, [self instanceMethodForSelector:@selector(runtimeFuncAction)], "v@:");

        ///第二種方式
        ///v@:代表形參
        class_addMethod(self, sel, (IMP)runtimeFuncActionC, "v@:");

        return YES;
    }

    return [super resolveInstanceMethod:sel];
}

///第一種方式
- (void)runtimeFuncAction {
    
    NSLog(@"runtimeActionq未實現(xiàn)-第一步亲雪、動態(tài)方法解析-runtimeFuncAction-執(zhí)行");
    
}
///第二種方式
void runtimeFuncActionC(id self, SEL _cmd)

{
    
    NSLog(@"runtimeActionq未實現(xiàn)-第一步、動態(tài)方法解析-runtimeFuncActionC-執(zhí)行-%@-%@",self,NSStringFromSelector(_cmd));
    
}

不過這種方案更多的是為了實現(xiàn)@dynamic屬性疚膊,dynamic修飾的屬性义辕,無set,get方法寓盗。

擴(kuò)展-@dynamic
@synthesize 和 @dynamic分別有什么作用

  • @property有兩個對應(yīng)的詞灌砖,一個是@synthesize璧函,一個是@dynamic。如果@synthesize和@dynamic都沒寫基显,那么默認(rèn)的就是@syntheszie var = _var;
  • @synthesize的語義是如果你沒有手動實現(xiàn)setter方法和getter方法蘸吓,那么編譯器會自動為你加上這兩個方法
  • @dynamic告訴編譯器:屬性的setter與getter方法由用戶自己實現(xiàn),不自動生成(當(dāng)然對于readonly的屬性只需提供getter即可)
  • 假如一個屬性被聲明為@dynamic var撩幽,然后你沒有提供@setter方法和@getter方法库继,編譯的時候沒問題,但是當(dāng)程序運(yùn)行到instance.var = someVar窜醉,由于缺setter方法會導(dǎo)致程序崩潰制跟;或者當(dāng)運(yùn)行到 someVar = instance.var時,由于缺getter方法同樣會導(dǎo)致崩潰酱虎。編譯時沒問題,運(yùn)行時才執(zhí)行相應(yīng)的方法擂涛,這就是所謂的動態(tài)綁定

第二步读串、備用接收者\(yùn)替換消息接收者(快速轉(zhuǎn)發(fā))

如果在上一步無法處理消息,則Runtime會繼續(xù)調(diào)以下方法:

///實例方法
- (id)forwardingTargetForSelector:(SEL)aSelector
///類方法
+ (id)forwardingTargetForSelector:(SEL)sel

如果一個對象實現(xiàn)了這個方法撒妈,并返回一個非nil的結(jié)果恢暖,則這個對象會作為消息的新接收者,且消息會被分發(fā)到這個對象狰右。當(dāng)然這個對象不能是self自身杰捂,否則就是出現(xiàn)無限循環(huán)。當(dāng)然棋蚌,如果我們沒有指定相應(yīng)的對象來處理aSelector嫁佳,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果。

使用這個方法通常是在對象內(nèi)部谷暮,可能還有一系列其它對象能處理該消息蒿往,我們便可借這些對象來處理消息并返回,這樣在對象外部看來湿弦,還是由該對象親自處理了這一消息瓤漏。如下代碼所示:

runtimeViewController.m文件
#import "runtimeViewController.h"
///運(yùn)行時頭文件
#import <objc/runtime.h>

#import "notificationViewController.h"
@interface runtimeViewController ()

@end


@implementation runtimeViewController

- (void)viewDidLoad {
 [self performSelector:@selector(runtimeAction)];
}

- (id)forwardingTargetForSelector:(SEL)sel{
    
    if(sel == @selector(runtimeAction)) {
///將消息轉(zhuǎn)發(fā)給notificationViewController來處理
//        return [[notificationViewController alloc]init];
        return NSClassFromString(@"notificationViewController");
    }
    return [super forwardingTargetForSelector:sel];
    
}

notificationViewController.m文件
- (void)runtimeAction {
    
    NSLog(@"runtimeActionq未實現(xiàn)-第二步、備用接收者或第三步颊埃、完整消息轉(zhuǎn)發(fā)-(對象)runtimeAction-執(zhí)行");
    
}

這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上蔬充。但這一步無法對消息進(jìn)行處理,如操作消息的參數(shù)和返回值

完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息班利,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了饥漫。此時會調(diào)用以下方法:


- (void)forwardInvocation:(NSInvocation *)anInvocation

運(yùn)行時系統(tǒng)會在這一步給消息接收者最后一次機(jī)會將消息轉(zhuǎn)發(fā)給其它對象。對象會創(chuàng)建一個表示消息的NSInvocation對象罗标,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中趾浅,包括selector愕提,目標(biāo)(target)和參數(shù)。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象皿哨。

forwardInvocation:方法的實現(xiàn)有兩個任務(wù):

  1. 定位可以響應(yīng)封裝在anInvocation中的消息的對象浅侨。這個對象不需要能處理所有未知消息。
  2. 使用anInvocation作為參數(shù)证膨,將消息發(fā)送到選中的對象如输。anInvocation將會保留調(diào)用結(jié)果,運(yùn)行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者央勒。

不過不见,在這個方法中我們可以實現(xiàn)一些更復(fù)雜的功能,我們可以對消息的內(nèi)容進(jìn)行修改崔步,比如追回一個參數(shù)等稳吮,然后再去觸發(fā)消息。另外井濒,若發(fā)現(xiàn)某個消息不應(yīng)由本類處理灶似,則應(yīng)調(diào)用父類的同名方法,以便繼承體系中的每個類都有機(jī)會處理此調(diào)用請求瑞你。

還有一個很重要的問題酪惭,我們必須重寫以下方法:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機(jī)制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個方法者甲,為給定的selector提供一個合適的方法簽名春感。

完整的示例如下所示:

runtimeViewController.m文件
#import "runtimeViewController.h"
///運(yùn)行時頭文件
#import <objc/runtime.h>

#import "notificationViewController.h"
@interface runtimeViewController ()

@end


@implementation runtimeViewController

- (void)viewDidLoad {
 [self performSelector:@selector(runtimeAction)];
}
///第三步、完整消息轉(zhuǎn)發(fā)
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(runtimeAction))
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)invocation{

    SEL aSelector = [invocation selector];
    ///判斷notificationViewController是否能響應(yīng)aSelector方法
    if ([notificationViewController respondsToSelector:aSelector]){
        ///將 invocation 消息轉(zhuǎn)發(fā)給其它對象 notificationViewController 執(zhí)行
        [invocation invokeWithTarget:[[notificationViewController alloc]init]];
    }else{
        [super forwardInvocation:invocation];
    }

}
notificationViewController.m文件
- (void)runtimeAction {
    
    NSLog(@"runtimeActionq未實現(xiàn)-第二步虏缸、備用接收者或第三步鲫懒、完整消息轉(zhuǎn)發(fā)-(對象)runtimeAction-執(zhí)行");
    
}

NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會轉(zhuǎn)發(fā)任何消息刽辙。這樣刀疙,如果不在以上所述的三個步驟中處理未知消息,則會引發(fā)一個異常扫倡。

從某種意義上來講谦秧,forwardInvocation:就像一個未知消息的分發(fā)中心,將這些未知的消息轉(zhuǎn)發(fā)給其它對象撵溃【卫穑或者也可以像一個運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個接收對象。這取決于具體的實現(xiàn)缘挑。

消息轉(zhuǎn)發(fā)與多重繼承

回過頭來看第二和第三步集歇,通過這兩個方法我們可以允許一個對象與其它對象建立關(guān)系,以處理某些未知消息语淘,而表面上看仍然是該對象在處理消息诲宇。通過這種關(guān)系际歼,我們可以模擬“多重繼承”的某些特性,讓對象可以“繼承”其它對象的特性來處理一些事情姑蓝。不過鹅心,這兩者間有一個重要的區(qū)別:多重繼承將不同的功能集成到一個對象中,它會讓對象變得過大纺荧,涉及的東西過多旭愧;而消息轉(zhuǎn)發(fā)將功能分解到獨立的小的對象中,并通過某種方式將這些對象連接起來宙暇,并做相應(yīng)的消息轉(zhuǎn)發(fā)输枯。

不過消息轉(zhuǎn)發(fā)雖然類似于繼承,但NSObject的一些方法還是能區(qū)分兩者占贫。如respondsToSelector:isKindOfClass:只能用于繼承體系桃熄,而不能用于轉(zhuǎn)發(fā)鏈。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來像是繼承型奥,則可以重寫這些方法瞳收,如以下代碼所示:


- (BOOL)respondsToSelector:(SEL)aSelector

{

    if ( [super respondsToSelector:aSelector])

        return YES;

    else {

        /* Here, test whether the aSelector message can     *

         * be forwarded to another object and whether that  *

         * object can respond to it. Return YES if it can.  */

    }

    return NO;  

}

小結(jié)

在此,我們已經(jīng)了解了Runtime中消息發(fā)送和轉(zhuǎn)發(fā)的基本機(jī)制桩引。這也是Runtime的強(qiáng)大之處,通過它收夸,我們可以為程序增加很多動態(tài)的行為坑匠,雖然我們在實際開發(fā)中很少直接使用這些機(jī)制(如直接調(diào)用objc_msgSend),但了解它們有助于我們更多地去了解底層的實現(xiàn)卧惜。其實在實際的編碼過程中厘灼,我們也可以靈活地使用這些機(jī)制,去實現(xiàn)一些特殊的功能咽瓷,如hook操作等设凹。

為什么Objective-C的消息轉(zhuǎn)發(fā)要設(shè)計三個階段?

第一階段意義在于動態(tài)添加方法實現(xiàn)茅姜,第二階段直接把消息轉(zhuǎn)發(fā)給其他對象闪朱,第三階段是對第二階段的擴(kuò)充,可以實現(xiàn)多次轉(zhuǎn)發(fā)钻洒,轉(zhuǎn)發(fā)給多個對象等奋姿。這也許就是設(shè)計這三個階段的意義。

補(bǔ)充:下面這張圖(來自:一縷殤流化隱半邊冰霜(侵刪))我覺得更符合消息轉(zhuǎn)發(fā)的流程素标,更容易理解称诗。


20200407161830450.png

參考:
http://www.reibang.com/p/8d4f2f1d8482
https://www.jb51.net/article/157079.htm
https://blog.csdn.net/fishmai/article/details/73468952
http://www.reibang.com/p/19c5736c5d9a
http://southpeak.github.io/categories/objectivec/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市头遭,隨后出現(xiàn)的幾起案子寓免,更是在濱河造成了極大的恐慌癣诱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袜香,死亡現(xiàn)場離奇詭異撕予,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)困鸥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門嗅蔬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疾就,你說我怎么就攤上這事澜术。” “怎么了猬腰?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵鸟废,是天一觀的道長。 經(jīng)常有香客問我姑荷,道長盒延,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任鼠冕,我火速辦了婚禮添寺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懈费。我一直安慰自己计露,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布憎乙。 她就那樣靜靜地躺著票罐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泞边。 梳的紋絲不亂的頭發(fā)上该押,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音阵谚,去河邊找鬼蚕礼。 笑死,一個胖子當(dāng)著我的面吹牛梢什,可吹牛的內(nèi)容都是我干的闻牡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼绳矩,長吁一口氣:“原來是場噩夢啊……” “哼罩润!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起翼馆,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤割以,失蹤者是張志新(化名)和其女友劉穎金度,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體严沥,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡猜极,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了消玄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跟伏。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翩瓜,靈堂內(nèi)的尸體忽然破棺而出受扳,到底是詐尸還是另有隱情,我是刑警寧澤兔跌,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布勘高,位于F島的核電站,受9級特大地震影響坟桅,放射性物質(zhì)發(fā)生泄漏华望。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一仅乓、第九天 我趴在偏房一處隱蔽的房頂上張望赖舟。 院中可真熱鬧,春花似錦夸楣、人聲如沸宾抓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洞慎。三九已至痛单,卻和暖如春嘿棘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旭绒。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工鸟妙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挥吵。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓重父,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忽匈。 傳聞我的和親對象是個殘疾皇子房午,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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