iOS原理探索03--isa與類的關(guān)聯(lián)原理

本文的主要介紹如何理解isa與類的關(guān)系本刽,在介紹之前我們首先要知道OC對象的本質(zhì)是什么赫冬?這里我們先插曲一個Clang編譯器讽坏。

一妇萄、下面是Clang介紹

Clang是?個C語?蜕企、C++、Objective-C語?的輕量級編譯器冠句。源代碼發(fā)布于BSD協(xié)議下轻掩。
Clang將?持其普通lambda表達式返回類型的簡化處理以及更好的處理constexpr關(guān)鍵字懦底。
Clang是?個由apple主導(dǎo)編寫唇牧,基于LLVM的C/C++/Objective-C編譯器
2013年4?,Clang已經(jīng)全??持C++11標(biāo)準(zhǔn)聚唐,并開始實現(xiàn)C++1y特性(也就是C++14丐重,這是C++的下?個?更新版本)。Clang將?持其普通lambda表達式杆查、返回類型的簡化處理以及更好的處理constexpr關(guān)鍵字扮惦。
Clang是?個C++編寫、基于LLVM亲桦、發(fā)布于LLVM BSD許可證下的C/C++/Objective-C/Objective-C++編譯器崖蜜。它與GNU C語?規(guī)范?乎完全兼容(當(dāng)然,也有部分不兼容的內(nèi)容客峭,包括編譯命令選項也會有點差異)豫领,并在此基礎(chǔ)上增加了額外的語法特性,?如C函數(shù)重載(通過__attribute__((overloadable))來修飾函數(shù))桃笙,其?標(biāo)(之?)就是超越GCC氏堤。

  • 如何將OC文件編譯為C++文件呢?
    clang -rewrite-objc main.m -o main.cpp?標(biāo)?件編譯成c++?件
  • UIKit報錯問題處理
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /
Applications/Xcode.app/Contents/Developer/Platforms/
iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m 

xcode安裝的時候順帶安裝了xcrun命令,xcrun命令在clang的基礎(chǔ)上進?了?些封裝鼠锈,要更好??些

  • 模擬器編譯
    xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)
  • 真機編譯
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main?-arm64.cpp (?機)

二闪檬、什么是對象?购笆?粗悯??

  • 我們先使用clang將下面這段代碼編譯為cpp文件同欠,在main中自定義一個類LGPerson样傍,有一個屬性name
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

編譯后的文件

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
};

// @property (nonatomic, copy) NSString *name;
/* @end */


// @implementation LGPerson

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
// @end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_99_79b11sqx7xd67mxf4rx5g0tr0000gn_T_main_949408_mi_0);
    }
    return 0;
}

  • 從編譯的main.cpp這段代碼铺遂,我們可以看出衫哥,源代碼的LGPerson編譯后是個結(jié)構(gòu)體struct
    • LGPerson_IMPL中的第一個屬性 其實就是 isa襟锐,是繼承自NSObject撤逢,屬于偽繼承, 偽繼承的方式 是直接將 LGPerson作為結(jié)構(gòu)體的第一個屬性粮坞,意味著LGPerson擁有 NSObject中的所有成員變量蚊荣。
    • LGPerson中的第一個屬性 NSObject_IVARS等效于NSObject中的isa

結(jié)論:對象的本質(zhì)是一個結(jié)構(gòu)體莫杈。

三互例、類的源碼分析

@interface NSObject <NSObject> {

    Class isa  OBJC_ISA_AVAILABILITY;
}

我們跟進源碼,發(fā)現(xiàn)NSObject的定義筝闹,我們發(fā)現(xiàn)了本文要討論的isa媳叨,由代碼可以看出isa是class類型,那么這是為什么呢丁存?為什么isa不是其他的類型呢肩杈?
緊接著我們通過源碼調(diào)試柴我,找到了ISA()方法解寝,這是isa的get方法

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

結(jié)論:從下面代碼(Class)isa.bits(Class)(isa.bits & ISA_MASK)我們可以看出艘儒,這兩步操作都是對ISA進行了Class的強制轉(zhuǎn)換聋伦,所以isa是class類型

四界睁、 objc_setProperty()方法解析

我們從什么是對象這一段的源碼編譯的cpp文件代碼可以看出觉增,在添加屬性name之后,通過Clang編譯后多了兩個方法翻斟,代碼如下:

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }

這里我們可以得知通過設(shè)置屬性逾礁,系統(tǒng)自己會添加屬性的set和get方法,并且是通過objc_setProperty方法來設(shè)置的访惜,那么我們就來追蹤一下它的源碼嘹履,

//objc_setProperty的接口實現(xiàn)
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}


//reallySetProperty()接口實現(xiàn)
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

通過reallySetProperty()接口實現(xiàn)我們發(fā)現(xiàn)屬性的設(shè)置腻扇,只是newValue = objc_retain(newValue);新值的retain,和objc_release(oldValue);舊值的release砾嫉。

五幼苛、cls 的關(guān)聯(lián)原理

探索一下通過initInstanceIsa是如何將cls與isa關(guān)聯(lián)的,在此之前焕刮,需要先了解什么是聯(lián)合體舶沿??配并?括荡?

聯(lián)合體與結(jié)構(gòu)體知識小拓展

  • 結(jié)構(gòu)體(struct)中所有變量是共存
    • 優(yōu)點是有容乃?,全?溉旋;
    • 缺點是struct內(nèi)存空間的分配是粗放的一汽,不管?不?,全分配低滩。
  • 聯(lián)合體(union)中是各變量是互斥
    • 缺點就是不夠包容召夹;
    • 優(yōu)點是內(nèi)存使?更為精細靈活,也節(jié)省了內(nèi)存空間
  • 首先我們來看一下isa_t的結(jié)構(gòu)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

我們可以看出isa_t是通過union聯(lián)合體來構(gòu)造的恕沫,從isa_t的定義中可以看出:

  • 提供了兩個成員监憎,cls 和 bits,由聯(lián)合體的定義所知婶溯,這兩個成員是互斥的鲸阔,也就意味著,當(dāng)初始化isa指針時迄委,有兩種初始化方式

    • 通過cls初始化褐筛,bits無默認值

    • 通過bits初始化,cls有默認值

除此之外它還提供了一個結(jié)構(gòu)體定義的位域叙身,用于存儲類信息及其他信息渔扎,結(jié)構(gòu)體的成員ISA_BITFIELD,這是一個宏定義信轿,有兩個版本 __arm64__(對應(yīng)ios 移動端)__x86_64__(對應(yīng)macOS)晃痴,以下是它們的一些宏定義,具體的宏定義代碼如下:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                     \
      uintptr_t has_assoc         : 1;                                        \
      uintptr_t has_cxx_dtor      : 1;                                        \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

先簡單介紹一下每個宏的意義:

nonpointer:表示是否對 isa 指針開啟指針優(yōu)化财忽;0純isa指針倘核,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數(shù)等即彪。

has_assoc:關(guān)聯(lián)對象標(biāo)志位紧唱,0沒有,1存在`。

has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更快的釋放對象漏益。

shiftcls存儲類指針的值酬凳。開啟指針優(yōu)化的情況下,在arm64 架構(gòu)中有 33 位?來存儲類指針遭庶。

magic:?于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間宁仔。

weakly_referenced:標(biāo)志對象是否被指向或者曾經(jīng)指向?個 ARC 的弱變量,沒有弱引?的對象可以更快釋放峦睡。

deallocating:標(biāo)志對象是否正在釋放內(nèi)存翎苫。

has_sidetable_rc:當(dāng)對象引?技術(shù)?于 10時,則需要借?該變量存儲進位榨了。

extra_rc當(dāng)表示該對象的引?計數(shù)值煎谍,實際上是引?計數(shù)值減 1,例如龙屉,如果對象的引?計數(shù)為 10呐粘,那么extra_rc為 9。如果引?計數(shù)?于 10转捕,則需要使?到下?的 has_sidetable_rc作岖。

源碼追蹤

通過alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路徑,查找到initInstanceIsa五芝,并進入其原理實現(xiàn)痘儡。

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        //初始化isa指針
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

六、isa與類的關(guān)聯(lián)

isa的關(guān)聯(lián)枢步,主要是通過initInstanceIsa方法中的calloc指針和當(dāng)前類的cls相關(guān)聯(lián)沉删,用isa指針中的shiftcls位域來存儲的相關(guān)信息,我們可以有以下幾種驗證方式:

【方式一】通過initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;驗證
我們通過initInstanceIsa->initIsa->newisa.shiftcls -> (uintptr_t)cls >> 3醉途,打印查看isa指針以及查看newisa.shiftcls存儲信息來驗證是否shiftcls位域中存儲著isa指針矾瑰。

打印(uintptr_t)cls >> 3和newisa.shiftcls
通過控制臺的輸出結(jié)果可以看出步驟一和步驟二的信息是一致的,這就驗證我們想法隘擎。

  • 我們再來比較一下bits賦值前后的結(jié)果殴穴,bits的位域中有兩處變化cls由默認值,變成了LGPerson嵌屎,將isacls完美關(guān)聯(lián)shiftcls0變成了536871965推正。

    圖片來自Style_月月簡書----isa成員值變化過程

  • 下面我們在提出疑問為什么在shiftcls賦值時需要類型強轉(zhuǎn)

因為內(nèi)存的存儲不能存儲字符串宝惰,機器碼只能識別0 、1這兩種數(shù)字再沧,所以需要將其轉(zhuǎn)換uintptr_t數(shù)據(jù)類型尼夺,這樣shiftcls中存儲的類信息才能被機器碼理解, 其中uintptr_tlong

  • 為什么需要右移3位淤堵?

主要是由于shiftcls的信息處于isa指針地址中間部分寝衫,前面還有3個位域,為了不影響前面的3個位域的數(shù)據(jù)拐邪,需要右移將其抹零慰毅。

【方式二】通過isa指針地址與ISA_MSAK 的值 & 來驗證
cla與isa關(guān)聯(lián)后,我們通過lldb扎阶,x/4gx 在控制臺輸出obj的存儲信息汹胃,在通過obj的isa指針&MASK,結(jié)果一樣的是LGPerson


isa指針&MASK結(jié)果
  • 注意
    • arm64中东臀,ISA_MASK 宏定義的值為0x0000000ffffffff8ULL

    • x86_64中着饥,ISA_MASK 宏定義的值為0x00007ffffffffff8ULL

【方式三】通過runtime的方法object_getClass驗證
通過查看object_getClass的源碼實現(xiàn),同樣可以驗證isa與類關(guān)聯(lián)的原理惰赋,通過runtimeapi宰掉,即object_getClass函數(shù)獲取類信息,下面我們來查看一下源碼

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

//接著往下走
inline Class 
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

//往下走到 ISA()

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

我們發(fā)現(xiàn)源碼一樣的是使用的isa.bits & ISA_MASK赁濒,在用Class類型做了強制轉(zhuǎn)換的轨奄。

【方式四】通過位運算驗證
回到_class_createInstanceFromZone方法。通過x/4gx obj 得到obj的存儲信息拒炎,當(dāng)前的信息存儲在isa指針中戚绕,且isa中的shiftcls此時占44位(因為處于macOS環(huán)境

  • 右邊3位,和左邊除去44位以外的部分都抹零枝冀,其相對位置是不變的;
    • isa地址右移3位
    • 在將得到的結(jié)果左移20位
    • 右移17位
      操作步驟以及結(jié)果輸出
      我們可以看出同樣的可以的此結(jié)論:isa的關(guān)聯(lián)舞丛,主要是通過initInstanceIsa方法中的calloc指針和當(dāng)前類的cls相關(guān)聯(lián),用isa指針中的shiftcls位域存儲類的相關(guān)信息果漾!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末球切,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绒障,更是在濱河造成了極大的恐慌吨凑,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户辱,死亡現(xiàn)場離奇詭異鸵钝,居然都是意外死亡,警方通過查閱死者的電腦和手機庐镐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門恩商,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人必逆,你說我怎么就攤上這事怠堪±柯遥” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵粟矿,是天一觀的道長凰棉。 經(jīng)常有香客問我,道長陌粹,這世上最難降的妖魔是什么撒犀? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮掏秩,結(jié)果婚禮上或舞,老公的妹妹穿的比我還像新娘。我一直安慰自己哗讥,他們只是感情好嚷那,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杆煞,像睡著了一般魏宽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上决乎,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天队询,我揣著相機與錄音,去河邊找鬼构诚。 笑死蚌斩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的范嘱。 我是一名探鬼主播送膳,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丑蛤!你這毒婦竟也來了叠聋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤受裹,失蹤者是張志新(化名)和其女友劉穎碌补,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棉饶,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡厦章,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了照藻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袜啃。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岩梳,靈堂內(nèi)的尸體忽然破棺而出囊骤,到底是詐尸還是另有隱情晃择,我是刑警寧澤冀值,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布也物,位于F島的核電站,受9級特大地震影響列疗,放射性物質(zhì)發(fā)生泄漏滑蚯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一抵栈、第九天 我趴在偏房一處隱蔽的房頂上張望告材。 院中可真熱鬧,春花似錦古劲、人聲如沸斥赋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疤剑。三九已至,卻和暖如春闷堡,著一層夾襖步出監(jiān)牢的瞬間隘膘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工杠览, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弯菊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓踱阿,卻偏偏與公主長得像管钳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子软舌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354