iOS - 消息流程之動態(tài)方法決議 & 消息轉(zhuǎn)發(fā)

在前面兩篇文章iOS- 消息流程之快速查找iOS- 消息流程之慢速查找中厅篓,分別分析了objc_msgSend快速查找慢速查找

在這兩種都沒找到方法實(shí)現(xiàn)的情況下饲漾,蘋果給了兩個(gè)建議

  • 動態(tài)方法決議:慢速查找流程未找到后修陡,會執(zhí)行一次動態(tài)方法決議
  • 消息轉(zhuǎn)發(fā):如果動態(tài)方法決議仍然沒有找到實(shí)現(xiàn)押桃,則進(jìn)行消息轉(zhuǎn)發(fā)

如果這兩個(gè)建議都沒有做任何操作兔乞,就會報(bào)我們?nèi)粘i_發(fā)中常見的方法未實(shí)現(xiàn)崩潰報(bào)錯(cuò)半夷,其步驟如下

  • 定義LGPerson類机杜,其中say666實(shí)例方法 和 sayNB類方法均沒有實(shí)現(xiàn)

  • main中 分別調(diào)用LGPerson的實(shí)例方法say666 和類方法sayNB客情,運(yùn)行程序其弊,均會報(bào)錯(cuò),提示方法未實(shí)現(xiàn)膀斋,如下所示

  • 調(diào)用類方法sayNB的報(bào)錯(cuò)結(jié)果

    image.png

方法未實(shí)現(xiàn)報(bào)錯(cuò)源碼

根據(jù)慢速查找的源碼梭伐,我們發(fā)現(xiàn),其報(bào)錯(cuò)最后都是走到__objc_msgForward_impcache方法仰担,以下是報(bào)錯(cuò)流程的源碼

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

//??
ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward
  • 匯編實(shí)現(xiàn)中查找__objc_forward_handler糊识,并沒有找到,在源碼中去掉一個(gè)下劃線進(jìn)行全局搜索_objc_forward_handler摔蓝,有如下實(shí)現(xiàn)赂苗,本質(zhì)是調(diào)用的objc_defaultForwardHandler方法
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

下面,我們來講講如何在崩潰前贮尉,如何操作拌滋,可以防止方法未實(shí)現(xiàn)的崩潰。

三次方法查找的挽救機(jī)會

根據(jù)蘋果的兩個(gè)建議绘盟,我們一共有三次挽救的機(jī)會:

  • 【第一次機(jī)會】動態(tài)方法決議

  • 消息轉(zhuǎn)發(fā)流程

    • 【第二次機(jī)會】快速轉(zhuǎn)發(fā)
    • 【第三次機(jī)會】慢速轉(zhuǎn)發(fā)
【第一次機(jī)會】動態(tài)方法決議

慢速查找流程未找到方法實(shí)現(xiàn)時(shí)鸠真,首先會嘗試一次動態(tài)方法決議,其源碼實(shí)現(xiàn)如下:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    //對象 -- 類
    if (! cls->isMetaClass()) { //類不是元類龄毡,調(diào)用對象的解析方法
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {//如果是元類吠卷,調(diào)用類的解析方法, 類 -- 元類
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        //為什么要有這行代碼沦零? -- 類方法在元類中是對象方法祭隔,所以還是需要查詢元類中對象方法的動態(tài)方法決議
        if (!lookUpImpOrNil(inst, sel, cls)) { //如果沒有找到或者為空,在元類的對象方法解析方法中查找
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    //如果方法解析中將其實(shí)現(xiàn)指向其他方法路操,則繼續(xù)走方法查找流程
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

主要分為以下幾步

  • 判斷類是否是元類
    • 如果是疾渴,執(zhí)行實(shí)例方法的動態(tài)方法決議resolveInstanceMethod
    • 如果是元類,執(zhí)行類方法的動態(tài)方法決議resolveClassMethod屯仗,如果在元類中沒有找到或者為搞坝,則在元類的實(shí)例方法的動態(tài)方法決議resolveInstanceMethod查找,主要是因?yàn)?code>類方法在元類中是實(shí)例方法魁袜,所以還需要查找元類中實(shí)例方法的動態(tài)方法決議
  • 如果動態(tài)方法決議中桩撮,將其實(shí)現(xiàn)指向了其他方法,則繼續(xù)查找指定的imp峰弹,即繼續(xù)慢速查找lookUpImpOrForward流程

其流程如下
實(shí)例方法

針對實(shí)例方法調(diào)用店量,在快速-慢速查找沒有找到實(shí)例方法的實(shí)現(xiàn)時(shí),我們有一次挽救的機(jī)會鞠呈,即嘗試一次動態(tài)方法決議融师,由于是實(shí)例方法,所以會走到resolveInstanceMethod方法蚁吝,其源碼如下

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    
    // look的是 resolveInstanceMethod --相當(dāng)于是發(fā)送消息前的容錯(cuò)處理
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel); //發(fā)送resolve_sel消息

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    //查找say666
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

主要分為以下幾個(gè)步驟:

  • 在發(fā)送resolveInstanceMethod消息前旱爆,需要查找cls類中是否有該方法的實(shí)現(xiàn),即通過lookUpImpOrNil方法又會進(jìn)入lookUpImpOrForward慢速查找流程查找resolveInstanceMethod方法
    • 如果沒有灭将,則直接返回
    • 如果疼鸟,則發(fā)送resolveInstanceMethod消息
  • 再次慢速查找實(shí)例方法的實(shí)現(xiàn),即通過lookUpImpOrNil方法又會進(jìn)入lookUpImpOrForward慢速查找流程查找實(shí)例方法

崩潰修改

所以庙曙,針對實(shí)例方法say666未實(shí)現(xiàn)的報(bào)錯(cuò)崩潰空镜,可以通過在類中重寫resolveInstanceMethod類方法,并將其指向其他方法的實(shí)現(xiàn)捌朴,即在LGPerson中重寫resolveInstanceMethod類方法吴攒,將實(shí)例方法say666的實(shí)現(xiàn)指向sayMaster方法實(shí)現(xiàn),如下所示

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來了", NSStringFromSelector(sel));
        //獲取sayMaster方法的imp
        IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
        //獲取sayMaster的實(shí)例方法
        Method sayMethod  = class_getInstanceMethod(self, @selector(sayMaster));
        //獲取sayMaster的豐富簽名
        const char *type = method_getTypeEncoding(sayMethod);
        //將sel的實(shí)現(xiàn)指向sayMaster
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

重新運(yùn)行砂蔽,其打印結(jié)果如下
  • 【第一次動態(tài)決議】第一次的“來了”是在查找say666方法時(shí)會進(jìn)入動態(tài)方法決議
  • 【第二次動態(tài)決議】第二次“來了”是在慢速轉(zhuǎn)發(fā)流程中調(diào)用了CoreFoundation框架中的NSObject(NSObject) methodSignatureForSelector:后洼怔,會再次進(jìn)入動態(tài)決議
類方法

針對類方法,與實(shí)例方法類似左驾,同樣可以通過重寫resolveClassMethod類方法來解決前文的崩潰問題镣隶,即在LGPerson類中重寫該方法极谊,并將sayNB類方法的實(shí)現(xiàn)指向類方法lgClassMethod

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel == @selector(sayNB)) {
        NSLog(@"%@ 來了", NSStringFromSelector(sel));
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        Method lgClassMethod  = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        const char *type = method_getTypeEncoding(lgClassMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    
    return [super resolveClassMethod:sel];
}

resolveClassMethod類方法的重寫需要注意一點(diǎn),傳入的cls不再是安岂,而是元類轻猖,可以通過objc_getMetaClass方法獲取類的元類,原因是因?yàn)轭惙椒ㄔ谠愔惺菍?shí)例方法

優(yōu)化

上面的這種方式是單獨(dú)在每個(gè)類中重寫域那,有沒有更好的咙边,一勞永逸的方法呢膀估?其實(shí)通過方法慢速查找流程可以發(fā)現(xiàn)其查找路徑有兩條

  • 實(shí)例方法:類 -- 父類 -- 根類 -- nil
  • 類方法:元類 -- 根元類 -- 根類 -- nil
    它們的共同點(diǎn)是如果前面沒找到揭措,都會來到根類即NSObject中查找,所以我們是否可以將上述的兩個(gè)方法統(tǒng)一整合在一起呢砰嘁?答案是可以的淑蔚,可以通過NSObject添加分類的方式來實(shí)現(xiàn)統(tǒng)一處理市殷,而且由于類方法的查找,在其繼承鏈刹衫,查找的也是實(shí)例方法被丧,所以可以將實(shí)例方法類方法的統(tǒng)一處理放在resolveInstanceMethod方法中,如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來了", NSStringFromSelector(sel));
        
        IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMethod  = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type = method_getTypeEncoding(sayMethod);
        return class_addMethod(self, sel, imp, type);
    }else if (sel == @selector(sayNB)) {
        NSLog(@"%@ 來了", NSStringFromSelector(sel));
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        Method lgClassMethod  = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
        const char *type = method_getTypeEncoding(lgClassMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return NO;
}

當(dāng)然,上面這種寫法還是會有其他的問題绪妹,比如系統(tǒng)方法也會被更改甥桂,針對這一點(diǎn),是可以優(yōu)化的邮旷,即我們可以針對自定義類中方法統(tǒng)一方法名的前綴黄选,根據(jù)前綴來判斷是否是自定義方法,然后統(tǒng)一處理自定義方法婶肩,例如可以在崩潰前pop到首頁办陷,主要是用于app線上防崩潰的處理,提升用戶的體驗(yàn)律歼。

消息轉(zhuǎn)發(fā)流程

慢速查找的流程中民镜,我們了解到,如果快速+慢速沒有找到方法實(shí)現(xiàn)险毁,動態(tài)方法決議也不行制圈,就使用消息轉(zhuǎn)發(fā)跷跪,但是,我們找遍了源碼也沒有發(fā)現(xiàn)消息轉(zhuǎn)發(fā)的相關(guān)源碼甘磨,可以通過以下方式來了解庵朝,方法調(diào)用崩潰前都走了哪些方法

  • 通過instrumentObjcMessageSends方式打印發(fā)送消息的日志

  • 通過hopper/IDA反編譯

通過instrumentObjcMessageSends

通過lookUpImpOrForward --> log_and_fill_cache --> logMessageSend,在logMessageSend源碼下方找到instrumentObjcMessageSends的源碼實(shí)現(xiàn)侄旬,所以汰蜘,在main中調(diào)用
instrumentObjcMessageSends打印方法調(diào)用的日志信息色难,有以下兩點(diǎn)準(zhǔn)備工作
1尺迂、打開 objcMsgLogEnabled 開關(guān)州疾,即調(diào)用instrumentObjcMessageSends方法時(shí),傳入YES

2、在main中通過extern 聲明instrumentObjcMessageSends方法

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *person = [LGPerson alloc];
        instrumentObjcMessageSends(YES);
        [person sayHello];
        instrumentObjcMessageSends(NO);
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 通過logMessageSend源碼闸迷,了解到消息發(fā)送打印信息存儲在目錄师溅,如下所示
    /tmp/msgSends
    image.png

運(yùn)行代碼窖维,并前往/tmp/msgSends 目錄判沟,發(fā)現(xiàn)有msgSends開頭的日志文件挪哄,打開發(fā)現(xiàn)在崩潰前,執(zhí)行了以下方法

  • 兩次動態(tài)方法決議:resolveInstanceMethod方法
  • 兩次消息快速轉(zhuǎn)發(fā):forwardingTargetForSelector方法
  • 兩次消息慢速轉(zhuǎn)發(fā):methodSignatureForSelector + resolveInstanceMethod

所以,綜上所述砂碉,消息轉(zhuǎn)發(fā)整體的流程如下

消息轉(zhuǎn)發(fā)的處理主要分為兩部分:

  • 【快速轉(zhuǎn)發(fā)】當(dāng)慢速查找,以及動態(tài)方法決議均沒有找到實(shí)現(xiàn)時(shí)证舟,進(jìn)行消息轉(zhuǎn)發(fā),首先是進(jìn)行快速消息轉(zhuǎn)發(fā)窗骑,即走到forwardingTargetForSelector方法

    • 如果返回消息接收者抵知,在消息接收者中還是沒有找到,則進(jìn)入另一個(gè)方法的查找流程

    • 如果返回nil软族,則進(jìn)入慢速消息轉(zhuǎn)發(fā)

  • 【慢速轉(zhuǎn)發(fā)】執(zhí)行到methodSignatureForSelector方法

    • 如果返回的方法簽名為nil刷喜,則直接崩潰報(bào)錯(cuò)

    • 如果返回的方法簽名不為nil,走到forwardInvocation方法中立砸,對invocation事務(wù)進(jìn)行處理掖疮,如果不處理也不會報(bào)錯(cuò)

【第二次機(jī)會】快速轉(zhuǎn)發(fā)

針對前文的崩潰問題,如果動態(tài)方法決議也沒有找到實(shí)現(xiàn)颗祝,則需要在LGPerson中重寫forwardingTargetForSelector方法浊闪,將LGPerson的實(shí)例方法的接收者指定為LGStudent的對象(LGStudent類中有say666的具體實(shí)現(xiàn)),如下所示

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

//     runtime + aSelector + addMethod + imp
    //將消息的接收者指定為LGStudent螺戳,在LGStudent中查找say666的實(shí)現(xiàn)
    return [LGStudent alloc];
}

【第三次機(jī)會】慢速轉(zhuǎn)發(fā)

針對第二次機(jī)會即快速轉(zhuǎn)發(fā)中還是沒有找到搁宾,則進(jìn)入最后的一次挽救機(jī)會,即在LGPerson中重寫methodSignatureForSelector倔幼,如下所示

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
}

forwardInvocation也可以處理invocation事務(wù)盖腿,如下所示,修改invocationtarget為[LGStudent alloc]损同,調(diào)用[anInvocation invoke]觸發(fā) 即LGPerson類的say666實(shí)例方法的調(diào)用會調(diào)用LGStudent的say666方法

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    anInvocation.target = [LGStudent alloc];
    [anInvocation invoke];
}

所以奸忽,由上述可知堕伪,無論在forwardInvocation方法中是否處理invocation事務(wù),程序都不會崩潰栗菜。

動態(tài)方法決議為什么執(zhí)行兩次欠雌?

上帝視角的探索

在慢速查找流程中,我們了解到resolveInstanceMethod方法的執(zhí)行是通過lookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethod來到resolveInstanceMethod源碼疙筹,在源碼中通過發(fā)送消息觸發(fā)富俄,如下所示resolve_sel

所以可以在resolveInstanceMethod方法中IMP imp = lookUpImpOrNil(inst, sel, cls);處加一個(gè)斷點(diǎn),通過bt打印堆棧信息來看到底發(fā)生了什么

  • 運(yùn)行程序而咆,直到第一次“來了”霍比,通過bt查看第一次動態(tài)方法決議的堆棧信息,此時(shí)的sel是say666

  • 繼續(xù)往下執(zhí)行暴备,直到第二次“來了”打印悠瞬,查看堆棧信息,在第二次中涯捻,我們可以看到是通過CoreFoundation-[NSObject(NSObject) methodSignatureForSelector:]方法浅妆,然后通過class_getInstanceMethod再次進(jìn)入動態(tài)方法決議

  • 通過上一步的堆棧信息,我們需要去看看CoreFoundation中到底做了什么障癌?通過Hopper反匯編CoreFoundation的可執(zhí)行文件凌外,查看methodSignatureForSelector方法的偽代碼

  • 通過methodSignatureForSelector偽代碼進(jìn)入___methodDescriptionForSelector的實(shí)現(xiàn)

  • 進(jìn)入___methodDescriptionForSelector的偽代碼實(shí)現(xiàn),結(jié)合匯編的堆棧打印涛浙,可以看到康辑,在___methodDescriptionForSelector這個(gè)方法中調(diào)用了objc4-781class_getInstanceMethod

  • 在objc中的源碼中搜索class_getInstanceMethod,其源碼實(shí)現(xiàn)如下所示

這一點(diǎn)可以通過代碼調(diào)試來驗(yàn)證轿亮,如下所示疮薇,在class_getInstanceMethod方法處加一個(gè)斷點(diǎn),在執(zhí)行了methodSignatureForSelector方法后我注,返回了簽名惦辛,說明方法簽名是生效的,蘋果在走到invocation之前仓手,給了開發(fā)者一次機(jī)會再去查詢胖齐,所以走到class_getInstanceMethod這里,又去走了一遍方法查詢say666,然后會再次走到動態(tài)方法決議

所以嗽冒,上述的分析也印證了前文中resolveInstanceMethod方法執(zhí)行了兩次的原因

經(jīng)過上面的論證呀伙,我們了解到其實(shí)在慢速小子轉(zhuǎn)發(fā)流程中,在methodSignatureForSelectorforwardInvocation方法之間還有一次動態(tài)方法決議添坊,即蘋果再次給的一個(gè)機(jī)會剿另,如下圖所示

image.png

總結(jié)

到目前為止,objc_msgSend發(fā)送消息的流程就分析完成了,在這里簡單總結(jié)下

  • 【快速查找流程】首先雨女,在類的緩存cache中查找指定方法的實(shí)現(xiàn)

  • 【慢速查找流程】如果緩存中沒有找到谚攒,則在類的方法列表中查找,如果還是沒找到氛堕,則去父類鏈的緩存和方法列表中查找

  • 【動態(tài)方法決議】如果慢速查找還是沒有找到時(shí)馏臭,第一次補(bǔ)救機(jī)會就是嘗試一次動態(tài)方法決議,即重寫resolveInstanceMethod/resolveClassMethod 方法

  • 【消息轉(zhuǎn)發(fā)】如果動態(tài)方法決議還是沒有找到讼稚,則進(jìn)行消息轉(zhuǎn)發(fā)括儒,消息轉(zhuǎn)發(fā)中有兩次補(bǔ)救機(jī)會:快速轉(zhuǎn)發(fā)+慢速轉(zhuǎn)發(fā)

  • 如果轉(zhuǎn)發(fā)之后也沒有,則程序直接報(bào)錯(cuò)崩潰unrecognized selector sent to instance

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锐想,一起剝皮案震驚了整個(gè)濱河市帮寻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赠摇,老刑警劉巖固逗,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異藕帜,居然都是意外死亡烫罩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門耘戚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗡髓,“玉大人操漠,你說我怎么就攤上這事收津。” “怎么了浊伙?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵撞秋,是天一觀的道長。 經(jīng)常有香客問我嚣鄙,道長吻贿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任哑子,我火速辦了婚禮舅列,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卧蜓。我一直安慰自己帐要,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布弥奸。 她就那樣靜靜地躺著榨惠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赠橙,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天耽装,我揣著相機(jī)與錄音,去河邊找鬼期揪。 笑死掉奄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的横侦。 我是一名探鬼主播挥萌,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枉侧!你這毒婦竟也來了引瀑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤榨馁,失蹤者是張志新(化名)和其女友劉穎憨栽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翼虫,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屑柔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了珍剑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掸宛。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖招拙,靈堂內(nèi)的尸體忽然破棺而出唧瘾,到底是詐尸還是另有隱情,我是刑警寧澤别凤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布饰序,位于F島的核電站,受9級特大地震影響规哪,放射性物質(zhì)發(fā)生泄漏求豫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一诉稍、第九天 我趴在偏房一處隱蔽的房頂上張望蝠嘉。 院中可真熱鬧,春花似錦杯巨、人聲如沸蚤告。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罩缴。三九已至蚊逢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箫章,已是汗流浹背烙荷。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留檬寂,地道東北人终抽。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像桶至,于是被迫代替她去往敵國和親昼伴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354