runtime1

Objective-C作為一門(mén)動(dòng)態(tài)語(yǔ)言肥印,將很多事都在運(yùn)行期間完成识椰,如消息發(fā)送、消息轉(zhuǎn)發(fā)深碱、動(dòng)態(tài)的方法交換腹鹉、對(duì)象關(guān)聯(lián)(為類(lèi)添加實(shí)例變量)、攔截系統(tǒng)自帶的方法調(diào)用(Swizzle黑魔法)敷硅、KVC功咒、KVO

我們先來(lái)探究下我們最常用的消息發(fā)送機(jī)制是如何實(shí)現(xiàn)的愉阎。

1.消息發(fā)送的基礎(chǔ)


(1)對(duì)象、類(lèi)航瞭、元類(lèi)

[receiver message];

這一類(lèi)代碼我們可以通過(guò)clang轉(zhuǎn)換為C++代碼進(jìn)行窺探

//NSString * str = [[NSString alloc] initWithFormat: @"hello world!"];
NSUInteger a = [str length];

//terminal終端輸入: clang -rewrite-objc test.m

NSUInteger a = ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)str, sel_registerName("length"));

我們看到調(diào)用了一個(gè)返回NSUInteger參數(shù)為(id, SEL)的函數(shù)诫硕,通過(guò)函數(shù)指針objc_msgSend調(diào)用。通過(guò)對(duì)參數(shù)的觀察發(fā)現(xiàn)刊侯,傳入了str對(duì)象指針章办,方法是通過(guò)對(duì)方法的字符串"length"進(jìn)行解析獲得。即轉(zhuǎn)變成的調(diào)用形式為

objc_msgSend(receiver, selector)    //無(wú)參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)  //有參數(shù)

通過(guò)匯編查看也是調(diào)用_objc_msgSend

movq    L_OBJC_SELECTOR_REFERENCES_.4(%rip), %rsi
movq    %rax, %rdi
callq   _objc_msgSend

沒(méi)有直接調(diào)用對(duì)應(yīng)的方法滨彻,而是放在了運(yùn)行時(shí)進(jìn)行查找藕届。這也是OC語(yǔ)言的動(dòng)態(tài)性所在。

我們先看參數(shù)id與SEL的意義亭饵,id是OC中的關(guān)鍵字之一休偶,作用類(lèi)似于void *,這里傳入了我們的對(duì)象指針str,顯然這是一個(gè)對(duì)象指針辜羊,OC中對(duì)id的定義可以在runtime中窺見(jiàn)

typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
private:
    isa_t isa;
public:
    //一堆方法實(shí)現(xiàn)
};

這個(gè)結(jié)構(gòu)體包含了一個(gè)isa和一堆方法實(shí)現(xiàn)踏兜,isa是isa_t的聯(lián)合體類(lèi)型,

OC所有對(duì)象都包含isa,實(shí)例對(duì)象中其指向類(lèi),類(lèi)中指向元類(lèi)拓轻。OC中類(lèi)和元類(lèi)也是對(duì)象郎任,不過(guò)只是唯一而已黍瞧。類(lèi)中存了實(shí)例對(duì)象方法,元類(lèi)中存了類(lèi)方法。具體關(guān)系如圖所示,superclass和isa構(gòu)成了整個(gè)Objective-C的對(duì)象繼承關(guān)系纳本。發(fā)送方法消息時(shí),實(shí)例對(duì)象可以通過(guò)isa找到類(lèi)對(duì)象腋颠。

對(duì)象類(lèi)元類(lèi)

Rootclass在OC中為NSObject類(lèi)繁成,繼承關(guān)系圖示非常清晰了。值得注意的便是NSObject的元類(lèi)的超類(lèi)是NSObject淑玫。

我們需要了解類(lèi)對(duì)象的定義巾腕。在runtime.h中我們可以找到類(lèi)對(duì)象的定義

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

我們?cè)谄渲锌梢钥吹筋?lèi)對(duì)象中定義如下

super_class:指明其父類(lèi)

isa:指明其元類(lèi)

methodLists :指明方法的具體實(shí)現(xiàn)

cache:緩存最近用到的方法,減少消息發(fā)送開(kāi)銷(xiāo)

protocols :要實(shí)現(xiàn)的原型列表

Ivars: 類(lèi)的實(shí)例變量

下圖展示了相關(guān)結(jié)構(gòu)體的關(guān)系與isa中結(jié)構(gòu)體bit意義


類(lèi)對(duì)象與isa
isa

ivars:該類(lèi)的對(duì)象成員變量鏈表,這里和下面的兩個(gè)結(jié)構(gòu)體都用到了變長(zhǎng)結(jié)構(gòu)體的技術(shù)混移,即通過(guò)定義一個(gè)ivar_list[1]占位符祠墅,最后alloc變長(zhǎng)堆空間時(shí)侮穿,以其為基準(zhǔn)地址進(jìn)行偏移量的增加獲得成員變量歌径。

objc_ivar_list * test = malloc(sizeof(objc_ivar_list)+sizeof(objc_ivar)*SIZE);
//然后通過(guò)test->ivar_list[i]可以直接訪問(wèn)
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;//成員變量個(gè)數(shù)
    #ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
    #endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
} 

struct objc_ivar {
    char *ivar_name                 OBJC2_UNAVAILABLE;  // 變量名
    char *ivar_type                 OBJC2_UNAVAILABLE;  // 變量類(lèi)型
    int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字節(jié)
    #ifdef __LP64__
    int space                       OBJC2_UNAVAILABLE;
    #endif
}   

methodLists:該類(lèi)的實(shí)例方法鏈表。這是一個(gè)二級(jí)指針亲茅,即指針變量當(dāng)中存的是一個(gè)地址回铛,你可以改變這個(gè)地址的值從而改變最終指向的變量狗准。可以動(dòng)態(tài)的修改*methodLists的值來(lái)添加方法茵肃。而ivars是一級(jí)指針腔长,這也是category能增加方法而無(wú)法增加實(shí)例變量的體現(xiàn)了。objc_method中包含了IMP的指針验残,IMP實(shí)際就是方法地址捞附。定義見(jiàn)下面

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
    #endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}   

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;//方法名
    char *method_types                                       OBJC2_UNAVAILABLE;//返回值和參數(shù)
    IMP method_imp                                           OBJC2_UNAVAILABLE;//實(shí)現(xiàn)
}


typedef void (*IMP)(void /* id, SEL, ... */ ); 

cache:緩存最近用到的方法,消息發(fā)送時(shí)先從cache中查找您没,沒(méi)有才會(huì)進(jìn)入方法表中查找鸟召。

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    //一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    //指定實(shí)際占用的緩存bucket的總數(shù)氨鹏。
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
    //buckets作為cache數(shù)組基址欧募,這個(gè)數(shù)組包含不超過(guò)mask+1個(gè)元素,
};

(2)消息發(fā)送


有了大致的基礎(chǔ)我們現(xiàn)在來(lái)看如何進(jìn)行消息發(fā)送

消息發(fā)送和轉(zhuǎn)發(fā)流程如圖仆抵,可略跟继,僅做備份。

消息發(fā)送與轉(zhuǎn)發(fā)路徑流程圖
<1>.objc_msgSend

參考objc-msg-x86_64.s镣丑,匯編偽指令參考匯編偽指令

首先看調(diào)用的objc_msgSend(id self, SEL _cmd,...);方法實(shí)現(xiàn)

我們看到在objc-msg-x86_64.s定義了多個(gè)msgsend函數(shù)舔糖,在編譯器消息發(fā)送時(shí),編譯器會(huì)根據(jù)不同的返回值類(lèi)型決定生成哪一個(gè)msgsend函數(shù)传轰,具體情況如下

//定義八字節(jié)數(shù)據(jù).quard偽指令剩盒,即函數(shù)地址。
.align 4
.private_extern _objc_entryPoints
_objc_entryPoints:
    .quad   _cache_getImp
    .quad   _objc_msgSend
    //Sends a message with a simple return value to an instance of a class.
    .quad   _objc_msgSend_fpret
    //a floating-point return value
    .quad   _objc_msgSend_fp2ret
    //complex long double` return only.
    .quad   _objc_msgSend_stret
    //a data-structure return value
    .quad   _objc_msgSendSuper
    //Sends a message with a simple return value to the superclass of an instance of a class.
    .quad   _objc_msgSendSuper_stret
    //a data-structure return value
    .quad   _objc_msgSendSuper2
    .quad   _objc_msgSendSuper2_stret
    .private_extern _objc_exitPoints
_objc_exitPoints:
    .quad   LExit_cache_getImp
    .quad   LExit_objc_msgSend
    .quad   LExit_objc_msgSend_fpret
    .quad   LExit_objc_msgSend_fp2ret
    .quad   LExit_objc_msgSend_stret
    .quad   LExit_objc_msgSendSuper
    .quad   LExit_objc_msgSendSuper_stret
    .quad   LExit_objc_msgSendSuper2
    .quad   LExit_objc_msgSendSuper2_stret
    .quad   0

我們這次只看第一個(gè)慨蛙,其他代碼結(jié)構(gòu)是類(lèi)似的辽聊。

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd,...);
 *
 ********************************************************************/
    
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0

    ENTRY   _objc_msgSend
    MESSENGER_START

    NilTest NORMAL

    GetIsaFast NORMAL       // r11 = self->isa
    CacheLookup NORMAL      // calls IMP on success
    /*
    CacheLookup實(shí)現(xiàn)中會(huì)判斷調(diào)用IMP
    4:  ret
    .elseif $0 == NORMAL  ||  $0 == FPRET  ||  $0 == FP2RET
    // eq already set for forwarding by `jne`
    MESSENGER_END_FAST
    jmp *8(%r10)        // call imp
    */

    NilTestSupport  NORMAL

    GetIsaSupport   NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r11
    MethodTableLookup %a1, %a2  // r11 = IMP
    cmp %r11, %r11      // set eq (nonstret) for forwarding
    jmp *%r11           // goto *imp

    END_ENTRY   _objc_msgSend
    
    ENTRY _objc_msgSend_fixup   //int3斷點(diǎn)
    int3
    END_ENTRY _objc_msgSend_fixup

    
    STATIC_ENTRY _objc_msgSend_fixedup
    // Load _cmd from the message_ref
    movq    8(%a2), %a2
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_fixedup

根據(jù)注解我們可以看到,objc_msgSend首先調(diào)用GetIsaFast將isa賦值到R11寄存器中期贫,然后通過(guò)調(diào)用CacheLookup進(jìn)行cache查找跟匆,如果找到則直接調(diào)用IMP指向的方法。

如果沒(méi)有找到則通過(guò)MethodTableLookup方法進(jìn)行查找方法表通砍,動(dòng)態(tài)解析也在這一步中添加玛臂。

/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup classRegister, selectorRegister
//
// Takes:   $0 = class to search (a1 or a2 or r10 ONLY)
//      $1 = selector to search for (a2 or a3 ONLY)
//      r11 = class to search
//
// On exit: imp in %r11
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup

    MESSENGER_END_SLOW
    
    SaveRegisters

    // _class_lookupMethodAndLoadCache3(receiver, selector, class)

    movq    $0, %a1
    movq    $1, %a2
    movq    %r11, %a3
    call    __class_lookupMethodAndLoadCache3

    // IMP is now in %rax
    movq    %rax, %r11

    RestoreRegisters

.endmacro

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

其中調(diào)用了_class_lookupMethodAndLoadCache3(receiver, selector, class)參數(shù)分別是對(duì)象,方法封孙,類(lèi)對(duì)象迹冤。

并將 IMP 返回(從 rax 挪到 r11)。最后在 objc_msgSend 中調(diào)用 IMP方法虎忌。

那么查找路徑顯然在lookUpImpOrForward里面

<2>. lookUpImpOrForward 快速查找 IMP

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

我們傳入了

return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);

先判斷類(lèi)是否時(shí)第一次用到泡徙,是否要初始化,然后進(jìn)行初始化膜蠢。retray中開(kāi)始進(jìn)行標(biāo)準(zhǔn)的方法表查找

過(guò)程如下:

  1. 查找類(lèi)中的cache是否緩存該方法堪藐。IMP非nil莉兰,命中緩存則跳轉(zhuǎn)到done;否則繼續(xù)
  2. 嘗試查找類(lèi)的方法表礁竞,調(diào)用getMethodNoSuper_nolock(cls, sel)查找Method糖荒。如果search_method_list有序則采用二分法查找,無(wú)序則順序查找模捂。如果找到Method捶朵,存cache,賦值imp=Method->imp,跳轉(zhuǎn)到done狂男;否則繼續(xù)
  3. 繼承層級(jí)中遞歸向超類(lèi)查找泉孩。先查cache。命中緩存并淋,找到imp寓搬,則跳到4;沒(méi)找到則跳5
  4. 其不是_objc_msgForward_impcache(消息轉(zhuǎn)發(fā)函數(shù))則寫(xiě)入類(lèi)的cache县耽,跳轉(zhuǎn)到done句喷;否則遇到消息轉(zhuǎn)發(fā)的標(biāo)記,終止遞歸查找兔毙,跳6
  5. 嘗試查找超類(lèi)的方法表唾琼,調(diào)用getMethodNoSuper_nolock(curClass, sel)查找Method。如果search_method_list有序則采用二分法查找澎剥,無(wú)序則順序查找锡溯。如果找到Method,存cache哑姚,賦值imp=Method->imp,跳轉(zhuǎn)到done祭饭;否則跳3
  6. 動(dòng)態(tài)方法解析,這步是消息轉(zhuǎn)發(fā)前的最后一次機(jī)會(huì)叙量。此時(shí)釋放讀入鎖(runtimeLock.unlockRead())倡蝙,接著間接地發(fā)送 +resolveInstanceMethod+resolveClassMethod 消息動(dòng)態(tài)添加imp方法。完成后回到1查找imp绞佩,否則將_objc_msgForward_impcache當(dāng)做 IMP 并寫(xiě)入緩存寺鸥,進(jìn)入done
  7. done:讀操作解鎖,并將之前找到的 IMP 返回品山。這里的imp有可能是找到的方法地址胆建,也可能是_objc_msgForward_impcache方法轉(zhuǎn)發(fā)函數(shù)地址。

通過(guò)這一過(guò)程消息發(fā)送找到imp肘交,或者返回消息轉(zhuǎn)發(fā)函數(shù)的地址笆载。在這一步消息機(jī)制結(jié)束,

jmp *%r11           // goto *imp

要么執(zhí)行方法,要么進(jìn)入消息轉(zhuǎn)發(fā)宰译。

<3>.動(dòng)態(tài)解析的使用

方法表的遞歸查找失敗時(shí)會(huì)進(jìn)行動(dòng)態(tài)解析,動(dòng)態(tài)解析失敗則進(jìn)入方法轉(zhuǎn)發(fā)魄懂。動(dòng)態(tài)解析會(huì)給我們?nèi)螜C(jī)會(huì)沿侈。

  • Method resolution
  • Fast forwarding
  • Normal forwarding

具體參考Objective-C Runtime 之動(dòng)態(tài)方法解析實(shí)踐

具體將會(huì)在后面文章闡述

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市市栗,隨后出現(xiàn)的幾起案子缀拭,更是在濱河造成了極大的恐慌,老刑警劉巖填帽,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛛淋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡篡腌,警方通過(guò)查閱死者的電腦和手機(jī)褐荷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嘹悼,“玉大人叛甫,你說(shuō)我怎么就攤上這事⊙罨铮” “怎么了其监?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)限匣。 經(jīng)常有香客問(wèn)我抖苦,道長(zhǎng),這世上最難降的妖魔是什么米死? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任锌历,我火速辦了婚禮,結(jié)果婚禮上峦筒,老公的妹妹穿的比我還像新娘辩涝。我一直安慰自己,他們只是感情好勘天,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布怔揩。 她就那樣靜靜地躺著,像睡著了一般脯丝。 火紅的嫁衣襯著肌膚如雪商膊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天宠进,我揣著相機(jī)與錄音晕拆,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛实幕,可吹牛的內(nèi)容都是我干的吝镣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼昆庇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼末贾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起整吆,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拱撵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后表蝙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拴测,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年府蛇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了集索。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汇跨,死狀恐怖抄谐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扰法,我是刑警寧澤蛹含,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站塞颁,受9級(jí)特大地震影響浦箱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祠锣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一酷窥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伴网,春花似錦蓬推、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至动分,卻和暖如春毅糟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澜公。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工姆另, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓迹辐,卻偏偏與公主長(zhǎng)得像蝶防,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子明吩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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