從 NSObject 的初始化了解 isa

原文鏈接: http://draveness.me/isa/

關(guān)注倉(cāng)庫(kù)隘截,及時(shí)獲得更新:iOS-Source-Code-Analyze

因?yàn)?ObjC 的 runtime 只能在 Mac OS 下才能編譯桶唐,所以文章中的代碼都是在 Mac OS椭赋,也就是 x86_64 架構(gòu)下運(yùn)行的,對(duì)于在 arm64 中運(yùn)行的代碼會(huì)特別說(shuō)明省撑。

如果你曾經(jīng)對(duì) ObjC 底層的實(shí)現(xiàn)有一定的了解魄衅,你應(yīng)該會(huì)知道 Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體鳍寂,所有的對(duì)象都包含一個(gè)類型為 isa 的指針蝗柔,那么你可能確實(shí)對(duì) ObjC 的底層有所知闻葵,不過(guò)現(xiàn)在的 ObjC 對(duì)象的結(jié)構(gòu)已經(jīng)不是這樣了。代替 isa 指針的是結(jié)構(gòu)體 isa_t, 這個(gè)結(jié)構(gòu)體中"包含"了當(dāng)前對(duì)象指向的類的信息癣丧,這篇文章中會(huì)介紹一些關(guān)于這個(gè)變化的知識(shí)槽畔。

struct objc_object {
    isa_t isa;
};

當(dāng) ObjC 為為一個(gè)對(duì)象分配內(nèi)存,初始化實(shí)例變量后胁编,在這些對(duì)象的實(shí)例變量的結(jié)構(gòu)體中的第一個(gè)就是 isa厢钧。

![class-object-struct](http://7xrlu3.com1.z0.glb.clouddn.com/2016-04-21-NSObject + NSObject Copy + @Draveness.png)

所有繼承自 NSObject 的類實(shí)例化后的對(duì)象都會(huì)包含一個(gè)類型為 isa_t 的結(jié)構(gòu)體。

從上圖中可以看出掏呼,不只是實(shí)例會(huì)包含一個(gè) isa 結(jié)構(gòu)體坏快,所有的也有這么一個(gè) isa铅檩。在 ObjC 中 Class 的定義也是一個(gè)名為 objc_class 的結(jié)構(gòu)體憎夷,如下:

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
};

由于 objc_class 結(jié)構(gòu)體是繼承自 objc_object 的,所以在這里顯式地寫出了 isa_t isa 這個(gè)成員變量昧旨。

isa 指針的作用與元類

到這里拾给,我們就明白了:Objective-C 中類也是一個(gè)對(duì)象祥得。

這個(gè) isa 包含了什么呢?回答這個(gè)問(wèn)題之前蒋得,要引入了另一個(gè)概念 元類(meta class)级及,我們先了解一些關(guān)于元類的信息。

因?yàn)樵?Objective-C 中额衙,對(duì)象的方法并沒(méi)有存儲(chǔ)于對(duì)象的結(jié)構(gòu)體中(如果每一個(gè)對(duì)象都保存了自己能執(zhí)行的方法饮焦,那么對(duì)內(nèi)存的占用有極大的影響)。

當(dāng)實(shí)例方法被調(diào)用時(shí)窍侧,它要通過(guò)自己持有的 isa 來(lái)查找對(duì)應(yīng)的類县踢,然后在這里的 class_data_bits_t 結(jié)構(gòu)體中查找對(duì)應(yīng)方法的實(shí)現(xiàn)。同時(shí)伟件,每一個(gè) objc_class 也有一個(gè)指向自己的父類的指針 super_class 用來(lái)查找繼承的方法硼啤。

關(guān)于如何在 class_data_bits_t 中查找對(duì)應(yīng)方法會(huì)在之后的文章中講到。這里只需要知道斧账,它會(huì)在這個(gè)結(jié)構(gòu)體中查找到對(duì)應(yīng)方法的實(shí)現(xiàn)就可以了谴返。

![Slice 1](http://7xrlu3.com1.z0.glb.clouddn.com/2016-04-21-Slice 1.png)

但是,這樣就有一個(gè)問(wèn)題咧织,類方法的實(shí)現(xiàn)又是如何查找并且調(diào)用的呢嗓袱?這時(shí),就需要引入元類來(lái)保證無(wú)論是類還是對(duì)象都能通過(guò)相同的機(jī)制查找方法的實(shí)現(xiàn)习绢。

objc-isa-meta-class

讓每一個(gè)類的 isa 指向?qū)?yīng)的元類索抓,這樣就達(dá)到了使類方法和實(shí)例方法的調(diào)用機(jī)制相同的目的:

  • 實(shí)例方法調(diào)用時(shí),通過(guò)對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)
  • 類方法調(diào)用時(shí)毯炮,通過(guò)類的 isa 在元類中獲取方法的實(shí)現(xiàn)

下面這張圖介紹了對(duì)象逼肯,類與元類之間的關(guān)系,筆者認(rèn)為已經(jīng)覺(jué)得足夠清晰了桃煎,所以不在贅述篮幢。

圖片來(lái)自 objc_explain_Classes_and_metaclasses

有關(guān)與介紹類與元類之間的關(guān)系的文章實(shí)在是太多了,因?yàn)檫@篇文章主要介紹 isa为迈,在這一小節(jié)只是對(duì)其作用以及元類的概念進(jìn)行介紹三椿。如果想要了解更多關(guān)于類與元類的信息,可以看 What is a meta-class in Objective-C?

結(jié)構(gòu)體 isa_t

其實(shí) isa_t 是一個(gè)定義得非常"奇怪"的結(jié)構(gòu)體葫辐,在 ObjC 源代碼中可以看到這樣的定義:

#define ISA_MASK        0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK  0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
#define RC_ONE   (1ULL<<56)
#define RC_HALF  (1ULL<<7)

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44;
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
    };
};

這是在 __x86_64__ 上的實(shí)現(xiàn)搜锰,對(duì)于 iPhone5s 等架構(gòu)為 __arm64__ 的設(shè)備上,具體結(jié)構(gòu)體的實(shí)現(xiàn)和位數(shù)可能有些差別耿战,不過(guò)這些字段都是存在的蛋叼,可以看這里的 arm64 上結(jié)構(gòu)體的實(shí)現(xiàn)

在本篇文章中, 我們會(huì)以 __x86_64__ 為例進(jìn)行分析,而不會(huì)對(duì)兩種架構(gòu)下由于不同的內(nèi)存布局方式導(dǎo)致的差異進(jìn)行分析。在我看來(lái)狈涮,這個(gè)細(xì)節(jié)不會(huì)影響對(duì) isa 指針的理解狐胎,不過(guò)還是要知道的。

筆者對(duì)這個(gè) isa_t 的實(shí)現(xiàn)聲明順序有一些更改歌馍,更方便分析和理解握巢。

union isa_t {
    ...
}?;

isa_t 是一個(gè) union 類型的結(jié)構(gòu)體,對(duì) union 不熟悉的讀者可以看這個(gè) stackoverflow 上的回答. 也就是說(shuō)其中的 isa_t松却、cls暴浦、 bits 還有結(jié)構(gòu)體共用同一塊地址空間。而 isa 總共會(huì)占據(jù) 64 位的內(nèi)存空間(決定于其中的結(jié)構(gòu)體)

objc-isa-isat
struct {
   uintptr_t indexed           : 1;
   uintptr_t has_assoc         : 1;
   uintptr_t has_cxx_dtor      : 1;
   uintptr_t shiftcls          : 44;
   uintptr_t magic             : 6;
   uintptr_t weakly_referenced : 1;
   uintptr_t deallocating      : 1;
   uintptr_t has_sidetable_rc  : 1;
   uintptr_t extra_rc          : 8;
};

isa 的初始化

我們可以通過(guò) isa 初始化的方法 initIsa 來(lái)初步了解這 64 位的 bits 的作用:

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

indexed 和 magic

當(dāng)我們對(duì)一個(gè) ObjC 對(duì)象分配內(nèi)存時(shí)晓锻,其方法調(diào)用棧中包含了上述的兩個(gè)方法肉渴,這里關(guān)注的重點(diǎn)是 initIsa 方法,由于在 initInstanceIsa 方法中傳入了 indexed = true带射,所以同规,我們簡(jiǎn)化一下這個(gè)方法的實(shí)現(xiàn):

inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    isa.bits = ISA_MAGIC_VALUE;
    isa.has_cxx_dtor = hasCxxDtor;
    isa.shiftcls = (uintptr_t)cls >> 3;
}

對(duì)整個(gè) isa 的值 bits 進(jìn)行設(shè)置,傳入 ISA_MAGIC_VALUE

#define ISA_MAGIC_VALUE 0x001d800000000001ULL

我們可以把它轉(zhuǎn)換成二進(jìn)制的數(shù)據(jù)窟社,然后看一下哪些屬性對(duì)應(yīng)的位被這行代碼初始化了(標(biāo)記為紅色):

objc-isa-isat-bits

從圖中了解到券勺,在使用 ISA_MAGIC_VALUE 設(shè)置 isa_t 結(jié)構(gòu)體之后,實(shí)際上只是設(shè)置了 indexed 以及 magic 這兩部分的值灿里。

  • 其中 indexed 表示 isa_t 的類型

    • 0 表示 raw isa关炼,也就是沒(méi)有結(jié)構(gòu)體的部分,訪問(wèn)對(duì)象的 isa 會(huì)直接返回一個(gè)指向 cls 的指針匣吊,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型儒拂。

        union isa_t {
            isa_t() { }
            isa_t(uintptr_t value) : bits(value) { }
        
            Class cls;
            uintptr_t bits;
        };
      
    • 1 表示當(dāng)前 isa 不是指針,但是其中也有 cls 的信息色鸳,只是其中關(guān)于類的指針都是保存在 shiftcls社痛。

        union isa_t {
            isa_t() { }
            isa_t(uintptr_t value) : bits(value) { }
        
            Class cls;
            uintptr_t bits;
        
            struct {
                uintptr_t indexed           : 1;
                uintptr_t has_assoc         : 1;
                uintptr_t has_cxx_dtor      : 1;
                uintptr_t shiftcls          : 44;
                uintptr_t magic             : 6;
                uintptr_t weakly_referenced : 1;
                uintptr_t deallocating      : 1;
                uintptr_t has_sidetable_rc  : 1;
                uintptr_t extra_rc          : 8;
            };
        };
      
  • magic 的值為 0x3b 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間

has_cxx_dtor

在設(shè)置 indexedmagic 值之后避消,會(huì)設(shè)置 isahas_cxx_dtor葛碧,這一位表示當(dāng)前對(duì)象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒(méi)有析構(gòu)器就會(huì)快速釋放內(nèi)存拄踪。

isa.has_cxx_dtor = hasCxxDtor;
objc-isa-isat-bits-has-css-dto

shiftcls

在為 indexed吏砂、 magichas_cxx_dtor 設(shè)置之后撵儿,我們就要將當(dāng)前對(duì)象對(duì)應(yīng)的類指針存入 isa 結(jié)構(gòu)體中了。

isa.shiftcls = (uintptr_t)cls >> 3;

將當(dāng)前地址右移三位的主要原因是用于將 Class 指針中無(wú)用的后三位清楚減小內(nèi)存的消耗狐血,因?yàn)轭惖闹羔樢凑兆止?jié)(8 bits)對(duì)齊內(nèi)存淀歇,其指針后三位都是沒(méi)有意義的 0

絕大多數(shù)機(jī)器的架構(gòu)都是 byte-addressable 的匈织,但是對(duì)象的內(nèi)存地址必須對(duì)齊到字節(jié)的倍數(shù)浪默,這樣可以提高代碼運(yùn)行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對(duì)齊的最后三位比特為 000浴鸿,我們只會(huì)用其中的 30 位來(lái)表示對(duì)象的地址。

而 ObjC 中的類指針的地址后三位也為 0弦追,在 _class_createInstanceFromZone 方法中打印了調(diào)用這個(gè)方法傳入的類指針:

objc-isa-print-cls

可以看到岳链,這里打印出來(lái)的所有類指針十六進(jìn)制地址的最后一位都為 8 或者 0。也就是說(shuō)劲件,類指針的后三位都為 0掸哑,所以,我們?cè)谏厦娲鎯?chǔ) Class 指針時(shí)右移三位是沒(méi)有問(wèn)題的零远。

isa.shiftcls = (uintptr_t)cls >> 3;

如果再嘗試打印對(duì)象指針的話苗分,會(huì)發(fā)現(xiàn)所有對(duì)象內(nèi)存地址的后四位都是 0,說(shuō)明 ObjC 在初始化內(nèi)存時(shí)是以 16 個(gè)字節(jié)對(duì)齊的, 分配的內(nèi)存地址后四位都是 0牵辣。

objc-isa-print-object

使用整個(gè)指針大小的內(nèi)存來(lái)存儲(chǔ) isa 指針有些浪費(fèi)摔癣,尤其在 64 位的 CPU 上。在 ARM64 運(yùn)行的 iOS 只使用了 33 位作為指針(與結(jié)構(gòu)體中的 33 位無(wú)關(guān)纬向,Mac OS 上為 47 位)择浊,而剩下的 31 位用于其它目的。類的指針也同樣根據(jù)字節(jié)對(duì)齊了逾条,每一個(gè)類指針的地址都能夠被 8 整除琢岩,也就是使最后 3 bits 為 0,為 isa 留下 34 位用于性能的優(yōu)化师脂。

Using an entire pointer-sized piece of memory for the isa pointer is a bit wasteful, especially on 64-bit CPUs which don't use all 64 bits of a pointer. ARM64 running iOS currently uses only 33 bits of a pointer, leaving 31 bits for other purposes. Class pointers are also aligned, meaning that a class pointer is guaranteed to be divisible by 8, which frees up another three bits, leaving 34 bits of the isa available for other uses. Apple's ARM64 runtime takes advantage of this for some great performance improvements.
from ARM64 and You

我嘗試運(yùn)行了下面的代碼將 NSObject 的類指針和對(duì)象的 isa 打印出來(lái)担孔,具體分析一下

objc-isa-print-class-object
object_pointer: 0000000001011101100000000000000100000000001110101110000011111001 // 補(bǔ)全至 64 位
class_pointer:                                 100000000001110101110000011111000

編譯器對(duì)直接訪問(wèn) isa 的操作會(huì)有警告,因?yàn)橹苯釉L問(wèn) isa 已經(jīng)不會(huì)返回類指針了吃警,這種行為已經(jīng)被啟用了糕篇,取而代之的是使用 ISA() 方法來(lái)獲取類指針。

代碼中的 object 對(duì)象的 isa 結(jié)構(gòu)體中的內(nèi)容是這樣的:

objc-isa-isat-class-highlight-bits

其中紅色的為類指針酌心,與上面打印出的 [NSObject class] 指針右移三位的結(jié)果完全相同娩缰。這也就驗(yàn)證了我們之前對(duì)于初始化 isa 時(shí)對(duì) initIsa 方法的分析是正確的。它設(shè)置了 indexed谒府、magic 以及 shiftcls拼坎。

<a id='ISA()'></a>ISA() 方法

因?yàn)槲覀兪褂媒Y(jié)構(gòu)體取代了原有的 isa 指針,所以要提供一個(gè)方法 ISA() 來(lái)返回類指針完疫。

其中 ISA_MASK 是宏定義泰鸡,這里通過(guò)掩碼的方式獲取類指針:

#define ISA_MASK 0x00007ffffffffff8ULL
inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

其它 bits

isa_t 中,我們還有一些沒(méi)有介紹的其它 bits壳鹤,在這個(gè)小結(jié)就簡(jiǎn)單介紹下這些 bits 的作用

  • has_assoc
    • 對(duì)象含有或者曾經(jīng)含有關(guān)聯(lián)引用盛龄,沒(méi)有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
  • weakly_referenced
    • 對(duì)象被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量,沒(méi)有弱引用的對(duì)象可以更快釋放
  • deallocating
    • 對(duì)象正在釋放內(nèi)存
  • has_sidetable_rc
    • 對(duì)象的引用計(jì)數(shù)太大了,存不下
  • extra_rc
    • 對(duì)象的引用計(jì)數(shù)超過(guò) 1余舶,會(huì)存在這個(gè)這個(gè)里面啊鸭,如果引用計(jì)數(shù)為 10,extra_rc 的值就為 9
struct {
   uintptr_t indexed           : 1;
   uintptr_t has_assoc         : 1;
   uintptr_t has_cxx_dtor      : 1;
   uintptr_t shiftcls          : 44;
   uintptr_t magic             : 6;
   uintptr_t weakly_referenced : 1;
   uintptr_t deallocating      : 1;
   uintptr_t has_sidetable_rc  : 1;
   uintptr_t extra_rc          : 8;
};

<a id='arm64'></a>arm64 架構(gòu)中的 isa_t 結(jié)構(gòu)體

#define ISA_MASK        0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK  0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
#define RC_ONE   (1ULL<<45)
#define RC_HALF  (1ULL<<18)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

    struct {
       uintptr_t indexed           : 1;
       uintptr_t has_assoc         : 1;
       uintptr_t has_cxx_dtor      : 1;
       uintptr_t shiftcls          : 33;
       uintptr_t magic             : 6;
       uintptr_t weakly_referenced : 1;
       uintptr_t deallocating      : 1;
       uintptr_t has_sidetable_rc  : 1;
       uintptr_t extra_rc          : 19;
    };
};

參考資料

關(guān)注倉(cāng)庫(kù)匿值,及時(shí)獲得更新:iOS-Source-Code-Analyze

轉(zhuǎn)載請(qǐng)注明 Blog: Draveness

Follow: @Draveness·Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赠制,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挟憔,更是在濱河造成了極大的恐慌钟些,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绊谭,死亡現(xiàn)場(chǎng)離奇詭異政恍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)达传,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門篙耗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人宪赶,你說(shuō)我怎么就攤上這事鹤树。” “怎么了逊朽?”我有些...
    開(kāi)封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵罕伯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我叽讳,道長(zhǎng)追他,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任岛蚤,我火速辦了婚禮邑狸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涤妒。我一直安慰自己单雾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布她紫。 她就那樣靜靜地躺著硅堆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贿讹。 梳的紋絲不亂的頭發(fā)上渐逃,一...
    開(kāi)封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音民褂,去河邊找鬼茄菊。 笑死疯潭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的面殖。 我是一名探鬼主播竖哩,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脊僚!你這毒婦竟也來(lái)了相叁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吃挑,失蹤者是張志新(化名)和其女友劉穎钝荡,沒(méi)想到半個(gè)月后街立,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體舶衬,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年赎离,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逛犹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梁剔,死狀恐怖虽画,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荣病,我是刑警寧澤码撰,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站个盆,受9級(jí)特大地震影響脖岛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颊亮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一柴梆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终惑,春花似錦绍在、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至霸奕,卻和暖如春卸察,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铅祸。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工坑质, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留合武,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓涡扼,卻偏偏與公主長(zhǎng)得像稼跳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吃沪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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