OC底層原理十八:類的加載(中) SEL & 分類的加載

OC底層原理 學習大綱

上一節(jié)伐憾,我們了解了map_images的整體結(jié)構(gòu) & 非懶加載類辅斟,了解了APP啟動時,所有都已記錄哈希表中(僅類名字地址)生真。

  • 實現(xiàn)類+load方法非懶加載類,會在啟動時奕筐,實現(xiàn)類的加載,從macho中讀取原始數(shù)據(jù)存放到rw
  • 懶加載類則是在被第一次調(diào)用時衔蹲,通過消息機制觸發(fā)類的實現(xiàn)坚踩。
    兩種類的加載方式,最終都是調(diào)用realizeClassWithoutSwift完成實現(xiàn)毡惜。

上節(jié)回顧:

上節(jié)回顧

我們上一節(jié)留下了2個問題:rwe何時加載拓轻?分類如何加載?

  • 現(xiàn)在不急著回答,本節(jié)結(jié)束后经伙,我相信你就完全懂了扶叉。

本節(jié)盡可能講得詳細一些:

  1. sel注冊
  2. 分類的本質(zhì)
  3. 分類的數(shù)據(jù)加載
  4. attachCategories詳解
  5. attachCategories的調(diào)用

準備工作:

1. sel注冊

我們在前面學習msgSend消息機制時,慢速查找階段中,在類的函數(shù)列表查找方法時枣氧,是使用二分查找(??流程圖)溢十。

Q: 二分查找必須是有序的,那排序依據(jù)是什么达吞,如何排序茶宵?

  • 上一節(jié)我們分析map_images流程時,在第2步 修復預編譯階段的SEL的混亂問題時宗挥,就需要將SEL插入到nameSelectors哈希表中乌庶。
image.png
  • 其中_getObjc2SelectorRefsmacho__objc_selrefs,存儲的內(nèi)容是SEL:
    image.png
  • 遍歷從macho__objc_selrefs讀取SEL契耿,其中的sels包含的是帶地址sel(后面證明)瞒大。
  • 循環(huán)注冊sel,檢查sel地址搪桂,如果不同透敌,就重新賦值sel地址

進入sel_registerNameNoLock:

image.png
  • 進入__sel_registerName:
image.png
  • 一般是可以通過name搜索到result,直接返回result踢械。
  • 但如果特殊情況name搜索不到酗电,就重新創(chuàng)建,再返回sel内列。

我們進入search_builtins來了解查詢路徑:

image.png
  • 發(fā)現(xiàn)_dyld_get_objc_selectorextern申明在dyld中:
// Called only by objc to see if dyld has uniqued this selector.
// Returns the value if dyld has uniqued it, or nullptr if it has not.
// Note, this function must be called after _dyld_objc_notify_register.
//
// Exists in Mac OS X 10.15 and later
// Exists in iOS 13.0 and later
extern const char* _dyld_get_objc_selector(const char* selName);
  • 打開dyld源碼撵术,搜索_dyld_get_objc_selector(const
image.png
  • 進入getObjCSelector
image.png
  • 發(fā)現(xiàn)是調(diào)用getString方法在讀取內(nèi)容,所以我們反向搜索getString(const话瞧,檢查函數(shù)的實現(xiàn):
image.png
  • 通過這里嫩与,我們就明確知道了:

sel雖然是函數(shù)名(字符串),但同時它是有地址值的交排。

拓展:

  1. 函數(shù)地址完全隨機划滋,是由它所在的段基礎地址偏移值確定的。程序每次運行埃篓,函數(shù)地址可能變化处坪。
  2. 判斷兩個函數(shù)是否相等,是通過地址值進行判斷
    兩個不同類相同名稱函數(shù)架专,但函數(shù)地址不同同窘,是兩個獨立的函數(shù)
  3. 函數(shù)列表排序胶征,是依據(jù)SEL地址進行排序塞椎。所以排序后,可使用二分查找睛低。

2.分類的本質(zhì)

  • main.m文件加入測試代碼
// 本類
@interface HTPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)func1;
- (void)func3;
- (void)func2;

+ (void)classFunc;

@end

@implementation HTPerson

+ (void)load { NSLog(@"%s",__func__); };

- (void)func1 { NSLog(@"%s",__func__); };
- (void)func3 { NSLog(@"%s",__func__); };
- (void)func2 { NSLog(@"%s",__func__); };

+ (void)classFunc { NSLog(@"%s",__func__); };

@end

// 分類 CatA
@interface HTPerson (CatA)

@property (nonatomic, copy) NSString *catA_name;
@property (nonatomic, assign) int catA_age;

- (void)func1;
- (void)func3;
- (void)func2;

+ (void)classFunc;

@end

@implementation HTPerson (CatA)

+ (void)load { NSLog(@"%s",__func__); };

- (void)func1 { NSLog(@"%s",__func__); };
- (void)func3 { NSLog(@"%s",__func__); };
- (void)func2 { NSLog(@"%s",__func__); };

+ (void)classFunc { NSLog(@"%s",__func__); };

@end

int main(int argc, const char * argv[]) {
    return 0;
}

檢查格式的方式:1. clang 2. 官方幫助文檔

  • 2.1 clang
    cdmain.m所在文件夾,輸入clang -rewrite-objc main.m -o main.cpp,打開main.cpp文件钱雷,搜索分類_CatA

  • 分類的實例方法類方法

    image.png

  • 分類的屬性:

    image.png

  • 分類的結(jié)構(gòu)

    image.png

  • 我們搜索struct _category_t骂铁,可看到分類的完整格式
    image.png

發(fā)現(xiàn)編譯期HTPerson(CatA)nameHTPersoncls也是HTPerosn類

  • 分類的實現(xiàn):
    image.png

本類屬性分類屬性的區(qū)別:

  • 本類屬性:在clang編譯環(huán)節(jié)罩抗,會自動生成并實現(xiàn)對應的set和get方法

  • 分類屬性:會存在set拉庵、get方法,但是沒有實現(xiàn)需要runtime設置關(guān)聯(lián)屬性)套蒂。

    易混淆點: 分類屬性存在set钞支、get方法,但沒有實現(xiàn)操刀。
    檢驗方式: 使用person對象可以快捷訪問到catA_age烁挟,并可以賦值。但是程序運行時crash骨坑。 這是因為方法存在撼嗓,但找不到對應的imp實現(xiàn)

    image.png

    Q: 1. 分類屬性為何存在set欢唾、get方法且警? 2. 如何讓它不crash(關(guān)聯(lián)屬性的動態(tài)實現(xiàn))

  • 第1個問題在本節(jié)后續(xù)探索中,會得到很清晰的答案礁遣。 第2個問題斑芜,我們下一節(jié)專門講解關(guān)聯(lián)屬性

  • 2.2 官方幫助文檔

打開官方文檔 (快捷鍵:shift + command + 0)祟霍,搜索Categor:

image.png
  • 切換語言為Objective-C:

    image.png

  • 發(fā)現(xiàn)類型是objc_category押搪,在objc4源碼中搜索:

image.png
  • ?? 格式不一樣?name呢浅碾?cls呢大州?
  • ?? 注意看后面的聲明:OBJC2_UNAVAILABLE, objc2不可用垂谢。文檔已過期的厦画。這個時候,我們要以真實運行的代碼為準滥朱。

了解了分類數(shù)據(jù)格式根暑,那分類的數(shù)據(jù)如何加到HTPerson的呢?

3. 分類的加載

如何研究呢徙邻?

  • 已知的信息出發(fā)排嫌,先找一條抵達目的地的路徑,找到核心方法缰犁,再反向搜索核心方法被調(diào)用的地方淳地,進行全面推理怖糊。

我們上一節(jié)分析_read_images結(jié)構(gòu)時,第9步 實現(xiàn)非懶加載類->methodizeClass內(nèi)部有對分類的處理颇象。

  • methodizeClass中加入測試代碼:
// >>>> 測試代碼
    const char *mangledName = cls->mangledName();
    const char * HTPersonName = "HTPerson";
    if (strcmp(HTPersonName, mangledName) == 0 ) {
        if (!isMeta) {
            printf("%s - 精準定位: %s\n", __func__, mangledName);
        }
    }
    // <<<< 測試代碼
  • printf打印處加入斷點伍伤,運行程序
image.png
  • 發(fā)現(xiàn)進入了HTPerosn類,查看ro信息遣钳,發(fā)現(xiàn)其中baseMethods只有8個扰魂,分別打印查看,都是HTPerosn本類實例函數(shù)蕴茴。 從信息欄可以看rwe此時為Null

ro的讀热捌馈:

image.png
  • 單步往下運行,發(fā)現(xiàn)最終會到達attachToClass處:
image.png

methodizeClass的內(nèi)容是:

  • 讀取函數(shù)(已排序)存到list -> 讀取屬性存到proplist -> 讀取協(xié)議存到protolist -> 分類添加到類attachToClass

有個細節(jié)倦淀,我們發(fā)現(xiàn)initialize在這里被添加到根元類函數(shù)列表了蒋畜。根元類擁有initialize方法,所有繼承NSObject的類晃听,都將擁有initialize方法百侧。

我們知道+load方法會將懶加載類轉(zhuǎn)變?yōu)?code>非懶加載類,在app啟動前完成了所有非懶加載類加載能扒。但是app啟動環(huán)節(jié)加載過多內(nèi)容佣渴,會影響app的啟動時長

  • Q:有些準備必須在類初始化之前就完成初斑,如果不寫+load方法內(nèi)辛润,怎么做到提前準備呢?
  • A:寫在initialize內(nèi)见秤,因為每個類都繼承自NSObject砂竖,所以都自帶了initialize函數(shù),而initialize函數(shù)是在類第一次發(fā)送消息時鹃答,就觸發(fā)乎澄。 所以可以做到提前準備
  • 進入attachToClass测摔,加入測試代碼:
image.png

看到了關(guān)鍵的attachCategories函數(shù):綁定分類置济。

  • 如果是元類,需要分別綁定對象類方法锋八。否則浙于,只需要綁定對象方法。

注意挟纱,此時測試代碼中HTPersonHTPerson(CatA)都必須實現(xiàn)+load方法羞酗,才會進入attachCategories代碼區(qū)域) 具體原因,后面第5部分 本類與分類的+load區(qū)別 會詳細講解紊服。

下面檀轨,我們詳細分析一下attachCategories

4. attachCategories詳解

進入attachCategories胸竞,加入定位測試代碼

image.png

開辟了64個空間大小的mlistsproplists裤园、protolists容器撤师,分別用于存儲函數(shù)剂府、屬性拧揽、協(xié)議

image.png

attachCategories流程:

  • 首先腺占,開辟空間淤袜,對rwe進行初始化
  • 然后衰伯,遍歷所有的分類
    entry記錄當前分類铡羡,entry.cat是category_t結(jié)構(gòu),存儲了分類所有數(shù)據(jù)意鲸。
    從分類中讀取函數(shù)烦周、屬性協(xié)議信息怎顾,存放指定容器內(nèi)读慎。
  • 最后,將容器內(nèi)數(shù)據(jù)槐雾,分別添加rwe指定屬性中夭委。

此處分為3小部分講解:

  1. rwe的初始化
  2. 數(shù)據(jù)讀取
  3. prepareMethodLists函數(shù)排序
  4. attachLists 綁定數(shù)據(jù)

4.1 rwe的初始化

哈哈哈 ?? 走過千山萬水,終于找到你募强,我的rwe

  • 進入 extAllocIfNeeded
image.png
  • 進入extAlloc:
image.png

此時株灸,rwe才完成了初始化工作。各項屬性完備擎值。(關(guān)于attachLists賦值操作慌烧,在4.3小部分進行講解)

關(guān)于rwe何時加載的問題:
我們現(xiàn)在知道分類加載會進行rwe初始化加載數(shù)據(jù)。那還有其他地方觸發(fā)rwe的加載嗎鸠儿?

  • rwe的加載屹蚊,是執(zhí)行了extAlloc方法,所以我們反向搜索捆交,查看誰調(diào)用extAlloc方法:
image.png

只有extAllocIfNeededdeepCopy調(diào)用了淑翼。

  • deepCopy深拷貝: 搜索deepCopy(,發(fā)現(xiàn)只被objc_duplicateClass調(diào)用品追,而是objc_duplicateClass開放使用的API接口玄括,并沒自動調(diào)用的地方。 所以此處不做考慮肉瓦。

  • extAllocIfNeeded: 搜索extAllocIfNeeded(遭京,發(fā)現(xiàn)有以下7處調(diào)用了它:

    image.png

  • 發(fā)現(xiàn)都是動態(tài)添加(函數(shù)胃惜、屬性、協(xié)議哪雕、分類等)時船殉,才會創(chuàng)建rwe

還記得上面ro的讀取嗎斯嚎?

  • rwe存在時:表示這個類有數(shù)據(jù)被修改了利虫,所以需要從rwe返回數(shù)據(jù)
  • 而如果rwe不存在堡僻,表明這個類的數(shù)據(jù)沒有動態(tài)修改過糠惫,所以可以直接從macho拷貝一份ro返回即可。

附上WWDC2020視頻Advancements in the Objective-C runtime钉疫,回顧官方對于rwe的解釋硼讽,會理解得更深刻。

4.2 數(shù)據(jù)讀取和prepareMethodLists函數(shù)排序

初始化rwe后牲阁,我們讀取分類數(shù)據(jù)

image.png

  • 查看entry.cat結(jié)構(gòu):
image.png
  • 查看category_t結(jié)構(gòu)固阁,發(fā)現(xiàn)存儲了分類所有數(shù)據(jù)。
    image.png

所以分類的數(shù)據(jù)都是從entry.cat進行讀取城菊。

  • 我們在上面定位測試代碼打印處加上斷點备燃,運行代碼,到達斷點后役电,往下進入循環(huán)內(nèi):
    image.png
  • 發(fā)現(xiàn)此時name已從編譯時的HTPerson變成了CatA赚爵,而我們的cls仍舊是HTPerson
    (類地址在內(nèi)存中是唯一的,地址相同表示是一個類)
    image.png
  • 下面以函數(shù)讀取為例法瑟,(屬性冀膝、協(xié)議的讀取和賦值方式一樣):
    image.png

將分類的methods函數(shù)列表讀取到mlist,如果存在:

  • 如果數(shù)組是否已滿(64)霎挟,將mlist內(nèi)部排序后窝剖,調(diào)用attachLists存到rwemethods中,并將mcount歸零酥夭。
  • mlist倒序插入到mlists

屬性協(xié)議也是相同的操作方式赐纱,只是讀取的內(nèi)容和存入的容器不同而已。

image.png
  • 至此熬北,已遍歷分類疙描,將分類的函數(shù)、屬性讶隐、協(xié)議都分別存儲到mlists起胰、proplistsprotolists中了巫延。

接下來效五,是將他們賦值給rwe對應屬性:

image.png

4.3 prepareMethodLists函數(shù)排序

函數(shù)在插入前地消,都會預先進行一輪排序,進入prepareMethodLists

image.png
  • 進入fixupMethodList:
image.png
  • 執(zhí)行完prepareMethodLists函數(shù)后畏妖,我們p mlists打印容器脉执,p $7[63]取出剛才存放在最后的mlistp $8->get(index)打印數(shù)據(jù):
image.png

發(fā)現(xiàn)排序后的順序為: [ func1, func3 , func2 ] 戒劫,確實不是根據(jù)sel字符串進行的排序半夷。

  • 我們使用p/x $8->get(0),打印SEL地址:
image.png
  • 0x0000000100003e12 < 0x0000000100003e18 < 0x0000000100003e1e谱仪,發(fā)現(xiàn)我們SEL地址確實是從小到大排列的玻熙。

所以驗證了:
函數(shù)的排序:不是根據(jù)SEL字符串排序否彩,也不是通過imp進行排序疯攒,而通過SEL地址進行排序

  • 排序后,我們通過attachLists完成數(shù)據(jù)的綁定

4.4 attachLists 綁定數(shù)據(jù)

  • 進入attachLists:
image.png

拓展函數(shù):

  • memcpy(開始位置列荔,放置內(nèi)容敬尺,占用大小)內(nèi)存拷貝
  • memmove(開始位置,移動內(nèi)容贴浙,占用大小)內(nèi)存平移

LRU算法:

  • Least Recently Used的縮寫砂吞,最近最少使用算法,越容易被調(diào)用(訪問)的放前面崎溃。

  • 回想一下蜻直,不管我們是動態(tài)插入函數(shù),還是添加分類袁串,一定是有需求時才這么操作概而。而新加入的數(shù)據(jù),明顯訪問頻率高于默認模板內(nèi)容囱修。所以我們addedLists使用LRU算法赎瑰,將舊數(shù)據(jù)放在最后面新數(shù)據(jù)永遠插入最前面破镰。 這樣可以提高查詢效率餐曼,減少運行時資源的占用

這里有3種情況:

- 0->1: 首次加入鲜漩,直接將addedLists[0]賦值給list源譬,是一維數(shù)組
(首次加載是本類數(shù)據(jù)在extAllocIfNeeded時孕似,從macho讀取ro中的對應數(shù)據(jù)加入)

image.png

- 1->多: 此時擴容為二維數(shù)組踩娘,舊數(shù)據(jù)插入后面新數(shù)據(jù)插入前面:
將數(shù)組擴容newCount大小
-> array()count記錄個數(shù)
-> 如果有舊數(shù)據(jù)鳞青,插入lists容器尾部
-> 調(diào)用memcpy內(nèi)存拷貝霸饲,從array()首地址開始为朋,將addedLists插入,占用addedCount個元素大小厚脉。

image.png

- 多 -> 更多: 類似于1->多的操作习寸,也是舊數(shù)據(jù)移到后面新數(shù)據(jù)插入前面
將數(shù)組擴容newCount大小
-> array()count記錄個數(shù)
-> 調(diào)用memmove內(nèi)存評議傻工,從array()首地址偏移addedCount個元素位置開始霞溪,移動array()舊數(shù)據(jù),占用oldCount個元素大小
-> 調(diào)用memcpy內(nèi)存拷貝中捆,從array()首地址開始鸯匹,將新數(shù)據(jù)addedLists插入,占用addedCount個元素大小泄伪。

image.png

所以這里rwe函數(shù)殴蓬、屬性、協(xié)議都是attachLists進行處理后完成的賦值蟋滴。

image.png

5. attachCategories的調(diào)用

此時染厅,我們通過一條線,完整熟悉了attachCategories分類數(shù)據(jù)添加到rwe中的整個流程和細節(jié)津函。

  • 我們可以反過來搜索attachCategories被哪些地方調(diào)用:
image.png

我們發(fā)現(xiàn)肖粮,除了我們已分析的attachToClass函數(shù),就只有load_categories_nolock函數(shù)調(diào)用了attachCategories尔苦。

  • 進入load_categories_nolock涩馆,加入測試代碼:
const char *mangledName = cls->mangledName();
const char * HTPersonName = "HTPerson";
if (strcmp(HTPersonName, mangledName) == 0 ) {
    auto ht_ro = (const class_ro_t *)cls->data();
    auto ht_isMeta = ht_ro->flags & RO_META;
    if (!ht_isMeta) {
        printf("%s - 精準定位: %s\n", __func__, mangledName);
    }
}
  • 再檢查load_categories_nolock在哪里被調(diào)用:

第一處被調(diào)用:loadAllCategories

image.png

繼續(xù)搜索loadAllCategories,發(fā)現(xiàn)在load_images被調(diào)用:

image.png

第二處被調(diào)用:_read_images第8步 分類的加載允坚。

image.png
  • _read_images的加載魂那,是從map_images過來的。

總結(jié):
分類的加載屋讶,總得來說有2個大的調(diào)用路徑

    1. map_images -> map_images_nolock -> _read_images 有2個可能路徑:
      路徑一: 第8步 分類的處理 -> load_categories_nolock -> attachCategories
      路徑二: 第9步 實現(xiàn)非懶加載類 -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
    1. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

至此冰寻,文初的2個問題,rwe何時加載皿渗?分類如何加載? 相信大家都十分清楚了


本節(jié)斩芭,我們已經(jīng)熟悉了分類加載方式

  • 但是我們一切研究都是在本類分類都實現(xiàn)+Load方法的前提乐疆,那其他組合的情況是怎樣呢划乖?
  • attachCategories這些調(diào)用路徑在什么情況下進入哪條路徑呢?

下一節(jié)OC底層原理十九:類的加載(下) 本類與分類load區(qū)別 & 關(guān)聯(lián)屬性挤土,我們將所有情況都一一分析琴庵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子迷殿,更是在濱河造成了極大的恐慌儿礼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆寺,死亡現(xiàn)場離奇詭異蚊夫,居然都是意外死亡,警方通過查閱死者的電腦和手機懦尝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門知纷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陵霉,你說我怎么就攤上這事琅轧。” “怎么了踊挠?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵乍桂,是天一觀的道長。 經(jīng)常有香客問我止毕,道長模蜡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任扁凛,我火速辦了婚禮,結(jié)果婚禮上闯传,老公的妹妹穿的比我還像新娘谨朝。我一直安慰自己,他們只是感情好甥绿,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布字币。 她就那樣靜靜地躺著,像睡著了一般共缕。 火紅的嫁衣襯著肌膚如雪洗出。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天图谷,我揣著相機與錄音翩活,去河邊找鬼。 笑死便贵,一個胖子當著我的面吹牛菠镇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播承璃,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼利耍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起隘梨,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤程癌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后轴猎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體席楚,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年税稼,在試婚紗的時候發(fā)現(xiàn)自己被綠了烦秩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡郎仆,死狀恐怖只祠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扰肌,我是刑警寧澤抛寝,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站曙旭,受9級特大地震影響盗舰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桂躏,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一钻趋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剂习,春花似錦蛮位、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至们何,卻和暖如春萄焦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冤竹。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工拂封, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贴见。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓烘苹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親片部。 傳聞我的和親對象是個殘疾皇子镣衡,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355