objc庫源碼分析(2)-初始化

類初始化的時機(jī)

每次調(diào)用類或是對象的方法的時候叛买,會轉(zhuǎn)換成消息發(fā)送棍苹,調(diào)用類或?qū)ο蟮姆椒ㄆ鋵嵕褪墙o類或?qū)ο蟀l(fā)送消息,
發(fā)送消息是通過objc_megSend()系列的方法,然后會調(diào)用 objc-runtime-new.mm 文件里面的lookUpImpOrForward()方法斋攀,這個方法會檢測類是否是已經(jīng)初始化過的狀態(tài)(initialized),如果沒有類沒有初始化梧田,則去初始類蜻韭。
lookUpImpOrForward()方法部分代碼片段如下

 //如果類沒有初始化,則初始化類
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        //#define fastpath(x) (__builtin_expect(bool(x), 1))
        //#define slowpath(x) (__builtin_expect(bool(x), 0))
        //__builtin_expect作用是"允許程序員將最有可能執(zhí)行的分支告訴編譯器"柿扣。這個指令的寫法為:__builtin_expect(EXP, N)肖方。意思是:EXP==N的概率很大。

        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
        //如果當(dāng)前調(diào)用的方法是initialize方法未状,而且是由messenger(一般是我們自己調(diào)用)調(diào)用俯画,會調(diào)用兩次,但是第一次調(diào)用initialize就會把類標(biāo)記為initialized
    }

可以看到,當(dāng)類沒有標(biāo)記為initialized方法的時候司草,會去調(diào)用initializeAndLeaveLocked()方法, initializeAndLeaveLocked()方法最終會調(diào)用initializeAndMaybeRelock()方法

static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {//如果已經(jīng)執(zhí)行過了初始化方法艰垂,則不調(diào)用
        if (!leaveLocked) lock.unlock();
        return cls;
    }

    // Find the non-meta class for cls, if it is not already one.
    // The +initialize message is sent to the non-meta class object.
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    // Realize the non-meta class if necessary.
    if (nonmeta->isRealized()) {
        // nonmeta is cls, which was already realized
        // OR nonmeta is distinct, but is already realized
        // - nothing else to do
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        // runtimeLock is now unlocked
        // fixme Swift can't relocate the class today,
        // but someday it will:
        cls = object_getClass(nonmeta);
    }

    // runtimeLock is now unlocked, for +initialize dispatch
    ASSERT(nonmeta->isRealized());
    initializeNonMetaClass(nonmeta);//執(zhí)行初始化方法

    if (leaveLocked) runtimeLock.lock();
    return cls;
}

// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

在initializeAndMaybeRelock()方法會判斷類是否已經(jīng)初始化,如果沒有初始化埋虹,會調(diào)用initializeNonMetaClass()方法去初始化類猜憎,并標(biāo)記類為initialized


類初始化流程

從上可知,類是通過initializeNonMetaClass()去初始化一個類的搔课,initializeNonMetaClass()代碼如下


/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
 初始化類
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    //優(yōu)先執(zhí)行父類的initial方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    //設(shè)置類正在初始化胰柑,并加鎖
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);//嘗試對這個類進(jìn)行加鎖,加鎖成功后才會去設(shè)置為狀態(tài)為正在初始化
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;

            // Grab a copy of the will-initialize funcs with the lock held.
            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    
    if (reallyInitialize) {
        //正常進(jìn)入初始化
        // We successfully set the CLS_INITIALIZING bit. Initialize the class
        // Record that we're initializing this class so we can message it.
        //標(biāo)記當(dāng)前線程正在初始化該類
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        for (auto callback : localWillInitializeFuncs)
            callback.f(callback.context, cls);


        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         objc_thread_self(), cls->nameForLogging());
        }

#if __OBJC2__
        @try
#endif
        {
              //假設(shè)子類沒有實現(xiàn)initialize方法爬泥,會調(diào)用父類的initialize方法
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            //標(biāo)記初始化完成
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        //類正在初始化
        //如果該類正在初始化柬讨,而且是當(dāng)前線程在初始化,則不執(zhí)行任何操作
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            //等待線程初始化
            return;
        } else {
            //如果是別的線程正在初始化該類
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        //如果該類已經(jīng)被初始化了袍啡,就不執(zhí)行任何操作
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

在initializeNonMetaClass()的開始踩官,有一個對該方法的遞歸調(diào)用

supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }

這里的作用是當(dāng)類存在父類的時候,會去優(yōu)先初始化父類境输。當(dāng)類初始化完成的時候蔗牡,再一層一層的往下初始化類颖系。
這里有個問題,類什么時候算是初始化完成辩越?記住偷拔,當(dāng)一個類被標(biāo)記為initialized狀態(tài)的時候姑丑,類就算是初始化完成,而不是等到類的+initialize()方法執(zhí)行完才算是初始化完成。
一個類關(guān)于初始化有三個狀態(tài),未初始化豪嚎,正在初始化(nitializing)糯耍,初始化完成(initialized)。
初始化的時候會根據(jù)類的初始化狀態(tài)設(shè)置reallyInitialize變量焊刹,當(dāng)reallyInitialize變量的值為YES,就進(jìn)入到真正的初始化,調(diào)用callInitialize()方法,去調(diào)用我們定義的+ initialize()方法,然后再調(diào)用lockAndFinishInitializing()方法以清,將類設(shè)置為初始化完成狀態(tài)initialized个绍。
callInitialize()方法定義如下

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

這個方法中也是通過消息發(fā)送機(jī)制objc_msgSend去調(diào)用我們定義的+ initialize()方法隅茎。因為是通過消息發(fā)送機(jī)制去調(diào)用的,所以當(dāng)一個類沒有定義+ initialize()方法的時候篷牌,會調(diào)用父類的+ initialize()方法睡蟋,所以一個類的+ initialize()方法有可能會被調(diào)用多次。但是只有在第一次調(diào)用+ initialize()才會把類的初始化狀態(tài)設(shè)置為initialized枷颊。
執(zhí)行完以后就要通過調(diào)用lockAndFinishInitializing()設(shè)置類為initilaized戳杀,實現(xiàn)如下

static void lockAndFinishInitializing(Class cls, Class supercls)
{
    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
}

static void _finishInitializing(Class cls, Class supercls)
{
    PendingInitialize *pending;

    classInitLock.assertLocked();
    ASSERT(!supercls  ||  supercls->isInitialized());

    if (PrintInitializing) {
        _objc_inform("INITIALIZE: thread %p: %s is fully +initialized",
                     objc_thread_self(), cls->nameForLogging());
    }

    // mark this class as fully +initialized
    cls->setInitialized();//標(biāo)志為inititaled狀態(tài)
    classInitLock.notifyAll();//釋放鎖
    _setThisThreadIsNotInitializingClass(cls);
    
    // mark any subclasses that were merely waiting for this class
    if (!pendingInitializeMap) return;

    auto it = pendingInitializeMap->find(cls);
    if (it == pendingInitializeMap->end()) return;//如果pendingInitializeMap只有一個元素,則直接返回·

    pending = it->second;
    pendingInitializeMap->erase(it);

    // Destroy the pending table if it's now empty, to save memory.
    if (pendingInitializeMap->size() == 0) {
        delete pendingInitializeMap;
        pendingInitializeMap = nil;
    }

    //遞歸調(diào)用夭苗,將相同父類而且完成了初始化方法信卡,但是狀態(tài)未被標(biāo)記成Initialied的類標(biāo)記成Initialied
    while (pending) {
        PendingInitialize *next = pending->next;
        if (pending->subclass) _finishInitializing(pending->subclass, cls);
        delete pending;
        pending = next;
    }
}

static void _finishInitializingAfter(Class cls, Class supercls)
{

    classInitLock.assertLocked();

    if (PrintInitializing) {
        _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully "
                     "+initialized after superclass +[%s initialize] completes",
                     objc_thread_self(), cls->nameForLogging(),
                     supercls->nameForLogging());
    }

    if (!pendingInitializeMap) {
        pendingInitializeMap = new PendingInitializeMap{10};
        // fixme pre-size this table for CF/NSObject +initialize
    }

    PendingInitialize *pending = new PendingInitialize{cls};
    auto result = pendingInitializeMap->try_emplace(supercls, pending);
    if (!result.second) {
        pending->next = result.first->second;
        result.first->second = pending;
    }
}

lockAndFinishInitializing()方法中有一個判斷 (!supercls || supercls->isInitialized()),判斷是否不存在父類或是存在父類且父類已經(jīng)完成初始化题造。

  • 如果是傍菇,則調(diào)用_finishInitializing(),_finishInitializing()的作用是標(biāo)記類完成初始化界赔。
  • 否則調(diào)用_finishInitializingAfter()丢习。

前面不是說類的初始化initializeNonMetaClass()方法通過遞歸調(diào)用牵触,可以確保父類會優(yōu)先執(zhí)行嗎?怎么會出現(xiàn)子類執(zhí)行l(wèi)ockAndFinishInitializing()的時候父類還沒完成初始化的情況呢咐低?

其實根據(jù)面向?qū)ο蟮乃枷肜克迹割惖亩x中是不存在關(guān)于任何關(guān)于子類的內(nèi)容的,如下


一般情況

见擦。

但是有些特殊情況钉汗,會在父類的+initialize()方法中去調(diào)用子類的方法,如下


QQ20200627-213128.png

當(dāng)我們執(zhí)行[[ClassB alloc] init]方法的時候鲤屡,會想ClassB發(fā)送消息损痰,調(diào)用ClassB的initializeNonMetaClass(),接著會調(diào)用ClassA的initializeNonMetaClass(),在Class A中調(diào)用子類Class C的alloc方法执俩,就是向子類Class C發(fā)送消息,由于子類Class C還沒有初始化徐钠,所以會調(diào)用initializeNonMetaClass(),如下圖中的 調(diào)用1役首。
然后就又回遞歸調(diào)用尝丐,一直調(diào)用到ClassA的initializeNonMetaClass()方法。如下圖中的 調(diào)用2

QQ20200627-221110.png

這時候執(zhí)行到Class A的initializeNonMetaClass()方法衡奥,由于ClassA正在初始化(當(dāng)執(zhí)行第一次initializeNonMetaClass()方法的時候爹袁,會把當(dāng)前類的初始化狀態(tài)設(shè)置為),會跳到

 (cls->isInitializing()) {
        //類正在初始化
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        //如果該類正在初始化矮固,而且是當(dāng)前線程在初始化失息,則不執(zhí)行任何操作
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            //等待線程初始化
            return;
        } else {
            //如果是別的線程正在初始化該類
            // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }

然后會判斷是否是當(dāng)前線程在初始化這個類,由于在前面我們是通過遞歸調(diào)用的档址,所以是在同一個線程(為什么需要判斷是否是當(dāng)前線程盹兢?由于設(shè)計到多線程,等會繼續(xù)說一下這里的處理)守伸。所以_thisThreadIsInitializingClass()成立绎秒,就直接返回。

這時候就會回到ClassB的initializeNonMetaClass()方法 ,如下圖 調(diào)用3尼摹。這時候由于之前的ClassB沒有初始化過见芹,reallyInitialize為YES,這時候就正常進(jìn)入到初始化流程蠢涝。

QQ20200627-223549.png

先調(diào)用callInitialize()方法玄呛,然后再調(diào)用lockAndFinishInitializing()方法,這時候注意了和二,ClassA正處于 正在初始化(initializing)的階段徘铝,在調(diào)用ClassB的lockAndFinishInitializing()方法的時候,由于父類還沒初始化完成,就會_finishInitializingAfter()方法庭砍,

_finishInitializingAfter()方法不會將類的狀態(tài)設(shè)置為initalized,只會把當(dāng)前類存入到pendingInitializeMap中场晶。pendingInitializeMap是一個存放那些完成了初始化方法混埠,但是父類還沒標(biāo)記成完成初始化狀態(tài)initalized的類的容器怠缸。

ClassC也和ClassB一樣,由于ClassC的初始化狀態(tài)不是initalized钳宪,所以會執(zhí)行callInitialize()方法揭北,然后再調(diào)用_finishInitializingAfter()方法,把類B存入到pendingInitializeMap中

最后回到ClassA的initializeNonMetaClass()方法吏颖,并調(diào)用lockAndFinishInitializing()方法搔体,由于ClassA沒有父類,所以最終會調(diào)用_finishInitializing()方法半醉。_finishInitializing()方法中先是把ClassA的狀態(tài)設(shè)置為initalized疚俱。然后通過遞歸在pendingInitializeMap找到以ClassA為父類的ClassB,并把ClassB的狀態(tài)設(shè)置為initalized缩多。再把以ClassB為父類的ClassC設(shè)置為initalized呆奕。

從上面可以看出,ClassA的+initialize()方法在ClassB的+initialize()前執(zhí)行衬吆,ClassB的+initialize()方法在ClassC的+initialize()前執(zhí)行梁钾。這樣就確保了一條規(guī)則

  • 父類的+initialize()無論在什么情況下都會比子類的+initialize()優(yōu)先執(zhí)行。

但是從上面也可以看出逊抡,ClassA的+initialize()方法在還沒有執(zhí)行完畢的時候姆泻,ClassB和ClassC的+initialize()已經(jīng)執(zhí)行完畢了。所以會有以下的一條規(guī)則

  • 當(dāng)父類的+initialize()調(diào)用了子類的方法的時候冒嫡,子類的+initialize()會比父類的+initialize()方法先執(zhí)行完畢

多線程與初始化

在initializeNonMetaClass()方法中涉及到了一些線程處理拇勃,在類的初始化階段為什么需要線程操作呢?
先來看以下的情況


QQ20200627-234732.png

存在ClassA,ClassB,ClassC,ClassD,ClassE五個類孝凌,繼承關(guān)系如上圖所示
在不考慮線程處理的情況下方咆,執(zhí)行代碼

  NSThread *t1 = [[NSThread alloc] initWithBlock:^{
               [[ClassC alloc] init];
            }];
            NSThread *t2 = [[NSThread alloc] initWithBlock:^{
                [[ClassD alloc] init];
            }];
             [t2 start];
            [t1 start];
    

開啟兩個線程,初始化ClassC 和 ClassD胎许,會出現(xiàn)什么問題呢峻呛?
這個問題有可能出現(xiàn)死鎖,為什么呢辜窑?
從類的初始化 流程可知钩述,當(dāng)父類+initiale()方法中有調(diào)用子類的方法的時候,遞歸調(diào)用initializeNonMetaClass()是會出現(xiàn)環(huán)形的穆碎。

當(dāng)我們在線程1向ClassD發(fā)送消息的時候牙勘,會調(diào)用initializeNonMetaClass()方法。然后這時候另外一個線程2向ClassC發(fā)送消息,然后調(diào)用initializeNonMetaClass()了方面,ClassC的initializeNonMetaClass()方法調(diào)用了ClassB的initializeNonMetaClass(),ClassB的initializeNonMetaClass()調(diào)用了ClassA的initializeNonMetaClass()放钦。

這時候ClassA中的+initialze()方法中會向ClassE發(fā)送消息,從而調(diào)用ClassE的initializeNonMetaClass()方法恭金。而ClassE的initializeNonMetaClass()會調(diào)用ClassD的initializeNonMetaClass()方法

線程2中的ClassE需要等到線程1中的ClassD初始化完成才能繼續(xù)走下去操禀。而線程1中的ClassD需要等到線程2中的ClassB初始化完成才能走下去,而線程2中的ClassB需要等到等到ClassE中初始化完成才走下去横腿。這樣颓屑,就形成了死鎖,如下


QQ20200628-001224.png

那objc庫是怎么解決這個問題呢?objc中是通過加鎖去保證當(dāng)前線程初始化的連續(xù)性耿焊。連續(xù)性是當(dāng)一個線程對類初始化的時候揪惦,類本身和類的所有負(fù)類的初始化工作全部交由該線程處理。別的線程如果向初始化該類或是父類罗侯,都會阻塞器腋。

在不考慮線程優(yōu)先級的情況下,當(dāng)兩個不同的線程去初始化具有相同父類的類的時候钩杰,先執(zhí)行初始化流程的線程會保證初始化的連續(xù)性纫塌。為什么呢?

因為初始化方法initializeNonMetaClass()方法榜苫,會通過遞歸方式去調(diào)用父類的initializeNonMetaClass()护戳。當(dāng)遞歸條件不成立,也就是類已經(jīng)沒有父類的情況下(根類)或是父類已經(jīng)初始化完成的情況下垂睬。才會進(jìn)入到初始化的執(zhí)行階段媳荒。

void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    //優(yōu)先執(zhí)行父類的initial方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    //設(shè)置類正在初始化,并枷鎖
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);//嘗試對這個類進(jìn)行加鎖驹饺,加鎖成功后才會去設(shè)置為狀態(tài)為正在初始化
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;

            // Grab a copy of the will-initialize funcs with the lock held.
            localWillInitializeFuncs.initFrom(?willInitializeFuncs);
        }
    }

...正式進(jìn)入初始化工作...

}

從代碼的實現(xiàn)可知钳枕,在正式進(jìn)入初始化工作前,會對代碼進(jìn)行加鎖

monitor_locker_t lock(classInitLock)

如果線程一旦加鎖成功赏壹,在完成初始化并釋放掉鎖之前鱼炒。別的線程就沒有辦法去加鎖,就會阻塞蝌借,就是沒有辦法進(jìn)入初始化流程昔瞧。那么該線程就可以處理類的初始化流程并不受別的線程打斷。

換言之菩佑,就是哪個線程可以優(yōu)先使沒有初始化的父類進(jìn)入到正在初始化的階段自晰,那么該線程就會負(fù)責(zé)該類的所有負(fù)類的初始化工作,并不受別的線程影響


其他

當(dāng)線程調(diào)用初始化方法initializeNonMetaClass()并進(jìn)入reallyInitialize條件時稍坯,就會執(zhí)行callInitialize()方法酬荞,調(diào)用我們定義的+initialze()方法。

但在callInitialize()前,會執(zhí)行_setThisThreadIsInitializingClass()方法,這個方法的作用是把當(dāng)前類添加到當(dāng)前線程的_objc_initializing_classes中混巧。
_objc_initializing_classes是指該線程正在初始化的類的集合枪向。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咧党,隨后出現(xiàn)的幾起案子秘蛔,更是在濱河造成了極大的恐慌,老刑警劉巖凿傅,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缠犀,死亡現(xiàn)場離奇詭異,居然都是意外死亡聪舒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門虐急,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箱残,“玉大人,你說我怎么就攤上這事止吁”患” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵敬惦,是天一觀的道長盼理。 經(jīng)常有香客問我,道長俄删,這世上最難降的妖魔是什么宏怔? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮畴椰,結(jié)果婚禮上臊诊,老公的妹妹穿的比我還像新娘。我一直安慰自己斜脂,他們只是感情好抓艳,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帚戳,像睡著了一般玷或。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上片任,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天偏友,我揣著相機(jī)與錄音,去河邊找鬼蚂踊。 笑死约谈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棱诱,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泼橘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迈勋?” 一聲冷哼從身側(cè)響起炬灭,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靡菇,沒想到半個月后重归,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厦凤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年鼻吮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较鼓。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡椎木,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出博烂,到底是詐尸還是另有隱情香椎,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布禽篱,位于F島的核電站畜伐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏躺率。R本人自食惡果不足惜玛界,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肥照。 院中可真熱鬧脚仔,春花似錦、人聲如沸舆绎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吕朵。三九已至猎醇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間努溃,已是汗流浹背硫嘶。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留梧税,地道東北人沦疾。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓称近,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哮塞。 傳聞我的和親對象是個殘疾皇子刨秆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354