objc_msgSend流程分析-動態(tài)決議與消息轉(zhuǎn)發(fā)

前言

在前兩篇節(jié)我們了解了快速方法查找如果找不到,則會進入慢速查找流程柿祈,其查找流程主要為在當(dāng)前類方法列表中查找哈误,如果還是沒有找到,則去父類鏈的緩存和方法列表中查找谍夭。

  • 快速查找

objc_msgSend流程分析(快速查找)

  • 慢速查找

objc_msgSend流程分析(慢速查找)

  • 根據(jù)慢速查找找不到時黑滴,程序崩潰會出現(xiàn)+[LGPerson sayNB]: unrecognized selector sent to class 0x1000022b0 像是這樣的提示,表示方法並未實現(xiàn).

防止崩潰

  • 為了提升用戶體驗紧索,蘋果給予我們兩個建議
    • 【建議一】:動態(tài)方法決議
    • 【建議二】:消息轉(zhuǎn)發(fā)(快速轉(zhuǎn)發(fā)/慢速轉(zhuǎn)發(fā))

【建議一】:動態(tài)方法決議

  • 如果慢速查找沒有找到方法的實現(xiàn)地址袁辈,則會進入第一次動態(tài)方法決議,如下是resolveMethod_locked 的源碼實現(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 { // 對象 - 元類 晚缩,如果元類則掉用類的解析方法
        // 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
    // 如果方法解析中將期實現(xiàn)指向其他方法,則繼續(xù)走方法查找流程
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

流程圖

實例方法解析-resolveInstanceMethod

  • 針對實例方法調(diào)用時待笑,如果快速/慢速都沒有找到實例方法的實現(xiàn)地址時鸣皂,有一次的挽救機會,就是執(zhí)行動態(tài)方法決議暮蹂,由於不是元類寞缝,是一個實例方法,程式走到resolveInstanceMethod仰泻。
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // 查看是否有resolveInstanceMethod的實現(xiàn)
    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));
        }
    }
}

實例方法解析-resolveInstanceMethod源碼簡述

  • 首先在發(fā)送resolveInstanceMethod消息前荆陆,需要查找cls類中是否有該方法的實現(xiàn),即通過lookUpImpOrNil方法進入lookUpImpOrForward 慢速查找流程查找resolveInstanceMethod方法集侯。
    • 如果沒有查找到被啼,則直接返回
    • 如果查找到,則接下來進行發(fā)送resolveInstanceMethod消息
  • 再次慢速查找實例方法的實現(xiàn)棠枉,即通過lookUpImpOrNil 方法進入lookUpImpOrForward 慢速查找實例方法

避免崩潰

實例方法

  • 我們知道了在沒有找到方法的實現(xiàn)地址前浓体,程序會進到resolveInstanceMethod 這裡,我們可以透過重寫類方法resolveInstanceMethod 方式辈讶,也就是在LGPerson中重寫resolveInstanceMethod類方法汹碱,將實例方法say666的實現(xiàn)指向sayMaster方法實現(xiàn),程式碼如下所示
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來了",NSStringFromSelector(sel));
        //獲取sayMaster方法的imp
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        //獲取sayMaster的實例方法
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
       //獲取sayMaster的方法簽名
        const char *type  = method_getTypeEncoding(sayMMethod);
       //將sel的實現(xiàn)指向sayMaster
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
}

  • 打印結(jié)果如下

類方法

  • 針對類方法的防止崩潰荞估,與實例方法相似咳促,一樣使用重寫resolveClassMethod 類方法來解決崩潰的問題稚新,一樣也是在LGPerson類 中重寫此方法,並將sayNB類方法的實現(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此處傳入的cls不是類跪腹,而是元類褂删,通過objc_getMetaClass 方法獲取類的元類,因為類方法在元類中是實例方法

優(yōu)化-避免崩潰

  • 我們知道isa的走位圖冲茸,而其中方法查找流程如下
    • 實例方法:類→父類→根類→nil
    • 類方法: 元類→根元類→根類→nil
  • 上節(jié)兩種對於實例方法及類方法避免崩潰的方式屯阀,可以透過寫加寫NSObject分類 的方式進行整合,由於類方法的查找轴术,其實也是查找元類的實例方法难衰,所以我們可以在根類將兩者(要查找的實例方法以及類方法)統(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;
}

  • 這樣的統(tǒng)整也示範(fàn)了船殉,調(diào)用了類方法的動態(tài)決議蝴簇,還會調(diào)用實例對象的動態(tài)方法決議,原因如上所述,因為類方法在元類中是實例方法巢价。
  • 但是上述這樣的寫法泡态,如果經(jīng)過系統(tǒng)版次的更新 选泻,系統(tǒng)方法是有可能會被修改的苇本,所以其實我們可以依照自定義的方法名,習(xí)慣在方法名加入自定義的前綴摧冀,根據(jù)前綴判斷是否是自定義方法倍踪,統(tǒng)一處理,這樣處理屬於AOP(Aspect Oriented Programming)面向切面編程索昂,比如說可以在檢測到崩潰時pop到首頁建车,進而提升用戶體驗。
  • 在此我們在這個動態(tài)方法決議內(nèi)楼镐,先不處理,而是流到下一個環(huán)節(jié)往枷,消息轉(zhuǎn)發(fā)流程來處理框产。

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

  • 如果快速/慢速查找流程與動態(tài)方法決議,還是沒有找到方法的實現(xiàn)地址(imp)错洁,就進行的是消息轉(zhuǎn)發(fā)流程秉宿,我們透過以下方式來探討,找不到方法地址崩潰前屯碴,調(diào)用了哪些方法描睦。
  • 接著我們將使用以下兩種方式
    • 通過instrumentObjcMessageSends 方法查看log
    • 通過hopper/IDA反編譯

通過instrumentObjcMessageSends 方法查看log

  • 查找流程lookUpImpOrForwardlog_and_fill_cachelogMessageSend 並且在logMessageSend下找到instrumentObjcMessageSends
  • 如下為instrumentObjcMessageSends 的源碼實現(xiàn)
void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

  • 接下來,我們打算在main中調(diào)用instrumentObjcMessageSends 导而,打印出log信息忱叭,需要完成 下列動作隔崎。
    • 傳入flag為YES,也就是objcMsgLogEnabled韵丑,即調(diào)用instrumentObjcMessageSends 爵卒,傳入?yún)?shù)為YES。
    • 需要使用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 目錄陌僵,發(fā)現(xiàn)有msgSends開頭的日誌文件轴合,打開查看在程式崩潰前,執(zhí)行了以下方法
    • 兩次動態(tài)方法決議:resolveInstanceMethod方法
    • 兩次消息快速轉(zhuǎn)發(fā):forwardingTargetForSelector方法
    • 兩次消息慢速轉(zhuǎn)發(fā):methodSignatureForSelectorresolveInstanceMethod

通過反彙編-Hopper/IDA反編譯

  • Hopper與IDA是逆向工程時靜態(tài)分析常用的工具碗短,將可執(zhí)行文件經(jīng)由反彙編工具(Hopper/IDA)反彙編程彙編語言受葛,透過工具還能將其轉(zhuǎn)換為偽代碼,控制流程圖豪椿。

程式碼編譯為彙編語言在編譯成機器語言(可執(zhí)行文件)奔坟,最後由計算器運行

  • 可以看到___forwarding___來自CoreFoundation
  • 利用image list調(diào)用讀取鏡相文件,然後搜索CoreFoundation 搭盾,查看執(zhí)行路徑
  • 通過文件路徑咳秉,找到CoreFoundation可執(zhí)行文件 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
  • 將可執(zhí)行文件,拖入hopper開啟
  • 通過左側(cè)的搜索框搜索_ forwarding_prep_0鸯隅,然後選擇偽代碼
  • 以下是__forwarding_prep_0 ___ 的彙編偽代碼澜建,跳轉(zhuǎn)至___forwarding___
  • 以下是___forwarding___ 的偽代碼實現(xiàn),首先是查看是否實現(xiàn) forwardingTargetForSelector 方法蝌以,如果沒有響應(yīng).跳轉(zhuǎn)

偽代碼-forwardingTargetForSelector

  • 如果沒有響應(yīng)炕舵,跳轉(zhuǎn)至loc_64b9b ,則直接報錯
  • 如果獲取methodSignatureForSelector方法簽名 為nil(if (rax == 0x0) goto loc_6501c;)跟畅,也是直接報錯if (strncmp(r13, "*NSZombie*", 0xa) == 0x0) goto loc_64fa1;
  • 如果methodSignatureForSelector 返回值不為空咽筋,則在forwardInvocation 方法中對invocation 進行處理

偽代碼-forwardInvocation

  • 所以,通過上面兩種查找方式可以驗證徊件,消息轉(zhuǎn)發(fā)的方法有3個
    • 【快速轉(zhuǎn)發(fā)】forwardingTargetForSelector
    • 【慢速轉(zhuǎn)發(fā)】
      • methodSignatureForSelector
      • forwardInvocation

綜上所述奸攻,消息轉(zhuǎn)發(fā)流程圖如下

  • 消息轉(zhuǎn)發(fā)的處理主要分為兩個部分:
    • 【快速轉(zhuǎn)發(fā)】當(dāng)慢速查找,以及動態(tài)方法決議均沒有找到方法實現(xiàn)時虱痕,進行消息轉(zhuǎn)發(fā)睹耐,首先是進行快速消息轉(zhuǎn)發(fā) 即走到forwardingTargetForSelector 方法
      • 如果返回消息接收者 ,在消息接收者中還是沒有找到部翘,則進入另一個 方法的查找流程
      • 如果返回nil硝训,則進入慢速轉(zhuǎn)發(fā)
    • 【慢速轉(zhuǎn)發(fā)】執(zhí)行到methodSignatureForSelector 方法
      • 如過返回的方法簽名為nil,則直接崩潰報錯
      • 如果返回的方法簽名不為nil,走到forwardInvocation 方法中窖梁,對invocation事務(wù)進行處理赘风,如果不處理也不會報錯

【建議二 】快速轉(zhuǎn)發(fā)

  • 針對前文解決崩潰問題,如果動態(tài)方法也沒有找到實現(xiàn)窄绒,則需要在LGPerson中重寫forwardingTargetForSelector 方法贝次,將LGPerson 的實力方法的接收者指定為LGStudent對象(LGStudent類中有say666的具體實現(xiàn)),如下所示
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));

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

執(zhí)行結(jié)果如下

  • 也可以直接不指定消息接收者蛔翅,直接調(diào)用父類的該方法,如果還是沒有找到位谋,則直接報錯

【建議三】慢速轉(zhuǎn)發(fā)

  • 針對第二次機會(快速轉(zhuǎn)發(fā))中還是沒有找到山析,則進入最後的一次挽救機會,即在LGPerson 中重寫methodSignatureForSelector 掏父,如下所示
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

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

  • 打印結(jié)果如下笋轨,發(fā)現(xiàn)forwardInvocation方法中不對invocation進行處理,也不會崩潰報錯
  • 另外也可以處理invocation事務(wù)赊淑,如下所示爵政,修改invocationtarget[LGStudent alloc] ,調(diào)用[anInvocation invoke] 觸發(fā)陶缺,即LGPerson 類的say666實例方法的調(diào)用會調(diào)用LGStudentsay666 方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    anInvocation.target = [LGStudent alloc];
    [anInvocation invoke];
}

打印結(jié)果如下

  • 所以钾挟,由上述可知,無論在forwardInvocation 方法中是否處裡invocation事務(wù)饱岸,程序都不會崩潰

問題探索

動態(tài)方法為何執(zhí)行兩次掺出?

  • 以下使用兩種方式分析動態(tài)方法決議
    • 通過偽代碼驗證
    • 通過程式推導(dǎo)

通過偽代碼+調(diào)適驗證

  • 首先LGPerson類裡面只有聲明,沒有say666的實現(xiàn)苫费,通過慢速查找流程我們知道方法執(zhí)行是通過lookUpImpOrForward --> resolveMethod_locked --> resolveInstanceMethod汤锨,來到resolveInstanceMethod源碼,在源碼中通過發(fā)送resolve_sel消息觸發(fā)百框,如下所示
  • 所以可以在resolveInstanceMethod 方法中IMP imp = lookUpImpOrNil(inst, sel, cls); 處加一個斷點闲礼,通過bt打印堆棧信息來看到底發(fā)生了什麼

  • resolveInstanceMethod 方法中IMP imp = lookUpImpOrNil(inst, sel, cls); 處加一個斷點,運行程序铐维,直到第一次“來了” ,我們透過lldb調(diào)適輸入bt打印堆棧信息柬泽,此時的sel是say666

  • 過掉斷點繼執(zhí)行到第二次的“來了” ,查看堆棧信息方椎,在第二次中聂抢,我們可以看到是通過CoreFoundation[NSObject(NSObject) methodSignatureForSelector:] 方法钧嘶,然後通過class_getInstanceMethod 再次進入動態(tài)方法決議棠众。
  • 通過上一步的堆棧信息,我們需要去看看CoreFoundation中到底做了什麼?通過Hopper 反編譯彙編CoreFoundation 的可執(zhí)行文件,查看methodSignatureForSelector 方法的偽代碼闸拿。
  • 接著我們透過hopper反編譯CoreFoundation 的可執(zhí)行文件空盼,開啟偽代碼模式,搜尋methodSignatureForSelector 新荤。
  • 通過methodSignatureForSelector 偽代碼進入___methodDescriptionForSelector 的實現(xiàn)揽趾。
  • 進入___methodDescriptionForSelector的偽代碼實現(xiàn),結(jié)合彙編的堆棧打印苛骨,可以看到篱瞎,在___methodDescriptionForSelector 這個方法中調(diào)用了objc4-781class_getInstanceMethod
  • 在objc中的源碼搜索class_getInstanceMethod ,其源碼實現(xiàn)如下所示
  • 這一點可以通過代碼調(diào)適來驗證痒芝,如下所示俐筋,在class_getInstanceMethod 方法處加一個斷點,在執(zhí)行了methodSignatureForSelector 方法後严衬,返回了簽名.說明方法簽名是生效的澄者,蘋果在走到invocation之前,給了開發(fā)者一次機會再去查詢请琳,所以走到class_getInstanceMethod 這裏粱挡,又去走了一遍方法查詢say666,然後會再次走到動態(tài)方法決議
  • 所以,上述的分析也印證了前文中resolveInstanceMethod 方法執(zhí)行了兩次的原因

通過程式推導(dǎo)

如果LGPerson類中重寫resolveInstanceMethod方法俄精,並加上class_addMethod操作即賦值IMP询筏,此時resolveInstanceMethod會走兩次嗎?

  • 答案是只走一次嘀倒,如果賦值了IMP屈留,動態(tài)方法決議只會走一次,說明不是在這裡走第二次動態(tài)方法決議
  • 排除掉resolveInstanceMethod 方法中的賦值IMP测蘑,在LGPerson類中重寫forwardingTargetForSelector 灌危,並指定返回值為[LGStudent alloc] ,重新運行碳胳,如果resolveInstanceMethod 打印兩次勇蝙,說明是在forwardingTargetForSelector 方法之前執(zhí)行了動態(tài)方法決議,反之在forwardingTargetForSelector方法之後.
  • 如圖可以看到 第二次動態(tài)法決議在methodSignatureForSelectorforwardInvocation 方法之間

  • 經(jīng)過上面的論證挨约,我們了解到其實在慢速消息轉(zhuǎn)發(fā)流程中味混,在methodSignatureForSelectorforwardInvocation 方法之間還有一次動態(tài)方法決議,即蘋果再次給的一個機會诫惭,如下圖所示

總結(jié)

當(dāng)進行objc_msgSend發(fā)送消息時

  • 首先進行快速查找流程翁锡,在類的緩存cache中查找指定方法的實現(xiàn)
  • 如果緩存沒有查找到,則進行慢速查找流程夕土,會先在類的方法列表中查找馆衔,如果還是沒有找到瘟判,則去父類鏈的緩存和方法列表中查找
  • 如果慢速差找也沒有找到,第一次的機會是動態(tài)方法決議角溃,也就是重寫resolveInstanceMethod/resolveClassMethod 方法
  • 如果動態(tài)方法決議還是沒有找到拷获,則進行消息轉(zhuǎn)發(fā),消息轉(zhuǎn)發(fā)有兩次機會:快速轉(zhuǎn)發(fā)+慢速轉(zhuǎn)發(fā)
  • 如果轉(zhuǎn)發(fā)後也沒有則報錯崩潰unrecognized selector sent to instance
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末减细,一起剝皮案震驚了整個濱河市匆瓜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌未蝌,老刑警劉巖驮吱,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萧吠,居然都是意外死亡糠馆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門怎憋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來又碌,“玉大人,你說我怎么就攤上這事绊袋”显龋” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵癌别,是天一觀的道長皂岔。 經(jīng)常有香客問我,道長展姐,這世上最難降的妖魔是什么躁垛? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮圾笨,結(jié)果婚禮上教馆,老公的妹妹穿的比我還像新娘。我一直安慰自己擂达,他們只是感情好土铺,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著板鬓,像睡著了一般悲敷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俭令,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天后德,我揣著相機與錄音,去河邊找鬼抄腔。 笑死瓢湃,一個胖子當(dāng)著我的面吹牛窟赏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箱季,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棍掐!你這毒婦竟也來了藏雏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤作煌,失蹤者是張志新(化名)和其女友劉穎掘殴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粟誓,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡奏寨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹰服。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病瞳。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悲酷,靈堂內(nèi)的尸體忽然破棺而出套菜,到底是詐尸還是另有隱情,我是刑警寧澤设易,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布逗柴,位于F島的核電站,受9級特大地震影響顿肺,放射性物質(zhì)發(fā)生泄漏戏溺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一屠尊、第九天 我趴在偏房一處隱蔽的房頂上張望旷祸。 院中可真熱鬧,春花似錦讼昆、人聲如沸肋僧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌吠。三九已至,卻和暖如春掺炭,著一層夾襖步出監(jiān)牢的瞬間辫诅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工涧狮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炕矮,地道東北人么夫。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像肤视,于是被迫代替她去往敵國和親档痪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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