iOS 面向bug開發(fā)之UIWindow出現(xiàn)的“穿透”問題

莫名的穿透

之前版本中出現(xiàn)一個bug配椭,個別的QA同事反映APP中的部分彈框(UIView)彈出來之后就沒法點擊了慎恒,然而這個彈框蒙層背后的界面依然可以正常交互和悦,好像出現(xiàn)了一種“穿透”效果:

穿透.gif

因為復(fù)現(xiàn)的次數(shù)很少赋焕,規(guī)律也沒找到耙厚,聽得我也是一頭霧水强挫,我在想這是什么高級效果,既然不能復(fù)現(xiàn)那只能從源頭颜曾,也就是代碼層面排查了纠拔,因為不管所謂的“高級特效”是不是自己寫出來的,但代碼可是永遠(yuǎn)不會對你說謊的泛豪。
既然是彈出來以后就無法交互了稠诲,我第一反應(yīng)是想看看這幾個UIView是以什么樣的方式彈出的侦鹏,結(jié)果在他們中間都找到了同一段代碼:

- (void)show {
    [[UIApplication sharedApplication].keyWindow addSubview:self];
}

這幾個UIView都是被加到了keyWindow上,當(dāng)時覺得問題也就出在這里了臀叙,因為加在UIWindow上的東西始終是展示在最上面的略水,可能某種情況下導(dǎo)致這些UIView無法被銷毀了,但至于是什么情況我也猜不到劝萤,最后將他們的展示方式都改為了:

    [self.tabBarController.view addSubview:self];

后來就沒有人反饋過類似的問題了渊涝,我的心里在還為自己又解決了一個“靈異”問題而自嗨了短暫的幾秒。

后來無意中被我找到了復(fù)現(xiàn)的規(guī)律床嫌,我發(fā)現(xiàn)只要我在某一個頁面彈出過一次UIAlertView以后跨释,其他只要是add到keyWindow上的彈框,100%會出現(xiàn)這種“穿透的效果”厌处,這也是我上面的demo中有一個按鈕是alert的原因鳖谈,后面會講到。

我跟旁邊的dj_rose同學(xué)提到了這個現(xiàn)象阔涉,他捕捉到了window這個關(guān)鍵字缆娃,告訴我在上個版本我們接入過一個第三方的推送組件,這個組件就是在UIWindow的基礎(chǔ)上做的瑰排,會不會是這個組件產(chǎn)生了影響贯要,這個線索很關(guān)鍵,于是我順著這條線椭住,在這個第三方組件的源碼里找到了蛛絲馬跡:

EBBanerWindow

源碼中自定義了兩個UIWindow用來作為推送消息彈框的載體崇渗,分別是EBBanerWindow類型和EBEmptyWindow類型,至于這兩個UIWindow到底是怎樣分工的這里就不分析了函荣,我也沒細(xì)看显押。圖中我圈出來的地方引起了我的注意,因為他給這兩個自定義的UIWindow設(shè)置的frame都是CGectZero,并且這兩個UIWindowwindowLevel都是UIWindowLevelAlert傻挂,這兩個屬性便讓我和“穿透”這一特性掛上了鉤,我在想項目的彈框是不是被無意間add到這兩個自定義UIWindow中的其中一個上面了挖息,因為這兩個UIWindowframe都是CGectZero金拒,如果父viewframeCGectZero的話那子view肯定是不會響應(yīng)到各種點擊事件的,而且這兩個UIWindow如果出現(xiàn)的話肯定是在層級的最上面套腹,所以會出現(xiàn)一直浮在上面無法銷毀的現(xiàn)象绪抛,但我又一想,不對啊电禀,我都是add在keyWindow上的啊幢码,同時我看到了下面這幾句代碼:

        sharedWindow = [[self alloc] initWithFrame:CGRectZero];
        sharedWindow.windowLevel = UIWindowLevelAlert;
        sharedWindow.layer.masksToBounds = NO;
        UIWindow *originKeyWindow = UIApplication.sharedApplication.keyWindow;
        [sharedWindow makeKeyAndVisible];
        [originKeyWindow makeKeyAndVisible];

他會將原來的keyWindow保存到originKeyWindow這個臨時變量中,最后再向它發(fā)送一遍makeKeyAndVisible消息尖飞,這樣理應(yīng)keyWindow是不會被影響到的症副,作者之所以加上這步操作我猜他也是為了防止keyWindow被替換而帶來的不必要的麻煩店雅,這就奇怪了,也就是說在某種情況下我的keyWindow還是被偷偷替換了贞铣。

keyWindow和UIAlertView

那到底是誰替換了keyWindow呢闹啦?答案我在上面已經(jīng)留下過伏筆了,我復(fù)現(xiàn)的規(guī)律是只要在APP中任何一個頁面彈出來過一次UIAlertView之后辕坝,這個“穿透”就會發(fā)生窍奋,那肯定就是UIAlertView影響到了keyWindow,之前對UIAlertView的了解也就停留在“其實它也是一個Window”的級別上酱畅,至于它為什么會改變keyWindow并沒有研究過琳袄,隨即百度一番(百度雖然有點low但卻能解決現(xiàn)階段我的大部分問題,原因可能是我太low了吧)纺酸,找到了幾篇有用的文章挚歧,將他們的觀點總結(jié)一下:

1.使用UIAlertViewshow時,系統(tǒng)使用了一個臨時的并且層級最高的UIWindow來展現(xiàn)UIAlertView吁峻,所以當(dāng)show彈窗時滑负,keyWindow已經(jīng)被替換為_UIAlertControllerShimPresenterWindow,打印了一下也的確是這樣用含。

UIAlertView替換keyWindow.png

2.當(dāng)UIAlertView消失后keyWindow將會轉(zhuǎn)向另一個UIWindow矮慕,至于這個UIWindow是哪一個,取決于在[UIApplication sharedApplication].windows數(shù)組中的位置的先后啄骇。

windows數(shù)組的排列順序

一般的痴鳄,我們項目中在AppDelegate里都會有這樣幾句代碼:

   self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  [self.window makeKeyAndVisible];

其實寫了這么多遍也沒細(xì)細(xì)分析過這句代碼,它其實就是給APP設(shè)置一個keyWindow缸夹,用來作為之后呈現(xiàn)的一切UIView的載體痪寻,這個window會排在windows數(shù)組里的第一個,一般項目中如果沒有特殊需求去自定義UIWindow的話虽惭,這個windows數(shù)組里的firstObject通常情況下始終會是我們在AppDelegate中創(chuàng)建的這個window(當(dāng)然也不排除有firstObject被系統(tǒng)偷偷替換的情況橡类,這也是我們另一個線上反饋很多次無法復(fù)現(xiàn)的bug的原因):

通常情況下的windows數(shù)組

所以在這種情況下不管我們用keyWidowappdelegate.window還是windows.firstobject去取出來的window都應(yīng)該是同一個芽唇,這也是我們沒有加入這個推送組件之前為什么不會發(fā)生“穿透”問題的原因顾画。

收到推送之后的windows數(shù)組

當(dāng)用戶收到過一次推送后可以看到這個時候windows數(shù)組中已經(jīng)有3個window了,在圖中最上面的UIWindow也是windows數(shù)組的第一個匆笤,后面兩個是EBBannerWindowEBEmptyWindow研侣,那為什么這兩個自定義的windowwindows數(shù)組中會排在UIWindow的后面呢?第一個決定因素是UIWindowLevel炮捧,它代表window的一個級別庶诡,共有三個類型,分別是:
windowLevel的不同級別

這是三個CGFloat類型的常量咆课,通過打印得到這個三個值分別是0.000000 2000.000000 1000.000000末誓。因為EBBannerWindowEBEmptyWindowwindowLevel都是UIWindowLevelAlert的扯俱,而UIWindowwindowLevelUIWindowLevelNormal,由此可以推斷影響windows數(shù)組排列的順序的第一個因素是level低的在前面基显,level高的在后面蘸吓。
那么當(dāng)level相同的時候呢,這就和window的展現(xiàn)方式有關(guān)系了撩幽,首先來看看這個方法库继。

makeKeyAndVisible和setHidden:

討論windows數(shù)組中元素的排列順序之前我們先來看一下蘋果官方文檔對于makeKeyAndVisible這個方法的解釋:

This is a convenience method to show the current window and position it in front of all other windows at the same level or lower. If you only want to show the window, change its hidden property to NO.

意思也就是這個方法可以讓一個window在跟它級別相等或者級別比它低的window中凸現(xiàn)出來,讓這個window中的view展示在其他window的上面窜醉,如果你只是想讓一個window展現(xiàn)出來宪萄,將它的hidden屬性設(shè)為NO就可以了,因為一個UIWindow被創(chuàng)建出來的時候hidden屬性是默認(rèn)為YES的榨惰。
其實在開發(fā)中我們也只需要將那個在AppDelegate中創(chuàng)建的window設(shè)為keyWindow拜英,其他的自定義window需要展現(xiàn),只需要將它們的hidden屬性設(shè)為NO琅催,makeKeyAndVisible雖然是一個“convenience method”居凶,也能讓window展現(xiàn),但是因為它會改變keyWindow藤抡,所以我個人是不建議使用侠碧。

回到windows數(shù)組的排列順序,前面說了缠黍,第一個是取決于windowLevel的大小弄兜,大的排后面,小的排前面瓷式,那么如果數(shù)組中兩個window的level相同替饿,那么誰排在前面,誰排在后面呢贸典?我在網(wǎng)上看到的一個答案說是最后一次調(diào)用makeKeyAndVisible或者setHidden:YES方法的那個window排在后面视卢,因為這兩個方法都會將一個windowhidden屬性改為YES,所以會影響這個window在數(shù)組中的順序瓤漏,最后一次調(diào)用這兩個方法中任何一個的window它也就類似于“最后一個被影響過”腾夯,所以它排在別人后面。

demoOne

我將原來EBBanerWindow.m中兩個自定義window的展現(xiàn)方式都改為了通過setHidden:的方式蔬充,并且代碼的順序改為emptyWindow在前,bannerWindow在后班利,因為這兩個window的level是相同的饥漫,按上面的觀點那bannerWindow肯定會在windows數(shù)組中排在emptyWindow的后面,我們來看看運行結(jié)果:

        UIWindow *originKeyWindow = UIApplication.sharedApplication.keyWindow;
        emptyWindow = [[EBEmptyWindow alloc] initWithFrame:CGRectZero];
        emptyWindow.windowLevel = UIWindowLevelAlert;
        [emptyWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];
        
        bannerWindow = [[self alloc] initWithFrame:CGRectZero];
        bannerWindow.windowLevel = UIWindowLevelAlert;
        [bannerWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];

demoOne

可以發(fā)現(xiàn)windows數(shù)組的順序也跟著變化了罗标,bannerWindow是排在了emptyWindow的后面庸队,這么看來上面的觀點好像有點道理积蜻,別急,我們接著往下看彻消。

demoTwo

為了進一步證明這個觀點我將源碼中給emptyWindowbannerWindow設(shè)置windowLevel的代碼注釋掉竿拆,這樣他們兩的level也變成了默認(rèn)值UIWindowLevelNormal,這樣[originKeyWindow makeKeyAndVisible]是最后調(diào)用的宾尚,那么按照最后調(diào)用排后面的原則是不是windows數(shù)組中UIWindow會跑到數(shù)組的最后一個去呢丙笋,let us see see:

        UIWindow *originKeyWindow = UIApplication.sharedApplication.keyWindow;
        emptyWindow = [[EBEmptyWindow alloc] initWithFrame:CGRectZero];
//        emptyWindow.windowLevel = UIWindowLevelAlert;
        [emptyWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];
        
        bannerWindow = [[self alloc] initWithFrame:CGRectZero];
//        bannerWindow.windowLevel = UIWindowLevelAlert;
        [bannerWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];

demoTwo

可以看到windows數(shù)組并沒有發(fā)生變化,那就證明上面的觀點是不正確的煌贴,那為毛這個UIWindow始終會排在第一個呢御板?突然一個想法冒了出來,決定它崇高地位的只有它創(chuàng)建的時間了牛郑,因為它是一啟動就被創(chuàng)建的怠肋,所以才會排在第一個,由此我假設(shè)決定windows數(shù)組中元素順序的是window們被創(chuàng)建的先后順序淹朋。

demoThree

根據(jù)這個假設(shè)我先改變了emptyWindowbannerWindow的創(chuàng)建順序笙各,將bannerWindow放在了emptyWindow之前,但是他們調(diào)用setHidden:的順序還是不變础芍,bannerWindowemptyWindow之后:

        UIWindow *originKeyWindow = UIApplication.sharedApplication.keyWindow;
        bannerWindow = [[self alloc] initWithFrame:CGRectZero];
        emptyWindow = [[EBEmptyWindow alloc] initWithFrame:CGRectZero];

        emptyWindow.windowLevel = UIWindowLevelAlert;
        [emptyWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];
        
        bannerWindow.windowLevel = UIWindowLevelAlert;
        [bannerWindow setHidden:NO];
        [originKeyWindow makeKeyAndVisible];

這樣如果按先創(chuàng)建的順序bannerWindow應(yīng)該排在emptyWindow前面杈抢,如果按照后調(diào)用setHidden:的順序bannerWindow應(yīng)該排在emptyWindow后面,剛好形成了一個互斥者甲,也就是說決定因素只有一個春感,我們來看看結(jié)果:

demoThree

結(jié)果是bannerWindow排在了windows前面,那就證明在level相同的前提下虏缸,決定數(shù)組中前后順序的是window創(chuàng)建的順序鲫懒,再來個demoFour佐證一下:

demoFour

demoFour

我在AppDelegateself.window之前先創(chuàng)建了這兩個自定義的window,這里有個點要注意下就是創(chuàng)建一個window的同時就要為它設(shè)置一個rootViewController刽辙,不然會crash的窥岩,運行的結(jié)果在左邊。

demoFive

那么windowlevel的高低和創(chuàng)建的順序這兩者哪一個優(yōu)先級更高呢宰缤?我將bannerWindowemptyWindow的優(yōu)先級都改為了UIWindowLevelNormal颂翼,然后將AppDelegate中的self.window的level提高到了UIWindowLevelAlert,但創(chuàng)建順序self.window還是在兩個自定義window的后面慨灭,看一下運行結(jié)果:

    UIViewController *vc = [UIViewController new];
    self.bannerWindow = [[EBBannerWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.bannerWindow.rootViewController = vc;
    [self.bannerWindow  setHidden:NO];
    self.emptyWindow = [[EBEmptyWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.emptyWindow.rootViewController = vc;
    [self.emptyWindow setHidden:NO];
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.windowLevel = UIWindowLevelAlert;

demoFive

可以看出來因為self.window因為level最高朦乏,所以它還是排在了數(shù)組最后一個,而上面兩個window因為level相同氧骤,則根據(jù)創(chuàng)建的先后順序排列呻疹,bannerWindow是先創(chuàng)建的,所以排在前面筹陵,代碼和運行結(jié)果基本可以證實我這個觀點了刽锤。到這里搞清楚了windows數(shù)組的排列順序镊尺,再來看看這個bug產(chǎn)生的原因吧。

穿透的原因

前面說了當(dāng)UIAlertView彈出來后keyWindow被替換成一個臨時的window并思,當(dāng)UIAlertViewdismiss之后這個臨時的window也隨即被銷毀庐氮,那么系統(tǒng)會去尋找一個新的keyWindow人選,這個候選人自然是來自windows數(shù)組宋彼,至于競選規(guī)則就是上面幾個demo總結(jié)出來的先優(yōu)先級后創(chuàng)建順序規(guī)則弄砍,按我們項目中最初的寫法,這個新的keyWindow人選是第三方組件的中的emptyWindow宙暇,因為它優(yōu)先級最高并且是最后一個被創(chuàng)建的输枯,同時這個emptyWindowframeCGRectZero,所以加上去的彈框的所有點擊事件都無法響應(yīng)了占贫,這個地方其實我仍然有點疑惑的是桃熄,為什么這個window的frameCGRectZero添加到這個window上的view依然可以展示出來,只是不能響應(yīng)點擊事件而已型奥,但也正因為它的frameCGRectZero瞳收,彈出來的被添加到emptyWindow上的這些UIView骇塘,并沒有阻斷其他window上控件的事件傳遞沐序,蓋在它們下面的控件依然可以正常點擊瘦癌,這也就是所謂的“穿透”效果眼俊。

解決方案

找到問題的原因之后我們對這個推送第三方組件進行了修改,因為他是一個單例荷辕,沒法在沒有推送的情況下去銷毀這兩個自定義的UIWindow伟阔,只能在彈出和消失的時候加以控制办桨。討論出來的方案是首先將bannerWindowemptyWindowwindowLevel降為了默認(rèn)值UIWindowLevelNormal搭综,因為一個推送彈框只要讓用戶看見即可垢箕,它不存在交互,所以不需要那么高的級別兑巾,而且windowLevel會影響一個windowwindows數(shù)組中的順序条获,而數(shù)組中的順序是影響keyWindow的關(guān)鍵因素。其次將這兩個自定義window的展現(xiàn)方式從makeKeyAndVisible改為setHidden:NO蒋歌,我猜想到原作者寫這句[originKeyWindow makeKeyAndVisible];代碼原因就只是想讓他的自定義window展現(xiàn)出來而不影響到keyWindow帅掘,所以調(diào)用setHidden:方法足矣,改了這兩個地方后堂油,emptyWindow修档、bannerWindowappdelegate.window都是UIWindowLevelNormal級別的,但因為emptyWindow是最后被創(chuàng)建的府框,所以它還是會排在數(shù)組的最后面萍悴,只要UIAlertView彈出并消失后,它依然是keyWindow的最佳人選寓免,所以我們需要在推送框的的hide方法里癣诱,在hide動畫完成之后,手動的調(diào)用一次[appdelegate.window makeKeyAndVisible];方法袜香,將appdelegate.window置回keyWindow撕予,也就是讓emptyWindowkeyWindow的席位交出去,我們雖然不能改變它最佳候選人的位置蜈首,但卻可以讓它自己讓位实抡,這樣就避免了keyWindow的影響,另外我們也把所有addkeyWindow上的這類彈框的展現(xiàn)方式統(tǒng)一做了替換欢策,分別添加到對應(yīng)的VC.view或者tabbarVC.view上吆寨,對于UIAlertView,因為在iOS 9.0以后也被蘋果廢棄了踩寇,我猜蘋果可能也是發(fā)現(xiàn)了這種用UIWindow承載UIAlertView這種方式會對keyWindow產(chǎn)生影響啄清,另一方面UIAlertView的代理方法對代碼邏輯也是一種拆散,所以我們也將項目中使用UIAlertView的地方逐步的替換為更好的UIAlertController俺孙,畢竟UIAlertController是不會去改變keyWindow的辣卒。

面向bug開發(fā)

踩過這一個坑后我想說在使用國內(nèi)非大神寫的第三方組件上時一定要“三思”,其實這個組件確實寫的蠻厲害睛榄,替我們節(jié)省了很多開發(fā)時間荣茫,不過拋開這個組件其他地方不談,我能在初始化window這段源碼中看到作者的防御機制场靴,但感覺并沒有吃透UIWindow的一些用法和原理啡莉,才導(dǎo)致在和UIAlertView一起的時候出現(xiàn)了問題。其實也是因為這個bug才迫使我去一點一點了解UIWindow的東西旨剥,可能bug才是推動技術(shù)進步的第一要素咧欣,以上是我自己總結(jié)出來的一些心得,看到這些觀點的同學(xué)也務(wù)必要“三思”而后行泞边。

參考文章:
EBBannerView只需一行代碼:展示跟 iOS 系統(tǒng)一樣的推送通知橫幅
添加多個UIWindow時该押,使用keyWindow要注意一點
iOS 關(guān)于UIAlertController、UIAlertView彈窗問題
UIWindow的windowLevel屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阵谚,一起剝皮案震驚了整個濱河市蚕礼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梢什,老刑警劉巖奠蹬,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗡午,居然都是意外死亡囤躁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狸演,“玉大人言蛇,你說我怎么就攤上這事∠啵” “怎么了腊尚?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長满哪。 經(jīng)常有香客問我婿斥,道長,這世上最難降的妖魔是什么哨鸭? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任民宿,我火速辦了婚禮,結(jié)果婚禮上像鸡,老公的妹妹穿的比我還像新娘活鹰。我一直安慰自己,他們只是感情好坟桅,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布华望。 她就那樣靜靜地躺著,像睡著了一般仅乓。 火紅的嫁衣襯著肌膚如雪赖舟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天夸楣,我揣著相機與錄音宾抓,去河邊找鬼。 笑死豫喧,一個胖子當(dāng)著我的面吹牛石洗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播紧显,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼讲衫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孵班?” 一聲冷哼從身側(cè)響起涉兽,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎篙程,沒想到半個月后枷畏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡虱饿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年拥诡,在試婚紗的時候發(fā)現(xiàn)自己被綠了触趴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡渴肉,死狀恐怖冗懦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宾娜,我是刑警寧澤批狐,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站前塔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏承冰。R本人自食惡果不足惜华弓,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望困乒。 院中可真熱鬧寂屏,春花似錦、人聲如沸娜搂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽百宇。三九已至考廉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間携御,已是汗流浹背昌粤。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啄刹,地道東北人涮坐。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像誓军,于是被迫代替她去往敵國和親袱讹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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