iOS之方法調(diào)用

源碼
message.h
message匯編源碼
runtime源碼

參考

在iOS中手形,方法調(diào)用過(guò)程分三步

  • 消息發(fā)送:從類及父類的緩存列表以及方法列表查找方法库糠。

  • 動(dòng)態(tài)解析:如果消息發(fā)送階段沒(méi)有找到方法,則會(huì)進(jìn)入動(dòng)態(tài)解析階段瞬欧,負(fù)責(zé)動(dòng)態(tài)的添加方法實(shí)現(xiàn)。

  • 消息轉(zhuǎn)發(fā):如果也沒(méi)有實(shí)現(xiàn)動(dòng)態(tài)解析方法唉侄,則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段属划,將消息轉(zhuǎn)發(fā)給可以處理消息的接受者來(lái)處理贬墩。

如果消息轉(zhuǎn)發(fā)也沒(méi)有實(shí)現(xiàn),則會(huì)拋出常見的異常unrecognzied selector sent to instance

消息發(fā)送流程.png

我們先來(lái)去掉方法調(diào)用的偽裝,在main.m文件中創(chuàng)建一個(gè)Animal類唠粥,添加一個(gè)run實(shí)例方法
在main函數(shù)中

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

    Animal *animal = [[Animal alloc] init];
    [animal run];
    
    return 0;
}

通過(guò)xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令轉(zhuǎn)成C++代碼main.cpp文件晤愧,進(jìn)入文件后定位到main函數(shù)可以看到

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

    Animal *animal = ((Animal *(*)(id, SEL))(void *)objc_msgSend)((id)((Animal *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animal"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)animal, sel_registerName("run"));

    return 0;
}

通過(guò)上面源碼可以很清楚的看到C++底層將方法調(diào)用最終轉(zhuǎn)換為調(diào)用objc_msgSend,并且傳入?yún)?shù)(id)animal(消息接受者self),sel_registerName("run")(消息名_cmd)舅巷,因此OC的方法調(diào)用也稱為消息機(jī)制钠右,表示給方法調(diào)用者發(fā)送消息飒房。

接下來(lái)我們慢慢剖析一個(gè)消息發(fā)送的完整過(guò)程狠毯。

Step 1.1垃你、 消息發(fā)送-緩存列表查找

Objective-C中方法調(diào)用從objc_msgSend開始就正式開始,objc_msgSend的申明定義在源碼message.h中少辣,實(shí)現(xiàn)是在Messengers.subproj/這個(gè)匯編文件夾中锨亏,以arm64的匯編源碼為例(這里就只貼關(guān)鍵的代碼)

_objc_msgSend,對(duì)消息接受者判空,是則進(jìn)入LReturnZero返回nil反浓,否則進(jìn)入CacheLookup檢查緩存

ENTRY _objc_msgSend
...
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged  // 判空
#else
    b.eq    LReturnZero // 不為空則進(jìn)入LGetIsaDone
#endif
......
LGetIsaDone:
    CacheLookup NORMAL // 開始查找
......

CacheLookup開始查找緩存,查到緩存進(jìn)入CacheHit并返回imp,否則進(jìn)入CheckMiss

.macro CacheLookup
......
CacheHit $0
......
CheckMiss
.endmacro

CheckMiss根據(jù)傳入?yún)?shù)跳轉(zhuǎn)到__objc_msgSend_uncached or __objc_msgLookup_uncached

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

__objc_msgSend_uncached調(diào)用MethodTableLookup進(jìn)入方法列表查找

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup  // 這里是MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

MethodTableLookup內(nèi)部邏輯進(jìn)入方法列表查找的核心函數(shù)__class_lookupMethodAndLoadCache3,該方法在runtime.m文件中實(shí)現(xiàn)

.macro MethodTableLookup
......
    bl  __class_lookupMethodAndLoadCache3
......
.endmacro

進(jìn)入到__class_lookupMethodAndLoadCache3后我們接下來(lái)看1.2的方法列表查找過(guò)程。

Step 1.2、 消息發(fā)送-方法列表查找

從核心函數(shù)_class_lookupMethodAndLoadCache3開始

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj,
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 已經(jīng)在1.1的緩存中已經(jīng)查找過(guò)缔俄,所以傳入的NO不再進(jìn)行緩存查找
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }


    runtimeLock.lock();
    checkIsKnownClass(cls);
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // 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.assertLocked();

    // 嘗試查找一次緩存蟹略,如果找到直接返回
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 傳入對(duì)象和方法名状婶,在方法列表中獲取
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 查找到后進(jìn)行緩存草姻,并直接返回
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 依次遍歷父類的方法列表中或緩存進(jìn)行查找
    {
        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.");
            }
            
            // 父類緩存
            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;
                }
            }
            
            // 父類的方法列表
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 未找到imp跌榔,如果傳入resolver為YES且triedResolver為NO(未解析過(guò))則開始動(dòng)態(tài)解析過(guò)程
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        // 開始動(dòng)態(tài)解析
        resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // 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;
    }

    // 未找到imp且動(dòng)態(tài)解析也未解析到,則開始消息轉(zhuǎn)發(fā)
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}

realizeClass是初始化了很多數(shù)據(jù)示绊,包括cls->ro賦值給cls->rw取胎,添加元類version為7,cls->chooseClassArrayIndex()設(shè)置cls的索引匪傍,supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA()))初始化superclasscls->isa,后邊針對(duì)沒(méi)有優(yōu)化的結(jié)構(gòu)進(jìn)行賦值這里不多講薪棒,然后協(xié)調(diào)實(shí)例變量偏移布局棵介,設(shè)置cls->setInstanceSize,拷貝flagsrorw中扣蜻,然后添加subclassrootclass,最后添加類別的方法芳肌,協(xié)議亿笤,和屬性汪榔。

getMethodNoSuper_nolock在類對(duì)象的方法列表中查找

getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    // cls->data() 得到的是 class_rw_t
    // class_rw_t->methods 得到的是methods二維數(shù)組
    for (auto mlists = cls->data()->methods.beginLists(),
              end = cls->data()->methods.endLists();
         mlists != end;
         ++mlists)
    {
         // mlists 為 method_list_t
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }
    return nil;
}

search_method_list

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
   int methodListIsFixedUp = mlist->isFixedUp();
   int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
   // 如果方法列表是有序的,則使用效率較高的二分法查找方法
   if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
       return findMethodInSortedMethodList(sel, mlist);
   } else {
       // 否則遍歷列表查找
       for (auto& meth : *mlist) {
           if (meth.name == sel) return &meth;
       }
   }
   return nil;
}

findMethodInSortedMethodList函數(shù)內(nèi)二分查找實(shí)現(xiàn)原理

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // >>1 表示將變量n的各個(gè)二進(jìn)制位順序右移1位剥悟,最高位補(bǔ)二進(jìn)制0躏尉。
    // count >>= 1 如果count為偶數(shù)則值變?yōu)?count / 2)教藻。如果count為奇數(shù)則值變?yōu)?count-1) / 2 
    for (count = list->count; count != 0; count >>= 1) {
        // probe 指向數(shù)組中間的值
        probe = base + (count >> 1);
        // 取出中間method_t的name悄窃,也就是SEL
        uintptr_t probeValue = (uintptr_t)probe->name;
        if (keyValue == probeValue) {
            // 取出 probe
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
           // 返回方法
            return (method_t *)probe;
        }
        // 如果keyValue > probeValue 則折半向后查詢
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }

    return nil;
}
_class_lookupMethodAndLoadCache3內(nèi)部流程.png

Step 2纠炮、 動(dòng)態(tài)解析

動(dòng)態(tài)解析階段源碼:

static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}

resolveInstanceMethod函數(shù)

static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // 未實(shí)現(xiàn)動(dòng)態(tài)解析方法SEL_resolveInstanceMethod
        return;
    }

    // 調(diào)用動(dòng)態(tài)解析方法也就是OC中的類方法"resolveInstanceMethod:"
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // 動(dòng)態(tài)解析后再次進(jìn)行查找
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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));
        }
    }
}

lookUpImpOrNil

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    // 如果是`_objc_msgForward_impcache`則說(shuō)明穷躁,動(dòng)態(tài)解析添加失敗耕肩,返回nil
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

在進(jìn)行過(guò)一次動(dòng)態(tài)解析后,通過(guò)resolvertriedResolver兩個(gè)參數(shù)的值知道折砸,無(wú)論動(dòng)態(tài)解析是否有用看疗,都不會(huì)在lookUpImpOrForward中再次進(jìn)行動(dòng)態(tài)解析。

接下來(lái)我們看一下OC中動(dòng)態(tài)解析的方法實(shí)現(xiàn)以及怎么實(shí)現(xiàn)動(dòng)態(tài)解析
動(dòng)態(tài)解析對(duì)象方法+(BOOL)resolveInstanceMethod:(SEL)sel睦授,動(dòng)態(tài)解析類方法+(BOOL)resolveClassMethod:(SEL)sel两芳。

// 這里以`resolveInstanceMethod :`舉例怎么實(shí)現(xiàn)動(dòng)態(tài)解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    if (sel == @selector(test)) {
        // 獲取實(shí)例對(duì)象方法的指針
        Method otherMethod = class_getInstanceMethod(self, @selector(run));
        // 添加到類實(shí)例對(duì)象方法列表
        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        // 是否動(dòng)態(tài)添加
        return YES;
    }

    NSLog(@"%s", __func__);
    return [super resolveInstanceMethod:sel];
}
int main(int argc, char * argv[]) {

    Animal *animal = [[Animal alloc] init];
    [animal run];
    [animal performSelector:@selector(test)];
    
    return 0;
}

animal對(duì)象調(diào)用方法test,可以看到最后調(diào)的run函數(shù)去枷,通過(guò)上面對(duì)消息發(fā)送的分析我們知道怖辆,當(dāng)本類和父類cacheclass_rw_t中都找不到方法時(shí),就會(huì)進(jìn)行動(dòng)態(tài)解析的方法删顶,也就是說(shuō)會(huì)自動(dòng)調(diào)用類的resolveInstanceMethod:方法進(jìn)行動(dòng)態(tài)查找竖螃。

需要注意Methodobjc_method結(jié)構(gòu)體,其內(nèi)部結(jié)構(gòu)同method_t(內(nèi)部包含SEL逗余、type特咆、IMP)結(jié)構(gòu)體相同,可以等同轉(zhuǎn)換

動(dòng)態(tài)解析流程.png

Step 3录粱、 消息轉(zhuǎn)發(fā)

runtime源碼中可以看出腻格,在未進(jìn)行動(dòng)態(tài)解析或者動(dòng)態(tài)解析失敗后,就會(huì)執(zhí)行imp = (IMP)_objc_msgForward_impcache;進(jìn)行消息轉(zhuǎn)發(fā)啥繁,但是此部分代碼并未開源菜职,我們可以從Objectiv-C提供的模板方法來(lái)進(jìn)行分析。

在Objectiv-C中消息轉(zhuǎn)發(fā)涉及三個(gè)函數(shù):
1.- (id)forwardingTargetForSelector:(SEL)aSelector
2.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  1. - (void)forwardInvocation:(NSInvocation *)anInvocation

如果forwardingTargetForSelector函數(shù)返回為nil或者沒(méi)有實(shí)現(xiàn)的話旗闽,就會(huì)調(diào)用methodSignatureForSelector方法酬核,用來(lái)返回一個(gè)方法簽名,這也是我們正確跳轉(zhuǎn)方法的最后機(jī)會(huì)适室。

如果methodSignatureForSelector方法返回正確的方法簽名就會(huì)調(diào)用forwardInvocation方法巾腕,forwardInvocation方法內(nèi)提供一個(gè)NSInvocation類型的參數(shù)您机,NSInvocation封裝了一個(gè)方法的調(diào)用咽安,包括方法的調(diào)用者船殉,方法名章母,以及方法的參數(shù)但狭。在forwardInvocation函數(shù)內(nèi)修改方法調(diào)用對(duì)象即可复隆。

如果methodSignatureForSelector返回的為nil迫吐,就會(huì)來(lái)到doseNotRecognizeSelector:方法內(nèi)部,程序crash提示無(wú)法識(shí)別選擇器unrecognized selector sent to instance整袁。

// 3.消息轉(zhuǎn)發(fā)階段(未進(jìn)行動(dòng)態(tài)解析或者動(dòng)態(tài)解析失敗進(jìn)入本階段)
- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(roar)) {
        
        return [[Lion alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(hunt)) {

        // 創(chuàng)建方法簽名
//        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        return [[[Lion alloc] init] methodSignatureForSelector:aSelector];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

/// 如果上一步‘methodSignatureForSelector:’返回正確的方法簽名菠齿,則執(zhí)行此步。這里需要注意修改NSInvocation的target對(duì)象坐昙,由于修改前的對(duì)象是原始調(diào)用對(duì)象未實(shí)現(xiàn)該方法绳匀,所以一直進(jìn)行消息轉(zhuǎn)發(fā),造成調(diào)用棧溢出
/// @param anInvocation anInvocation炸客,封裝了上一步分方法簽名
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSLog(@"NSInvocation original target is %@", anInvocation.target);
    Lion *target = [[Lion alloc] init];
    anInvocation.target = target;
    [anInvocation invoke];
//    [anInvocation invokeWithTarget:[[Lion alloc] init]];
    NSLog(@"NSInvocation destination target is %@", anInvocation.target);
}

同樣上述代碼疾棵,我們?yōu)閯?dòng)態(tài)轉(zhuǎn)發(fā)方法添加返回值和參數(shù),并在forwardInvocation方法中修改方法的返回值及參數(shù)痹仙。

// 3.消息轉(zhuǎn)發(fā)階段(未進(jìn)行動(dòng)態(tài)解析或者動(dòng)態(tài)解析失敗進(jìn)入本階段)
- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(roar)) {
        
        return [[Lion alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 創(chuàng)建方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(hunt)) {

        return [[[Lion alloc] init] methodSignatureForSelector:aSelector];
    }
    
    if (aSelector == @selector(offspringCountWithAge:)) {
        
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

/// 如果上一步‘methodSignatureForSelector:’返回正確的方法簽名是尔,則執(zhí)行此步。這里需要注意修改NSInvocation的target對(duì)象开仰,由于修改前的對(duì)象是原始調(diào)用對(duì)象未實(shí)現(xiàn)該方法拟枚,所以一直進(jìn)行消息轉(zhuǎn)發(fā),造成調(diào)用棧溢出
/// @param anInvocation anInvocation众弓,封裝了上一步分方法簽名
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    if (anInvocation.selector == @selector(hunt)) {
     
            NSLog(@"NSInvocation original target is %@", anInvocation.target);
            Lion *target = [[Lion alloc] init];
            anInvocation.target = target;
            [anInvocation invoke];
        //    [anInvocation invokeWithTarget:[[Lion alloc] init]];
            NSLog(@"NSInvocation destination target is %@", anInvocation.target);
    } else if (anInvocation.selector == @selector(offspringCountWithAge:)) {
     
        int age;
        // 獲取方法的參數(shù)恩溅,方法默認(rèn)還有self和cmd兩個(gè)參數(shù),因此新添加的參數(shù)下標(biāo)為2
        [anInvocation getArgument:&age atIndex:2];
        NSLog(@"offspringCountWithAge:修改前參數(shù)的值 = %d",age);
        age = 3;
        NSLog(@"offspringCountWithAge:修改后參數(shù)的值 = %d",age);
        [anInvocation setArgument:&age atIndex:2];
        [anInvocation invokeWithTarget:[[Lion alloc] init]];

        // 獲取方法的返回值
        int offspringCountWithAge;
        [anInvocation getReturnValue:&offspringCountWithAge];
        NSLog(@"offspringCountWithAge:返回值 = %d",offspringCountWithAge); // result = 220,說(shuō)明參數(shù)修改成功
        offspringCountWithAge = 100;
        [anInvocation setReturnValue:&offspringCountWithAge];
        [anInvocation getReturnValue:&offspringCountWithAge];
        NSLog(@"offspringCountWithAge:修改后返回值為 = %d",offspringCountWithAge);
    }
}
消息轉(zhuǎn)發(fā)流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谓娃,一起剝皮案震驚了整個(gè)濱河市脚乡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滨达,老刑警劉巖奶稠,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異弦悉,居然都是意外死亡窒典,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門稽莉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瀑志,“玉大人,你說(shuō)我怎么就攤上這事污秆∨恚” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵良拼,是天一觀的道長(zhǎng)战得。 經(jīng)常有香客問(wèn)我,道長(zhǎng)庸推,這世上最難降的妖魔是什么常侦? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任浇冰,我火速辦了婚禮,結(jié)果婚禮上聋亡,老公的妹妹穿的比我還像新娘肘习。我一直安慰自己,他們只是感情好坡倔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布漂佩。 她就那樣靜靜地躺著,像睡著了一般罪塔。 火紅的嫁衣襯著肌膚如雪投蝉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天征堪,我揣著相機(jī)與錄音瘩缆,去河邊找鬼。 笑死请契,一個(gè)胖子當(dāng)著我的面吹牛咳榜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播爽锥,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼涌韩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了氯夷?” 一聲冷哼從身側(cè)響起臣樱,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腮考,沒(méi)想到半個(gè)月后雇毫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踩蔚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年棚放,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馅闽。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飘蚯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出福也,到底是詐尸還是另有隱情局骤,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布暴凑,位于F島的核電站峦甩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏现喳。R本人自食惡果不足惜凯傲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一犬辰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泣洞,春花似錦忧风、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腿宰。三九已至呕诉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吃度,已是汗流浹背甩挫。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椿每,地道東北人伊者。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像间护,于是被迫代替她去往敵國(guó)和親亦渗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345