iOS開發(fā)之進(jìn)階篇(8)—— Run Loops

引用

Apple文檔 -- Run Loops
深入理解RunLoop
iOS刨根問(wèn)底-深入理解RunLoop

首先感謝ibiremeKenshinCui兩位大神對(duì)Run Loops做出的探討和總結(jié). 本文將大量地直接或間接引用自以上三個(gè)出處的內(nèi)容, 是對(duì)這三篇文章的一個(gè)理解和整合. 如需更加深入了解Run Loops, 還請(qǐng)單擊上文引用鏈接.

目錄

了解Run Loops

  • 概念
  • Run Loops與線程的關(guān)系
  • CFRunLoop相關(guān)類
  • Call out
  • 運(yùn)行流程
  • 底層實(shí)現(xiàn)
  • 什么時(shí)候使用

應(yīng)用

  • NSTimer
  • AutoreleasePool
  • UI更新
  • NSURLConnection

了解Run Loops

一般來(lái)說(shuō), 我們開辟一個(gè)線程, 在任務(wù)執(zhí)行完之后就會(huì)退出. 如果我們想反復(fù)執(zhí)行線程里的這些任務(wù), 可以使用 for / while / do while 等各種循環(huán), 例如:

int loop()
{
    initialize();

    while (1) {

        // do something

        if (quit) break;
        sleep(0.2);
    }

    return 0;
}

雖然這樣簡(jiǎn)單方便, 但會(huì)有幾個(gè)問(wèn)題:

  • 如何管理事件/消息
  • 如何讓線程在沒(méi)有處理消息時(shí)休眠以避免資源占用
  • 如何在有消息到來(lái)時(shí)立刻被喚醒

使用Run Loops能夠解決這些問(wèn)題.

概念

Run Loops (運(yùn)行循環(huán)) 是與線程相關(guān)聯(lián)的基礎(chǔ)設(shè)施的一部分, 目的是在有工作要做時(shí)讓線程忙, 而在沒(méi)有工作時(shí)讓線程進(jìn)入睡眠狀態(tài).

iOS中的 Run Loops 指的分別是 Cocoa 中的 NSRunLoop 以及 CoreFoundation 中的 CFRunLoopRef. 其中, CFRunloopRef是純C的函數(shù), 而NSRunloop僅僅是CFRunloopRef的OC封裝, 并未提供額外的其他功能, 因此本文將直接討論CF框架下的CFRunLoopRef.

NSRunLoop一般不被認(rèn)為是線程安全的, 并且它的方法只應(yīng)在當(dāng)前線程的上下文中被調(diào)用. 您永遠(yuǎn)不要嘗試在不同線程中調(diào)用同一個(gè)NSRunLoop對(duì)象的方法焦履,因?yàn)檫@樣做可能會(huì)導(dǎo)致意外結(jié)果禁添。

Run Loops與線程關(guān)系

Run Loops是用來(lái)管理線程的, 包括線程的 循環(huán)運(yùn)行 / 事件處理 / 喚醒 等等, 因此我們不能拋開線程來(lái)談Run Loops.

  • Run Loops與線程是一一對(duì)應(yīng)的, 其關(guān)系是保存在一個(gè)全局的 Dictionary 里.
  • 我們不能主動(dòng)創(chuàng)建RunLoop, 只能通過(guò)CFRunLoopGetMain() 和 CFRunLoopGetCurrent()分別獲取主線程和子線程的RunLoop.
  • 主線程中, 系統(tǒng)會(huì)自動(dòng)幫我們創(chuàng)建一個(gè)RunLoop; 而子線程中的RunLoop采用類似懶加載的機(jī)制, 即我們第一次去獲取的時(shí)候才會(huì)創(chuàng)建, 如果不調(diào)用CFRunLoopGetCurrent()獲取RunLoop, 那么就一直沒(méi)有.

這兩個(gè)函數(shù)內(nèi)部的邏輯大概是下面這樣 (CFRunloop開源):

/// 全局的Dictionary症杏,key 是 pthread_t豫尽, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問(wèn) loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
 
/// 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        // 第一次進(jìn)入時(shí)亚斋,初始化全局Dic作媚,并先為主線程創(chuàng)建一個(gè) RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    /// 直接從 Dictionary 里獲取帅刊。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
    
    if (!loop) {
        /// 取不到時(shí)纸泡,創(chuàng)建一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注冊(cè)一個(gè)回調(diào),當(dāng)線程銷毀時(shí)赖瞒,順便也銷毀其對(duì)應(yīng)的 RunLoop女揭。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
 
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
 
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

CFRunLoop相關(guān)類

  • CFRunLoopRef RunLoop的CF對(duì)象
  • CFRunLoopModeRef RunLoop的運(yùn)行模式
  • CFRunLoopSourceRef 各種輸入源 (除計(jì)時(shí)器)
  • CFRunLoopTimerRef 計(jì)時(shí)器源
  • CFRunLoopObserverRef 觀察者
CFRunLoopRef

即RunLoop的CF對(duì)象, 提供各種方法來(lái)管理RunLoop.

CFRunLoopModeRef

即CFRunLoopRef的運(yùn)行模式. 每個(gè)CFRunLoopRef可以有多個(gè)CFRunLoopModeRef, 但在同一時(shí)刻有且僅有一種CFRunLoopModeRef.
幾種常用的mode (或mode組合):

  • kCFRunLoopDefaultMode 默認(rèn)mode
  • UITrackingRunLoopMode 追蹤ScrollView滾動(dòng)時(shí)的狀態(tài)
  • kCFRunLoopCommonModes 前兩個(gè)mode的組合

ScrollView滾動(dòng)時(shí)NSTimer不工作
主線程中注冊(cè)了kCFRunLoopDefaultMode和UITrackingRunLoopMode這兩個(gè)mode. 當(dāng)有ScrollView發(fā)生滾動(dòng)時(shí), RunLoop從kCFRunLoopDefaultMode切換到UITrackingRunLoopMode, 此模式下計(jì)時(shí)器(NSTimer)將不工作.

CFRunLoopSourceRef

輸入源Source將事件異步傳遞到線程, 事件的來(lái)源取決于輸入來(lái)源的類型. 通常有兩個(gè)類別: 基于端口的輸入源自定義輸入源.

Source.jpg

圖中, 左邊是線程循環(huán), 右邊是輸入源. 輸入源1~4分別為基于端口的源, 自定義源, 選擇器源, 以及計(jì)時(shí)器源. 其中, 1和4都屬于基于端口的源, 不同的是所有的計(jì)時(shí)器源都共用一個(gè)端口“Mode Timer Port”,而每個(gè)基于端口的源都有不同的對(duì)應(yīng)端口. 2和3都屬于自定義輸入源, 可以理解為選擇器源是官方為我們定義好的自定義源.

  1. Port-Based Sources 基于端口的源
    監(jiān)視您的應(yīng)用程序的Mach端口, 由內(nèi)核自動(dòng)發(fā)出信號(hào). 關(guān)于Mach/內(nèi)核/端口, 詳見(jiàn)后文<底層實(shí)現(xiàn)>.
    Cocoa和Core Foundation提供了內(nèi)置支持, 用于創(chuàng)建基于端口的輸入源.

例如

  • 在Cocoa中栏饮,您根本不需要直接創(chuàng)建輸入源, 而只需創(chuàng)建一個(gè)端口對(duì)象(NSPort), 然后使用端口對(duì)象的方法將該端口添加到運(yùn)行循環(huán)中吧兔。端口對(duì)象為您處理所需輸入源的創(chuàng)建和配置。
  • 在Core Foundation中袍嬉,您必須手動(dòng)創(chuàng)建端口及其運(yùn)行循環(huán)源境蔼。在這兩種情況下灶平,都使用與端口不透明類型(CFMachPortRef、CFMessagePortRef或CFSocketRef)關(guān)聯(lián)的函數(shù)來(lái)創(chuàng)建適當(dāng)?shù)膶?duì)象箍土。
  1. Custom Input Sources 自定義輸入源
    監(jiān)視事件的定制源, 從另一個(gè)線程手動(dòng)發(fā)出信號(hào).
    參見(jiàn)定義自定義輸入源

  2. Cocoa Perform Selector Sources 選擇器源 (屬于自定義輸入源)
    該源可讓您在任何線程上執(zhí)行選擇器. 與基于端口的源不同, 執(zhí)行選擇器源在執(zhí)行選擇器后將其自身從運(yùn)行循環(huán)中刪除.

注意: 在另一個(gè)線程上執(zhí)行選擇器時(shí), 目標(biāo)線程必須開啟了RunLoop.

CFRunLoopTimerRef

計(jì)時(shí)器源, 屬于端口輸入源, 在將來(lái)的預(yù)設(shè)時(shí)間將事件同步傳遞到您的線程.

如果timer不支持當(dāng)前RunLoop的模式, 則不會(huì)觸發(fā); 直到RunLoop切換到timer所支持的模式, 才會(huì)觸發(fā); 如果RunLoop沒(méi)有運(yùn)行, 則timer永遠(yuǎn)不會(huì)觸發(fā).

重復(fù)計(jì)時(shí)器會(huì)根據(jù)計(jì)劃的觸發(fā)時(shí)間(而不是實(shí)際的觸發(fā)時(shí)間)自動(dòng)重新計(jì)劃自身. 例如逢享,如果 timer 每5秒觸發(fā)一次, 則即使實(shí)際觸發(fā)時(shí)間被延遲, 計(jì)劃的觸發(fā)時(shí)間也將始終落在原始的5秒時(shí)間間隔上.如果觸發(fā)時(shí)間延遲得太久, 以致錯(cuò)過(guò)了一個(gè)或多個(gè)計(jì)劃的觸發(fā)時(shí)間, 則 timer 將在錯(cuò)過(guò)的時(shí)間段內(nèi)僅觸發(fā)一次. 在錯(cuò)過(guò)了一段時(shí)間后觸發(fā)后, 計(jì)時(shí)器將重新安排為下一個(gè)計(jì)劃的觸發(fā)時(shí)間.

CFRunLoopObserverRef

觀察者 (監(jiān)聽(tīng)器), 隨時(shí)通知外部當(dāng)前RunLoop的運(yùn)行狀態(tài).

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即將進(jìn)入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
};

Call out

在開發(fā)過(guò)程中幾乎所有的操作都是通過(guò)Call out進(jìn)行回調(diào)的(無(wú)論是Observer的狀態(tài)通知還是Timer、Source的處理)吴藻,而系統(tǒng)在回調(diào)時(shí)通常使用如下幾個(gè)函數(shù)進(jìn)行回調(diào)(換句話說(shuō)你的代碼其實(shí)最終都是通過(guò)下面幾個(gè)函數(shù)來(lái)負(fù)責(zé)調(diào)用的瞒爬,即使你自己監(jiān)聽(tīng)Observer也會(huì)先調(diào)用下面的函數(shù)然后間接通知你,所以在調(diào)用堆棧中經(jīng)彻当ぃ看到這些函數(shù)):

    static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
    static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();

例如在控制器的touchBegin中打入斷點(diǎn)查看堆棧(由于UIEvent是Source0侧但,所以可以看到一個(gè)Source0的Call out函數(shù)CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION調(diào)用)(圖片來(lái)源):

call out

運(yùn)行流程

線程的運(yùn)行循環(huán)都會(huì)處理事件源并為所有已注冊(cè)的觀察者生成通知. RunLoop 內(nèi)部的邏輯大致如下 (圖片來(lái)源):

RunLoop運(yùn)行流程

Source0: 自定義輸入源
Source1: 基于端口的輸入源

可以看到, 實(shí)際上 RunLoop 內(nèi)部是一個(gè) do-while 循環(huán). 當(dāng)你調(diào)用 CFRunLoopRun() 時(shí), 線程就會(huì)一直停留在這個(gè)循環(huán)里; 直到超時(shí)或被手動(dòng)停止, 該循環(huán)才會(huì)退出.

底層實(shí)現(xiàn)

RunLoop 的核心是基于 mach port 的,其進(jìn)入休眠時(shí)調(diào)用的函數(shù)是 mach_msg()航罗。為了解釋這個(gè)邏輯禀横,下面稍微介紹一下 OSX/iOS 的系統(tǒng)架構(gòu)。圖片來(lái)源

系統(tǒng)架構(gòu).png

Darwin 即操作系統(tǒng)的核心, 包括系統(tǒng)內(nèi)核粥血、驅(qū)動(dòng)燕侠、Shell 等內(nèi)容. 我們?cè)偕钊肟匆幌?Darwin 這個(gè)核心的架構(gòu) (圖片來(lái)源):

Darwin架構(gòu).png

其中,在硬件層上面的三個(gè)組成部分:Mach立莉、BSD、IOKit (還包括一些上面沒(méi)標(biāo)注的內(nèi)容)七问,共同組成了 XNU 內(nèi)核蜓耻。

  • XNU 內(nèi)核的內(nèi)環(huán)被稱作 Mach,其作為一個(gè)微內(nèi)核,僅提供了諸如處理器調(diào)度、IPC (進(jìn)程間通信)等非常少量的基礎(chǔ)服務(wù)晒衩。
  • BSD 層可以看作圍繞 Mach 層的一個(gè)外環(huán)柿隙,其提供了諸如進(jìn)程管理、文件系統(tǒng)和網(wǎng)絡(luò)等功能誓竿。
  • IOKit 層是為設(shè)備驅(qū)動(dòng)提供了一個(gè)面向?qū)ο?C++)的一個(gè)框架。

在Mach中,進(jìn)程蔼卡、線程間的通信是以消息的方式來(lái)完成的,消息在兩個(gè)Port之間進(jìn)行傳遞(這也正是Source1之所以稱之為Port-based Source的原因挣磨,因?yàn)樗褪且揽肯到y(tǒng)發(fā)送消息到指定的Port來(lái)觸發(fā)的)雇逞。消息的發(fā)送和接收使用<mach/message.h>中的mach_msg()函數(shù).

什么時(shí)候使用

我們只需在子線程創(chuàng)建的時(shí)候決定是否需要運(yùn)行RunLoop (主線程中自動(dòng)創(chuàng)建并運(yùn)行). 當(dāng)然, 我們無(wú)需在所有情況下都啟動(dòng)線程的運(yùn)行循環(huán). 例如, 如果使用線程執(zhí)行一些很耗時(shí)的任務(wù), 則可以避免啟動(dòng)RunLoop.

RunLoop用于需要與線程更多交互的情況. 例如:

  • 使用端口或自定義輸入源與其他線程進(jìn)行通信
  • 在線程上使用計(jì)時(shí)器
  • 使用任何performSelector方法
  • 保持線程執(zhí)行定期任務(wù)

應(yīng)用

NSTimer

NSTimer 是基于 RunLoop 運(yùn)行的 (對(duì)應(yīng)于CFRunloopTimerRef), 所以使用 NSTimer 之前必須注冊(cè)到 RunLoop, 但是 RunLoop 為了節(jié)省資源并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)調(diào)用定時(shí)器.

NSTimer 的創(chuàng)建通常有兩種方式: 一種是timerWithXXX, 另一種scheduedTimerWithXXX

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block ;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block ;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo

二者最大的區(qū)別是, scheduedTimerWithXXX除了創(chuàng)建一個(gè)定時(shí)器外會(huì)自動(dòng)以NSDefaultRunLoopMode添加到當(dāng)前線程 RunLoop 中, 而不添加到 RunLoop 中的 NSTimer 是無(wú)法正常工作的.

AutoreleasePool

AutoreleasePool與RunLoop并沒(méi)有直接的關(guān)系, 之所以將兩個(gè)話題放到一起討論最主要的原因是因?yàn)樵趇OS應(yīng)用啟動(dòng)后會(huì)注冊(cè)兩個(gè)Observer管理和維護(hù)AutoreleasePool. 這兩個(gè)Observer是和自動(dòng)釋放池相關(guān)的兩個(gè)監(jiān)聽(tīng).
不妨在應(yīng)用程序剛剛啟動(dòng)時(shí)打印currentRunLoop可以看到系統(tǒng)默認(rèn)注冊(cè)了很多個(gè)Observer,其中有兩個(gè)Observer的callout如下:

<CFRunLoopObserver 0x6080001246a0 [0x101f81df0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
<CFRunLoopObserver 0x608000124420 [0x101f81df0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
  • 第一個(gè) Observer 監(jiān)視的事件是 Entry (即將進(jìn)入Loop), 它會(huì)回調(diào)objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池, 并向當(dāng)前的AutoreleasePoolPage增加一個(gè)哨兵對(duì)象標(biāo)志. 這個(gè)Observer的order是-2147483647, 優(yōu)先級(jí)最高, 保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前.

  • 第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting (準(zhǔn)備進(jìn)入休眠) 和 Exit (即將退出Loop) . BeforeWaiting時(shí)調(diào)用方法釋放舊的池并創(chuàng)建新池茁裙;Exit時(shí)調(diào)用方法來(lái)釋放自動(dòng)釋放池. 這個(gè)Observer的order是2147483647, 優(yōu)先級(jí)最低, 保證其釋放池子發(fā)生在其他所有回調(diào)之后塘砸。

在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)晤锥、Timer回調(diào)內(nèi)的掉蔬。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著廊宪,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了女轿。

UI更新

當(dāng)在操作 UI 時(shí), 比如改變了 Frame箭启、更新了 UIView/CALayer 的層次時(shí), 或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后, 這個(gè) UIView/CALayer 就被標(biāo)記為待處理, 并被提交到一個(gè)全局的容器去, 等待下一次RunLoop運(yùn)行時(shí)更新UI. 但是如果當(dāng)前正在執(zhí)行大量的邏輯運(yùn)算可能UI的更新就會(huì)比較卡.

蘋果注冊(cè)了一個(gè) Observer 監(jiān)聽(tīng) BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個(gè)很長(zhǎng)的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()谈喳。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整册烈,并更新 UI 界面。

NSURLConnection

NSURLConnection 啟動(dòng)以后就會(huì)不斷調(diào)用delegate方法接收數(shù)據(jù)婿禽,這樣一個(gè)連續(xù)的的動(dòng)作正是基于RunLoop來(lái)運(yùn)行赏僧。

一旦NSURLConnection設(shè)置了delegate會(huì)立即創(chuàng)建一個(gè)線程com.apple.NSURLConnectionLoader,同時(shí)內(nèi)部啟動(dòng)RunLoop并在NSDefaultMode模式下添加4個(gè)Source0扭倾。其中CFHTTPCookieStorage用于處理cookie ;CFMultiplexerSource負(fù)責(zé)各種delegate回調(diào)并在回調(diào)中喚醒delegate內(nèi)部的RunLoop(通常是主線程)來(lái)執(zhí)行實(shí)際操作淀零。

NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的膛壹,但底層仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 線程).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驾中,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子模聋,更是在濱河造成了極大的恐慌肩民,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件链方,死亡現(xiàn)場(chǎng)離奇詭異持痰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)祟蚀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門工窍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人前酿,你說(shuō)我怎么就攤上這事患雏。” “怎么了罢维?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵淹仑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肺孵,道長(zhǎng)攻人,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任悬槽,我火速辦了婚禮怀吻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘初婆。我一直安慰自己蓬坡,他們只是感情好猿棉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屑咳,像睡著了一般萨赁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兆龙,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天杖爽,我揣著相機(jī)與錄音,去河邊找鬼紫皇。 笑死慰安,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聪铺。 我是一名探鬼主播化焕,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼铃剔!你這毒婦竟也來(lái)了撒桨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤键兜,失蹤者是張志新(化名)和其女友劉穎凤类,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體普气,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踱蠢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棋电。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苇侵,死狀恐怖赶盔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榆浓,我是刑警寧澤于未,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站陡鹃,受9級(jí)特大地震影響烘浦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萍鲸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一闷叉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脊阴,春花似錦握侧、人聲如沸蚯瞧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)埋合。三九已至,卻和暖如春萄传,著一層夾襖步出監(jiān)牢的瞬間甚颂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工秀菱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留振诬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓答朋,卻偏偏與公主長(zhǎng)得像贷揽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梦碗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361