深入解析 ObjC 中方法的結(jié)構(gòu)

原文鏈接: http://draveness.me/method-struct/

關(guān)注倉庫憔狞,及時(shí)獲得更新:iOS-Source-Code-Analyze

因?yàn)?ObjC 的 runtime 只能在 Mac OS 下才能編譯鼻忠,所以文章中的代碼都是在 Mac OS露乏,也就是 x86_64 架構(gòu)下運(yùn)行的,對于在 arm64 中運(yùn)行的代碼會(huì)特別說明匈庭。

在上一篇分析 isa 的文章從 NSObject 的初始化了解 isa中曾經(jīng)說到過實(shí)例方法被調(diào)用時(shí)污桦,會(huì)通過其持有 isa 指針尋找對應(yīng)的類,然后在其中的 class_data_bits_t 中查找對應(yīng)的方法饼记,在這一篇文章中會(huì)介紹方法在 ObjC 中是如何存儲(chǔ)方法的。

這篇文章的首先會(huì)根據(jù) ObjC 源代碼來分析方法在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)慰枕,然后在 lldb 調(diào)試器中一步一步驗(yàn)證分析的正確性具则。

方法在內(nèi)存中的位置

先來了解一下 ObjC 中類的結(jié)構(gòu)圖:

objc-method-class
  • isa 是指向元類的指針,不了解元類的可以看 Classes and Metaclasses
  • super_class 指向當(dāng)前類的父類
  • cache 用于緩存指針和 vtable具帮,加速方法的調(diào)用
  • bits 就是存儲(chǔ)類的方法博肋、屬性低斋、遵循的協(xié)議等信息的地方

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

這一小結(jié)會(huì)分析類結(jié)構(gòu)體中的 class_data_bits_t bits

下面就是 ObjC 中 class_data_bits_t 的結(jié)構(gòu)體匪凡,其中只含有一個(gè) 64 位的 bits 用于存儲(chǔ)與類有關(guān)的信息:

objc-method-class-data-bits-t

objc_class 結(jié)構(gòu)體中的注釋寫到 class_data_bits_t 相當(dāng)于 class_rw_t 指針加上 rr/alloc 的標(biāo)志膊畴。

class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

它為我們提供了便捷方法用于返回其中的 class_rw_t * 指針:

class_rw_t* data() {
   return (class_rw_t *)(bits & FAST_DATA_MASK);
}

bitsFAST_DATA_MASK 進(jìn)行位運(yùn)算,只取其中的 [3, 47] 位轉(zhuǎn)換成 class_rw_t * 返回病游。

在 x86_64 架構(gòu)上唇跨,Mac OS 只使用了其中的 47 位來為對象分配地址。而且由于地址要按字節(jié)在內(nèi)存中按字節(jié)對齊衬衬,所以掩碼的后三位都是 0买猖。

因?yàn)?class_rw_t * 指針只存于第 [3, 47] 位,所以可以使用最后三位來存儲(chǔ)關(guān)于當(dāng)前類的其他信息:

objc-method-class_data_bits_t
#define FAST_IS_SWIFT           (1UL<<0)
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
#define FAST_DATA_MASK          0x00007ffffffffff8UL
  • isSwift()
    • FAST_IS_SWIFT 用于判斷 Swift 類
  • hasDefaultRR()
    • FAST_HAS_DEFAULT_RR 當(dāng)前類或者父類含有默認(rèn)的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法
  • requiresRawIsa()
    • FAST_REQUIRES_RAW_ISA 當(dāng)前類的實(shí)例需要 raw isa

執(zhí)行 class_data_bits_t 結(jié)構(gòu)體中的 data() 方法或者調(diào)用 objc_class 中的 data() 方法會(huì)返回同一個(gè) class_rw_t * 指針滋尉,因?yàn)?objc_class 中的方法只是對 class_data_bits_t 中對應(yīng)方法的封裝玉控。

// objc_class 中的 data() 方法
class_data_bits_t bits;

class_rw_t *data() { 
   return bits.data();
}

// class_data_bits_t 中的 data() 方法
uintptr_t bits;

class_rw_t* data() {
   return (class_rw_t *)(bits & FAST_DATA_MASK);
}

class_rw_tclass_ro_t

ObjC 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中:

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
};

其中還有一個(gè)指向常量的指針 ro狮惜,其中存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性高诺、方法以及遵循的協(xié)議

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

在編譯期間類的結(jié)構(gòu)中的 class_data_bits_t *data 指向的是一個(gè) class_ro_t * 指針:

objc-method-before-realize

然后在加載 ObjC 運(yùn)行時(shí)的過程中在 realizeClass 方法中:

  1. class_data_bits_t 調(diào)用 data 方法讽挟,將結(jié)果從 class_rw_t 強(qiáng)制轉(zhuǎn)換為 class_ro_t 指針
  2. 初始化一個(gè) class_rw_t 結(jié)構(gòu)體
  3. 設(shè)置結(jié)構(gòu)體 ro 的值以及 flag
  4. 最后設(shè)置正確的 data懒叛。
const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

下圖是 realizeClass 方法執(zhí)行過后的類所占用內(nèi)存的布局,你可以與上面調(diào)用方法前的內(nèi)存布局對比以下耽梅,看有哪些更改:

objc-method-after-realize-class

但是薛窥,在這段代碼運(yùn)行之后 class_rw_t 中的方法,屬性以及協(xié)議列表均為空眼姐。這時(shí)需要 realizeClass 調(diào)用 methodizeClass 方法來將類自己實(shí)現(xiàn)的方法(包括分類)诅迷、屬性和遵循的協(xié)議加載到 methodspropertiesprotocols 列表中众旗。

XXObject

下面罢杉,我們將分析一個(gè)類 XXObject 在運(yùn)行時(shí)初始化過程中內(nèi)存的更改,這是 XXObject 的接口與實(shí)現(xiàn):

// XXObject.h 文件
#import <Foundation/Foundation.h>

@interface XXObject : NSObject

- (void)hello;

@end

// XXObject.m 文件

#import "XXObject.h"

@implementation XXObject

- (void)hello {
    NSLog(@"Hello");
}

@end

這段代碼是運(yùn)行在 Mac OS X 10.11.3 (x86_64)版本中贡歧,而不是運(yùn)行在 iPhone 模擬器或者真機(jī)上的滩租,如果你在 iPhone 或者真機(jī)上運(yùn)行,可能有一定差別利朵。

objc-method-target

這是主程序的代碼:

#import <Foundation/Foundation.h>
#import "XXObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class cls = [XXObject class];
        NSLog(@"%p", cls);
    }
    return 0;
}

編譯后內(nèi)存中類的結(jié)構(gòu)

因?yàn)?strong>類在內(nèi)存中的位置是編譯期就確定的律想,先運(yùn)行一次代碼獲取 XXObject 在內(nèi)存中的地址。

0x100001168

接下來绍弟,在整個(gè) ObjC 運(yùn)行時(shí)初始化之前技即,也就是 _objc_init 方法中加入一個(gè)斷點(diǎn):

objc-method-after-compile

然后在 lldb 中輸入以下命令:

(lldb) p (objc_class *)0x100001168
(objc_class *) $0 = 0x0000000100001168
(lldb) p (class_data_bits_t *)0x100001188
(class_data_bits_t *) $1 = 0x0000000100001188
(lldb) p $1->data()
warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
(class_rw_t *) $2 = 0x00000001000010e8
(lldb) p (class_ro_t *)$2 // 將 class_rw_t 強(qiáng)制轉(zhuǎn)化為 class_ro_t
(class_ro_t *) $3 = 0x00000001000010e8
(lldb) p *$3
(class_ro_t) $4 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f7a "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
}
objc-method-lldb-print-before-realize

現(xiàn)在我們獲取了類經(jīng)過編譯器處理后的只讀屬性 class_ro_t

(class_ro_t) $4 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f7a "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
}

可以看到這里面只有 baseMethodListname 是有值的,其它的 ivarLayout樟遣、 baseProtocols而叼、 ivars身笤、weakIvarLayoutbaseProperties 都指向了空指針,因?yàn)轭愔袥]有實(shí)例變量葵陵,協(xié)議以及屬性液荸。所以這里的結(jié)構(gòu)體符合我們的預(yù)期。

通過下面的命令查看 baseMethodList 中的內(nèi)容:

(lldb) p $4.baseMethodList
(method_list_t *) $5 = 0x00000001000010c8
(lldb) p $5->get(0)
(method_t) $6 = {
  name = "hello"
  types = 0x0000000100000fa4 "v16@0:8"
  imp = 0x0000000100000e90 (method`-[XXObject hello] at XXObject.m:13)
}
(lldb) p $5->get(1)
Assertion failed: (i < count), function get, file /Users/apple/Desktop/objc-runtime/runtime/objc-runtime-new.h, line 110.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
objc-method-lldb-print-method-list

使用 $5->get(0) 時(shí)埃难,成功獲取到了 -[XXObject hello] 方法的結(jié)構(gòu)體 method_t莹弊。而嘗試獲取下一個(gè)方法時(shí),斷言提示我們當(dāng)前類只有一個(gè)方法涡尘。

realizeClass

這篇文章中不會(huì)對 realizeClass 進(jìn)行詳細(xì)的分析忍弛,該方法的主要作用是對類進(jìn)行第一次初始化,其中包括:

  • 分配可讀寫數(shù)據(jù)空間
  • 返回真正的類結(jié)構(gòu)
static Class realizeClass(Class cls)

上面就是這個(gè)方法的簽名考抄,我們需要在這個(gè)方法中打一個(gè)條件斷點(diǎn)细疚,來判斷當(dāng)前類是否為 XXObject

objc-method-lldb-breakpoint

這里直接判斷兩個(gè)指針是否相等,而不使用 [NSStringFromClass(cls) isEqualToString:@"XXObject"] 是因?yàn)樵谶@個(gè)時(shí)間點(diǎn)川梅,這些方法都不能調(diào)用疯兼,在 ObjC 中沒有這些方法,所以只能通過判斷類指針是否相等的方式來確認(rèn)當(dāng)前類是 XXObject贫途。

直接與指針比較是因?yàn)轭愒趦?nèi)存中的位置是編譯期確定的吧彪,只要代碼不改變,類在內(nèi)存中的位置就會(huì)不變(已經(jīng)說過很多遍了)丢早。

objc-method-breakpoint-before-set-r

這個(gè)斷點(diǎn)就設(shè)置在這里姨裸,因?yàn)?XXObject 是一個(gè)正常的類,所以會(huì)走 else 分支分配可寫的類數(shù)據(jù)怨酝。

運(yùn)行代碼時(shí)傀缩,因?yàn)槊看味紩?huì)判斷當(dāng)前類指針是不是指向的 XXObject,所以會(huì)等一會(huì)才會(huì)進(jìn)入斷點(diǎn)农猬。

在這時(shí)打印類結(jié)構(gòu)體中的 data 的值赡艰,發(fā)現(xiàn)其中的布局依舊是這樣的:

objc-method-before-realize

在運(yùn)行完這段代碼之后:

objc-method-after-realize-breakpoint

我們再來打印類的結(jié)構(gòu):

(lldb) p (objc_class *)cls // 打印類指針
(objc_class *) $262 = 0x0000000100001168
(lldb) p (class_data_bits_t *)0x0000000100001188 // 在類指針上加 32 的 offset 打印 class_data_bits_t 指針
(class_data_bits_t *) $263 = 0x0000000100001188
(lldb) p *$263 // 訪問 class_data_bits_t 指針的內(nèi)容
(class_data_bits_t) $264 = (bits = 4302315312)
(lldb) p $264.data() // 獲取 class_rw_t
(class_rw_t *) $265 = 0x0000000100701f30
(lldb) p *$265 // 訪問 class_rw_t 指針的內(nèi)容,發(fā)現(xiàn)它的 ro 已經(jīng)設(shè)置好了
(class_rw_t) $266 = {
  flags = 2148007936
  version = 0
  ro = 0x00000001000010e8
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = nil
  demangledName = 0x0000000000000000 <no value available>
}
(lldb) p $266.ro // 獲取 class_ro_t 指針
(const class_ro_t *) $267 = 0x00000001000010e8
(lldb) p *$267 // 訪問 class_ro_t 指針的內(nèi)容
(const class_ro_t) $268 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000f7a "XXObject"
  baseMethodList = 0x00000001000010c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
}
(lldb) p $268.baseMethodList // 獲取基本方法列表
(method_list_t *const) $269 = 0x00000001000010c8
(lldb) p $269->get(0) // 訪問第一個(gè)方法
(method_t) $270 = {
  name = "hello"
  types = 0x0000000100000fa4 "v16@0:8"
  imp = 0x0000000100000e90 (method`-[XXObject hello] at XXObject.m:13)
}
(lldb) p $269->get(1) // 嘗試訪問第二個(gè)方法斤葱,越界
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
Assertion failed: (i < count), function get, file /Users/apple/Desktop/objc-runtime/runtime/objc-runtime-new.h, line 110.
(lldb)
objc-method-print-class-struct-after-realize

最后一個(gè)操作實(shí)在是截取不到了

const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

在上述的代碼運(yùn)行之后慷垮,類的只讀指針 class_ro_t 以及可讀寫指針 class_rw_t 都被正確的設(shè)置了。但是到這里揍堕,其 class_rw_t 部分的方法等成員都指針均為空料身,這些會(huì)在 methodizeClass 中進(jìn)行設(shè)置:

objc-method-after-methodizeClass

在這里調(diào)用了 method_array_tattachLists 方法,將 baseMethods 中的方法添加到 methods 數(shù)組之后鹤啡。我們訪問 methods 才會(huì)獲取當(dāng)前類的實(shí)例方法。

方法的結(jié)構(gòu)

說了這么多蹲嚣,到現(xiàn)在我們可以簡單看一下方法的結(jié)構(gòu)递瑰,與類和對象一樣祟牲,方法在內(nèi)存中也是一個(gè)結(jié)構(gòu)體。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

其中包含方法名抖部,類型還有方法的實(shí)現(xiàn)指針 IMP

obj-method-struct

上面的 -[XXObject hello] 方法的結(jié)構(gòu)體是這樣的:

name = "hello"
types = 0x0000000100000fa4 "v16@0:8"
imp = 0x0000000100000e90 (method`-[XXObject hello] at XXObject.m:13

方法的名字在這里沒有什么好說的说贝。其中,方法的類型是一個(gè)非常奇怪的字符串 "v16@0:8" 這在 ObjC 中叫做類型編碼(Type Encoding)慎颗,你可以看這篇官方文檔了解與類型編碼相關(guān)的信息乡恕。

對于方法的實(shí)現(xiàn),lldb 為我們標(biāo)注了方法在文件中實(shí)現(xiàn)的位置俯萎。

小結(jié)

在分析方法在內(nèi)存中的位置時(shí)傲宜,筆者最開始一直在嘗試尋找只讀結(jié)構(gòu)體 class_ro_t 中的 baseMethods 第一次設(shè)置的位置(了解類的方法是如何被加載的)。嘗試從 methodizeClass 方法一直向上找夫啊,直到 _obj_init 方法也沒有找到設(shè)置只讀區(qū)域的 baseMethods 的方法函卒。

而且在 runtime 初始化之后,realizeClass 之前撇眯,從 class_data_bits_t 結(jié)構(gòu)體中獲取的 class_rw_t 一直都是錯(cuò)誤的报嵌,這個(gè)問題在最開始非常讓我困惑,直到后來在 realizeClass 中發(fā)現(xiàn)原來在這時(shí)并不是 class_rw_t 結(jié)構(gòu)體熊榛,而是class_ro_t锚国,才明白錯(cuò)誤的原因。

后來突然想到類的一些方法玄坦、屬性和協(xié)議實(shí)在編譯期決定的(baseMethods 等成員以及類在內(nèi)存中的位置都是編譯期決定的)血筑,才感覺到豁然開朗。

  1. 類在內(nèi)存中的位置是在編譯期間決定的营搅,在之后修改代碼云挟,也不會(huì)改變內(nèi)存中的位置。
  2. 類的方法转质、屬性以及協(xié)議在編譯期間存放到了“錯(cuò)誤”的位置园欣,直到 realizeClass 執(zhí)行之后,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t休蟹,這樣我們即可以在運(yùn)行時(shí)為 class_rw_t 添加方法沸枯,也不會(huì)影響類的只讀結(jié)構(gòu)。
  3. class_ro_t 中的屬性在運(yùn)行期間就不能改變了赂弓,再添加方法時(shí)绑榴,會(huì)修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods盈魁,對于方法的添加會(huì)在之后的文章中分析翔怎。

參考資料

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

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

Follow: @Draveness·Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赤套,隨后出現(xiàn)的幾起案子飘痛,更是在濱河造成了極大的恐慌,老刑警劉巖容握,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣脉,死亡現(xiàn)場離奇詭異,居然都是意外死亡剔氏,警方通過查閱死者的電腦和手機(jī)塑猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谈跛,“玉大人羊苟,你說我怎么就攤上這事”揖桑” “怎么了践险?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吹菱。 經(jīng)常有香客問我巍虫,道長,這世上最難降的妖魔是什么鳍刷? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任占遥,我火速辦了婚禮,結(jié)果婚禮上输瓜,老公的妹妹穿的比我還像新娘瓦胎。我一直安慰自己,他們只是感情好尤揣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布搔啊。 她就那樣靜靜地躺著,像睡著了一般北戏。 火紅的嫁衣襯著肌膚如雪负芋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天嗜愈,我揣著相機(jī)與錄音旧蛾,去河邊找鬼。 笑死蠕嫁,一個(gè)胖子當(dāng)著我的面吹牛锨天,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剃毒,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼病袄,長吁一口氣:“原來是場噩夢啊……” “哼搂赋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起益缠,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對情侶失蹤厂镇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后左刽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酌媒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年欠痴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秒咨。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喇辽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雨席,到底是詐尸還是另有隱情菩咨,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布陡厘,位于F島的核電站抽米,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏糙置。R本人自食惡果不足惜云茸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谤饭。 院中可真熱鬧标捺,春花似錦、人聲如沸揉抵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冤今。三九已至闺兢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辟汰,已是汗流浹背列敲。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帖汞,地道東北人戴而。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像翩蘸,于是被迫代替她去往敵國和親所意。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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