iOS 底層面試題

前言

我們類的底層探索已經(jīng)告一段落唆铐,我們梳理一下常見的面試題走趋,希望對你有些幫助蘸鲸。

問題

  • 1.runtime是什么?
  • 2.runtime如何實(shí)現(xiàn)weak,為什么可以自動置為nil?
  • 3.runtime Associate方法關(guān)聯(lián)的對象囚戚,是否需要在dealloc中釋放?
  • 4.關(guān)聯(lián)對象AssociationsManager是否唯一酵熙?
  • 5.分類方法會覆蓋本類方法嗎?
  • 6.所有分類方法都優(yōu)先于本類嗎驰坊?
  • 7.方法的本質(zhì)匾二,SEL是什么?IMP是什么拳芙?兩者之間關(guān)系是什么察藐?
  • 8.編譯后的類能否添加實(shí)例變量?能否向運(yùn)行時創(chuàng)建的類添加實(shí)例變量?
  • 9.[self class][super class]區(qū)別和原理分析
  • 10.內(nèi)存平移問題
問題一:runtime是什么?

runtime 是由CC++匯編實(shí)現(xiàn)的?套API舟扎,為OC語?加?了?向?qū)ο蠓址桑\(yùn)?時的功能。
運(yùn)?時(Runtime)是指將數(shù)據(jù)類型的確定由編譯時推遲到了運(yùn)?時.
舉個例?: extension - category 的區(qū)別(extension是編譯期就確定了睹限,但是懶加載的category是在運(yùn)行時動態(tài)加入的)譬猫。
平時我們編寫的OC代碼讯檐,在程序運(yùn)?過程中,其實(shí)最終會轉(zhuǎn)換成RuntimeC語?代碼染服,Runtime 是Object-C 的幕后?作者

問題二:runtime如何實(shí)現(xiàn)weak别洪,為什么可以自動置為nil?
  1. 通過SideTable找到我們的weak_table
  2. weak_table 根據(jù)referent 找到或者創(chuàng)建 weak_entry_t
  3. 然后append_referrer(entry, referrer)將我的新弱引用的對象加進(jìn)去entry
  4. 最后weak_entry_insertentry加入到我們的weak_table

底層源碼調(diào)用流程如下圖所示

weak底層調(diào)用
問題三:runtime Associate方法關(guān)聯(lián)的對象,是否需要在dealloc中釋放?

當(dāng)我們創(chuàng)建的對象釋放時肌索,會調(diào)用dealloc方法蕉拢,其中的大致流程如下:

  • 1、C++函數(shù)釋放 :objc_cxxDestruct
  • 2诚亚、移除關(guān)聯(lián)屬性:_object_remove_assocations
  • 3晕换、將弱引用自動設(shè)置nil:weak_clear_no_lock(&table.weak_table, (id)this);
  • 4、引用計數(shù)處理:table.refcnts.erase(this)
  • 5站宗、銷毀對象:free(obj)

所以闸准,關(guān)聯(lián)對象不需要我們手動移除,會在對象析構(gòu)即dealloc時釋放

dealloc 源碼

dealloc的源碼查找路徑為:dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose(釋放對象)-> objc_destructInstance -> _object_remove_assocations

  • 在objc源碼中搜索dealloc的源碼實(shí)現(xiàn)
// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
  • 進(jìn)入_objc_rootDealloc源碼實(shí)現(xiàn)梢灭,主要是對對象進(jìn)行析構(gòu)
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}
  • 進(jìn)入rootDealloc源碼實(shí)現(xiàn)夷家,發(fā)現(xiàn)其中有關(guān)聯(lián)屬性時設(shè)置bool值,當(dāng)有這些條件時敏释,需要進(jìn)入else流程

    image
  • 進(jìn)入object_dispose源碼實(shí)現(xiàn)库快,主要是銷毀實(shí)例對象

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

  • 進(jìn)入objc_destructInstance源碼實(shí)現(xiàn),在這里有移除關(guān)聯(lián)屬性的方法
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
  • 進(jìn)入_object_remove_assocations源碼钥顽,關(guān)聯(lián)屬性的移除义屏,主要是從全局哈希map中找到相關(guān)對象的迭代器,然后將迭代器中關(guān)聯(lián)屬性蜂大,從頭到尾的移除
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
void
_object_remove_assocations(id object)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        //獲取迭代器
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        //從頭到尾逐個移除
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }

    // release everything (outside of the lock).
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}
問題四:關(guān)聯(lián)對象AssociationsManager是否唯一闽铐?

AssociationsManager結(jié)構(gòu)中,manager只是對外代言人奶浦,并不是唯一的兄墅,AssociationsHashMap哈希表才是唯一的。

1. 運(yùn)行驗(yàn)證:
移除鎖澳叉,這樣可以同時存在2個manager了隙咸。

image
  • 加入測試代碼,創(chuàng)建2個manager耳高,都調(diào)用get()扎瓶,發(fā)現(xiàn)2個讀取的associations相同地址
  • 證明AssociationsHashMap在內(nèi)存中是獨(dú)一份的泌枪,而manager只是外層包裝概荷,可以創(chuàng)建多個。
測試
問題五:分類方法會覆蓋本類方法嗎碌燕?
  • 分類方法會調(diào)用attachLists误证,將分類方法插入了本類方法前面继薛,全都存儲起來。并不是覆蓋本類方法愈捅,這個在我們之前的文章中 iOS-類的加載(下)有詳細(xì)的解釋遏考。
問題六:所有分類方法都優(yōu)先于本類嗎?

類的方法 和 分類方法 重名蓝谨,如果調(diào)用灌具,是什么情況?

  • 如果同名方法是普通方法譬巫,包括initialize -- 先調(diào)用分類方法

    • 因?yàn)?code>分類的方法是在類realize之后 attach進(jìn)去的咖楣,插在類的方法的前面,所以優(yōu)先調(diào)用分類的方法(注意:不是分類覆蓋主類B簟S栈摺)

    • initialize方法什么時候調(diào)用? initialize方法也是主動調(diào)用咕缎,即第一次消息時調(diào)用珠十,為了不影響整個load,可以將需要提前加載的數(shù)據(jù)寫到initialize

  • 如果同名方法是load方法 -- 先 主類load凭豪,后分類load(分類之間焙蹭,看編譯的順序)

image
問題七:方法的本質(zhì),SEL是什么嫂伞?IMP是什么壳嚎?兩者之間關(guān)系是什么?

方法的本質(zhì):發(fā)送消息末早,消息會有以下幾個流程

  • 快速查找(objc_msgSend) -cache_t緩存消息中查找
  • 慢速查找 - 遞歸自己|父類 -lookUpImpOrForward
  • 查找不到消息:動態(tài)方法解析 -resolveInstanceMethod
  • 消息快速轉(zhuǎn)發(fā) - forwardingTargetForSelector
  • 消息慢速轉(zhuǎn)發(fā) - methodSignatureForSelector & forwardInvocation

sel是方法編號 - 在read_images期間就編譯進(jìn)了內(nèi)存

imp是函數(shù)實(shí)現(xiàn)指針 ,找imp就是找函數(shù)的過程

打個比方:加入你要從一本字典中查找某個字说庭,那么sel相當(dāng)于 字典的目錄title然磷,imp 相當(dāng)于 字典的頁碼。

問題八:編譯后的類能否添加實(shí)例變量刊驴?能否向運(yùn)行時創(chuàng)建的類添加實(shí)例變量?

1姿搜、不可以。 因?yàn)榫幾g好的實(shí)例變量存放的位置在類的ro捆憎,一旦編譯完成舅柜,內(nèi)存結(jié)構(gòu)就完全確定了,無法修改躲惰。

2致份、運(yùn)行時在register注冊前,可以添加础拨。但是調(diào)用運(yùn)行時register注冊后氮块,就完成了內(nèi)存的注入绍载,內(nèi)存結(jié)構(gòu)確定了,無法修改滔蝉。

問題九:[self class][super class]區(qū)別和原理分析
  • [self class]就是發(fā)送消息objc_msgSend击儡,消息接受者是self,方法編號(SEL)是class

  • [super class]本質(zhì)是objc_msgSendSuper蝠引,消息接受者還是self阳谍,方法編號是class

實(shí)際運(yùn)行時螃概,[super class]在匯編層執(zhí)行的是objc_msgSendSuper2矫夯,直接從superclass父類開始搜索,節(jié)約了一輪查找資源

測試代碼:

@interface ZGPerson : NSObject
@end
@implementation ZGPerson
- (instancetype)init {
 if (self = [super init]) {
       NSLog(@"%@ %@", [self class], [super class]);
   }
   return self; }
@end

int main(int argc, const char * argv[]) {
   @autoreleasepool {
       ZGPerson * person = [[ZGPerson alloc] init];
  }
   return 0;
}

  • 打印結(jié)果: 都是ZGPerson
    結(jié)果

    結(jié)果與我想的不一樣,為什么不是ZGPersonNSObject呢谅年?我們查看源碼分析一下

我們查看 [self class]中的class源碼

- (Class)class {
    return object_getClass(self);
}

??
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

其底層是獲取對象的isa茧痒,當(dāng)前的對象是ZGPerson,其isa是同名的ZGPerson融蹂,所以[self class]打印的是ZGPerson

[super class]中旺订,其中super 是語法的 關(guān)鍵字,可以通過clangsuper的本質(zhì)超燃,clang生成cpp編譯文件(clang -rewrite-objc ZGPerson.m -o ZGPerson.cpp)区拳,打開main.cpp文件:

ZGPerson.cpp

底層源碼中搜索__rw_objc_super,是一個中間結(jié)構(gòu)體

__rw_objc_super

objc中搜索objc_msgSendSuper意乓,查看其隱藏參數(shù)

objc_msgSendSuper

搜索struct objc_super

objc_super

通過clang的底層編譯代碼可知樱调,當(dāng)前消息的接收者 等于 self,而self 等于 LGTeacher届良,所以 [super class]進(jìn)入class方法源碼后笆凌,其中的self是init后的實(shí)例對象,實(shí)例對象的isa指向的是本類士葫,即消息接收者是LGTeacher本類

  • 我們再來看[super class]在運(yùn)行時是否如上一步的底層編碼所示乞而,是objc_msgSendSuper,打開匯編調(diào)試慢显,調(diào)試結(jié)果如下

    image
    • 搜索objc_msgSendSuper2爪模,從注釋得知,是從 類開始查找荚藻,而不是父類

      objc_msgSendSuper2
    • 查看objc_msgSendSuper2的匯編源碼屋灌,是從superclass中的cache中查找方法

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame

ldp p0, p16, [x0]       // p0 = real receiver, p16 = class 取出receiver 和 class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2//cache中查找--快速查找

END_ENTRY _objc_msgSendSuper2
總結(jié):
  • [self class]方法調(diào)用的本質(zhì)是 發(fā)送消息,調(diào)用class的消息流程应狱,拿到元類的類型共郭,在這里是因?yàn)轭愐呀?jīng)加載到內(nèi)存,所以在讀取時是一個字符串類型,這個字符串類型是在map_imagesreadClass時已經(jīng)加入表中落塑,所以打印為ZGPerson

  • [super class]打印的是ZGPerson纽疟,原因是當(dāng)前的super是一個關(guān)鍵字,在這里只調(diào)用objc_msgSendSuper2憾赁,其實(shí)他的消息接收者和[self class]是一模一樣的污朽,所以返回的是ZGPerson

問題十:runtime是什么?內(nèi)存平移問題
Class cls = [LGPerson class];
void  *kc = &cls;  //
[(__bridge id)kc saySomething];

LGPerson中有一個屬性 kc_name 和一個實(shí)例方法saySomething,通過上面代碼這種方式龙考,能否調(diào)用實(shí)例方法蟆肆?為什么?

代碼調(diào)試

  • 我們在日常開發(fā)中的調(diào)用方式是下面這種
LGPerson *person = [LGPerson alloc];
[person saySomething];

  • 通過運(yùn)行發(fā)現(xiàn)晦款,是可以執(zhí)行的炎功,打印結(jié)果如下

    image
  • [person saySomething]的本質(zhì)是對象發(fā)送消息,那么當(dāng)前的person是什么缓溅?

    • personisa指向類LGPersonperson的首地址 指向 LGPerson的首地址蛇损,我們可以通過LGPerson的內(nèi)存平移找到cache,在cache中查找方法

      image
  • [(__bridge id)kc saySomething]中的kc是來自于LGPerson 這個類坛怪,然后有一個指針kc淤齐,將其指向LGPerson的首地址

    image

所以,person是指向LGPerson類的結(jié)構(gòu)袜匿,kc也是指向LGPerson類的結(jié)構(gòu)更啄,然后都是在LGPerson中的methodList中查找方法

image

修改:saySomething里面有屬性 self.kc_name 的打印

代碼如下所示

- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.kc_name);
}

//下面這兩種方式調(diào)用
//方式一
Class cls = [LGPerson class];
void  *kc = &cls; 
[(__bridge id)kc saySomething]; 

//方式二:常規(guī)調(diào)用
LGPerson *person = [LGPerson alloc];
 [person saySomething];

  • 查看這兩種調(diào)用方式的打印結(jié)果,如下所示
    • kc方式的調(diào)用打印的kc_name<ViewController: 0x7fe29170b560>

    • person方式的調(diào)用打印的kc_name(null)

      image

為什么會出現(xiàn)打印不一致的情況居灯?

  • 其中person方式的kc_name 是由于 self指向person的內(nèi)存結(jié)構(gòu)祭务,然后通過內(nèi)存平移8字節(jié),取出去kc_name怪嫌,即self指針首地址平移8字節(jié)獲得

    image
  • 【方式一】其中kc指針中沒有任何义锥,所以kc表示8字節(jié)指針self.kc_name的獲取岩灭,相當(dāng)于 kc首地址的指針也需要平移8字節(jié)找kc_name缨该,那么此時的kc的指針地址是多少?平移8字節(jié)獲取的是什么川背?

    • kc是一個指針,是存在中的蛤袒,棧是一個先進(jìn)后出的結(jié)構(gòu)熄云,參數(shù)傳入就是一個不斷壓棧的過程,
      • 其中隱藏參數(shù)會壓入棧妙真,且每個函數(shù)都會有兩個隱藏參數(shù)(id self缴允,sel _cmd),可以通過clang查看底層編譯

      • 隱藏參數(shù)壓棧的過程,其地址是遞減的,而棧是從高地址->低地址 分配的练般,即在棧中矗漾,參數(shù)會從前往后一直壓

      • super通過clang查看底層的編譯,是objc_msgSendSuper薄料,其第一個參數(shù)是一個結(jié)構(gòu)體__rw_objc_super(self敞贡,class_getSuperclass),那么結(jié)構(gòu)體中的屬性是如何壓棧的摄职?可以通過自定義一個結(jié)構(gòu)體誊役,判斷結(jié)構(gòu)體內(nèi)部成員的壓棧情況

        • p &person3

        • p *(NSNumber **)0x00007ffee83a8090

        • p *(NSNumber **)0x00007ffee83a8098

          image

          所以圖中可以得出 20先加入,再加入10谷市,因此結(jié)構(gòu)體內(nèi)部的壓棧情況是 低地址->高地址蛔垢,遞增的,棧中結(jié)構(gòu)體內(nèi)部的成員是反向壓入棧迫悠,即低地址->高地址鹏漆,是遞增的,

  • 所以到目前為止创泄,棧中從高地址到低地址的順序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person

    • self_cmdviewDidLoad方法的兩個隱藏參數(shù)艺玲,是高地址->低地址正向壓棧

    • class_getSuperClassselfobjc_msgSendSuper2中的結(jié)構(gòu)體成員,是從最后一個成員變量验烧,即低地址->高地址反向壓棧

可以通過下面這段代碼打印下棧的存儲是否如上面所說

void *sp  = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;

for (long i = 0; i<count; i++) {
    void *address = sp - 0x8 * I;
    if ( i == 1) {
        NSLog(@"%p : %s",address, *(char **)address);
    }else{
        NSLog(@"%p : %@",address, *(void **)address);
    }
}

運(yùn)行結(jié)果如下

image

其中為什么class_getSuperclassViewController板驳,因?yàn)?code>objc_msgSendSuper2返回的是當(dāng)前類,兩個self碍拆,并不是同一個self若治,而是棧的指針不同,但是指向同一片內(nèi)存空間

  • [(__bridge id)kc saySomething]調(diào)用時感混,此時的kc是 LGPerson: 0x7ffeec381098端幼,所以saySomething方法中傳入的self 還是LGPerson,但并不是我們通常認(rèn)為的LGPerson弧满,使我們當(dāng)前傳入的消息接收者婆跑,即LGPerson: 0x7ffeec381098,是LGPerson的實(shí)例對象庭呜,此時的操作與普通的LGPerson是一致的滑进,即LGPerson的地址內(nèi)存平移8字節(jié)
    • 普通person流程:person -> kc_name - 內(nèi)存平移8字節(jié)

    • kc流程:0x7ffeec381098 + 0x80 -> 0x7ffeec3810a0,即為self,指向<ViewController: 0x7fac45514f50>募谎,如下圖所示

      image

其中 personLGPerson的關(guān)系是 person是以LGPerson為模板的實(shí)例化對象扶关,即alloc有一個指針地址,指向isa数冬,isa指向LGPerson节槐,它們之間關(guān)聯(lián)是有一個isa指向

而kc也是指向LGPerson的關(guān)系,編譯器會認(rèn)為 kc也是LGPerson的一個實(shí)例化對象铜异,即kc相當(dāng)于isa哥倔,即首地址,指向LGPerson揍庄,具有和person一樣的效果咆蒿,簡單來說,我們已經(jīng)完全將編譯器騙過了币绩,即kc也有kc_name酝枢。由于person查找kc_name是通過內(nèi)存平移8字節(jié)咖祭,所以kc也是通過內(nèi)存平移8字節(jié)去查找kc_name

哪些東西在棧里 哪些在堆里

  • alloc的對象 都在

  • 指針扰她、對象中犹赖,例如person指向的空間中,person所在的空間在棧中

  • 臨時變量

  • 屬性值董瞻,屬性隨對象是在

注意:

  • 是從小到大寞蚌,即低地址->高地址
  • 棧是從大到小,即從高地址->低地址分配
*   函數(shù)隱藏參數(shù)會`從前往后`一直壓钠糊,即 `從高地址->低地址 開始入棧`挟秤,
    
    
*   結(jié)構(gòu)體內(nèi)部的成員是`從低地址->高地址`
  • 一般情況下,內(nèi)存地址有如下規(guī)則
*   `0x60` 開頭表示在 `堆`中
    
    
*   `0x70` 開頭的地址表示在 `棧`中
    
    
*   `0x10` 開頭的地址表示在`全局區(qū)域`中

以上就是全部的內(nèi)容了抄伍,如有錯誤艘刚,還望指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末截珍,一起剝皮案震驚了整個濱河市攀甚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岗喉,老刑警劉巖秋度,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钱床,居然都是意外死亡荚斯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門查牌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來事期,“玉大人,你說我怎么就攤上這事纸颜⌒谈希” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵懂衩,是天一觀的道長。 經(jīng)常有香客問我,道長浊洞,這世上最難降的妖魔是什么牵敷? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮法希,結(jié)果婚禮上枷餐,老公的妹妹穿的比我還像新娘。我一直安慰自己苫亦,他們只是感情好毛肋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屋剑,像睡著了一般润匙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唉匾,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天孕讳,我揣著相機(jī)與錄音,去河邊找鬼巍膘。 笑死厂财,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峡懈。 我是一名探鬼主播璃饱,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肪康!你這毒婦竟也來了荚恶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤梅鹦,失蹤者是張志新(化名)和其女友劉穎裆甩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體齐唆,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗤栓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了箍邮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茉帅。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锭弊,靈堂內(nèi)的尸體忽然破棺而出堪澎,到底是詐尸還是另有隱情,我是刑警寧澤味滞,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布樱蛤,位于F島的核電站钮呀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昨凡。R本人自食惡果不足惜爽醋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望便脊。 院中可真熱鬧蚂四,春花似錦、人聲如沸哪痰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晌杰。三九已至跷睦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乎莉,已是汗流浹背送讲。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惋啃,地道東北人哼鬓。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像边灭,于是被迫代替她去往敵國和親异希。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345