iOS-RunLoop詳解(一):底層結(jié)構(gòu)源碼學(xué)習(xí)

image-20210512112630849
image-20210512112700812
image-20210512112721759
image-20210512112742222
image-20210512112806252
image-20210512112838719
image-20210512112855440
image-20210512112912651
image-20210512112931568
image-20210512112951469
image-20210512113013491
image-20210512113049516

RunLoop概念

RunLoop介紹

RunLoop 是什么?RunLoop 還是比較顧名思義的一個(gè)東西,說(shuō)白了就是一種循環(huán),只不過(guò)它這種循環(huán)比較高級(jí)。一般的 while 循環(huán)會(huì)導(dǎo)致 CPU 進(jìn)入忙等待狀態(tài),而 RunLoop 則是一種“閑”等待,這部分可以類(lèi)比 Linux 下的 epoll。當(dāng)沒(méi)有事件時(shí),RunLoop 會(huì)進(jìn)入休眠狀態(tài),有事件發(fā)生時(shí), RunLoop 會(huì)去找對(duì)應(yīng)的 Handler 處理事件。RunLoop 可以讓線(xiàn)程在需要做事的時(shí)候忙起來(lái),不需要的話(huà)就讓線(xiàn)程休眠。

img

沒(méi)有Runloop的程序

我們通過(guò)Xcode新建一個(gè)命令行項(xiàng)目,main.m文件里的代碼如下

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

程序在執(zhí)行完代碼NSLog(@"Hello, World!");之后,就會(huì)通過(guò) return 0;推出程序,這是一種線(xiàn)性的執(zhí)行流程。

我們?cè)傩陆ㄒ粋€(gè)iOS項(xiàng)目,你看到的main.m文件是這個(gè)樣子的

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我們會(huì)進(jìn)入app的界面,然后app就不會(huì)退出了,會(huì)一直運(yùn)行著。

在命令行工程里面的main.m里面,是沒(méi)有加Runloop的,而iOS工程的main.m里面,其實(shí)在UIApplicationMain()這個(gè)方法中,系統(tǒng)加上了Runloop,讓程序可以一直循環(huán)運(yùn)行下去不退出。

iOS項(xiàng)目,在main函數(shù)中系統(tǒng)就會(huì)自動(dòng)幫我們創(chuàng)建runloop對(duì)象:return UIApplicationMain(argc, argv, nil, appDelegateClassName);.
RunLoop的基本作用就是:

  • 保證程序的基本運(yùn)行.程序一啟動(dòng)就會(huì)開(kāi)一個(gè)主線(xiàn)程,主線(xiàn)程一開(kāi)起來(lái)就會(huì)跑一個(gè)主線(xiàn)程對(duì)應(yīng)的RunLoop,RunLoop保證主線(xiàn)程不會(huì)被銷(xiāo)毀,也就保證了程序的持續(xù)運(yùn)行

  • 處理App中的各種事件 (比如:觸摸事件,定時(shí)器事件 等等).

  • 節(jié)省 CPU 資源,提高程序性能: 該做事時(shí)做事,沒(méi)有事的時(shí)候就休息.(程序運(yùn)行起來(lái)時(shí),當(dāng)什么操作都沒(méi)有做的時(shí)候,RunLoop就告訴CPU,現(xiàn)在沒(méi)有事情做,我要去休息,這時(shí)CPU就會(huì)將其資源釋放出來(lái)去做其他的事情,當(dāng)有事情做的時(shí)候RunLoop就會(huì)立馬起來(lái)去做事情)

RunLoop工作原理的偽代碼大概如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            //睡眠中等待消息
            int message = sleep_and_wait();
            //處理消息
            retVal = process_message(message);
        } while (retVal = 0);
        return 0;
    }
}

流程:條件成立的時(shí)候一直循環(huán):有事情就處理事情,沒(méi)有事情就休眠睡覺(jué).Runloop其實(shí)就是一個(gè)do-while循環(huán),每次循環(huán)一圈,都會(huì)判斷一次retVal,決定是否結(jié)束循環(huán),繼續(xù)執(zhí)行循環(huán)外的代碼。

RunLoop對(duì)象

iOS中提供了兩套API來(lái)訪(fǎng)問(wèn)RunLoop:

  • Foundation : NSRunLoop : OC 框架
  • Core Foundation : CFRunLoopRef : C 語(yǔ)言框架

NSRunLoopCFRunLoopRef都代表Runloop對(duì)象,NSRunLoop是基于CFRunLoopRef的一層OC包裝,CFRunLoopRef開(kāi)源的

我們下載好源代碼后新建一個(gè)項(xiàng)目,把源代碼拖到項(xiàng)目中.

Runloop對(duì)象的獲取
  • Foundation
    [NSRunloop currentRunLoop];獲得當(dāng)前線(xiàn)程的RunLoop對(duì)象
    [NSRunLoop mainRunLoop];獲得主線(xiàn)程的Runloop對(duì)象
  • Core Foundation
    CFRunLoopGetCurrent();獲得當(dāng)前線(xiàn)程的RunLoop對(duì)象
    CFRunLoopGetMain();獲得主線(xiàn)程的Runloop對(duì)象

CFRunLoop.c文件 -> 然后找到CFRunLoopGetCurrent函數(shù) -> 進(jìn)入_CFRunLoopGet0函數(shù)

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//??????根據(jù)線(xiàn)程取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    ????????????
    static CFMutableDictionaryRef __CFRunLoops = NULL; //字典
    // 獲取 runloop 對(duì)象 參數(shù):傳入一個(gè) 字典 和 key (線(xiàn)程)
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //如果 runloop 不存在 , 就創(chuàng)建,并放到字典中
    if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    
    ????????????
}

RunLoop是在第一次獲取的時(shí)候創(chuàng)建的,并且RunLoop和 線(xiàn)程 是 一一對(duì)應(yīng)的關(guān)系,RunLoop是存放在一個(gè)全局字典中:以線(xiàn)程作為key,RunLoop作為value.

Runloop與線(xiàn)程

為什么聊Runloop一定要搭上線(xiàn)程?我們知道,程序里的每一句代碼,都會(huì)在線(xiàn)程(主線(xiàn)程/子線(xiàn)程)里面被執(zhí)行,上面四種獲得Runloop對(duì)象的代碼也不例外,一定是跑在線(xiàn)程里面的。之前我們說(shuō)到,Runloop是為了讓程序不退出,其實(shí)更準(zhǔn)確地說(shuō),是為了保持某個(gè)線(xiàn)程不結(jié)束,只要還有未結(jié)束的線(xiàn)程,那么整個(gè)程序就不會(huì)退出,因?yàn)榫€(xiàn)程是程序的運(yùn)行的調(diào)度的基本單元。

線(xiàn)程與Runloop的關(guān)系是一對(duì)一的,一個(gè)新創(chuàng)建的線(xiàn)程,是沒(méi)有Runloop對(duì)象的,當(dāng)我們?cè)谠摼€(xiàn)程里第一次通過(guò)上面的API獲得Runloop時(shí),Runloop對(duì)象才會(huì)被創(chuàng)建,并且通過(guò)一個(gè)全局字典將Runloop對(duì)象和該線(xiàn)程存儲(chǔ)綁定在一起,形成一對(duì)一關(guān)系。

Runloop會(huì)在線(xiàn)程結(jié)束時(shí)銷(xiāo)毀,主線(xiàn)程的Runloop已經(jīng)自動(dòng)獲取過(guò)(創(chuàng)建),子線(xiàn)程默認(rèn)沒(méi)有開(kāi)啟RunLoop(直到你在該線(xiàn)程獲取它)。RunLoop對(duì)象創(chuàng)建后,會(huì)被保存在一個(gè)全局的Dictionary里,線(xiàn)程作為keyRunloop對(duì)象作為value

Runloop對(duì)象底層結(jié)構(gòu)

image-20210512120133677

我們可以在源碼CFRunloop.c中找到Runloop的定義

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    //???????????? 核心組成 ????????????
    pthread_t _pthread;//RunLoop對(duì)應(yīng)的線(xiàn)程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;//存儲(chǔ)的是字符串,記錄所有標(biāo)記為common的mode
    CFMutableSetRef _commonModeItems;//存儲(chǔ)所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;//當(dāng)前運(yùn)行的mode
    CFMutableSetRef _modes;//存儲(chǔ)的是CFRunLoopModeRef
    //???????????? 核心組成 ????????????
    struct _block_item *_blocks_head;//doblocks的時(shí)候用到
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

RunLoop Mode Mode可以視為事件的管家,一個(gè)Mode管理著各種事件,它的結(jié)構(gòu)如下:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名稱(chēng)
    Boolean _stopped;  //mode是否被終止
    char _padding[3];
    //幾種事件
    //???????????? 核心組成 ????????????
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers; //通知
    CFMutableArrayRef _timers;//定時(shí)器
    //???????????? 核心組成 ????????????
    CFMutableDictionaryRef _portToV1SourceMap;//字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要監(jiān)聽(tīng)的port,比如_wakeUpPort,_timerPort都保存在這個(gè)數(shù)組中
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

一個(gè)CFRunLoopMode對(duì)象有一個(gè)name,若干source0、source1、timer、observer和若干port,可見(jiàn)事件都是由Mode在管理,而RunLoop管理Mode。

Runloop相關(guān)的5個(gè)相關(guān)的類(lèi)

  • CFRunLoopRef——這個(gè)就是Runloop對(duì)象
  • CFRunLoopModeRef——其內(nèi)部主要包括四個(gè)容器,分別用來(lái)存放source0source1observer以及timer
  • CFRunLoopSourceRef——分為source0source1
    source0:包括 觸摸事件處理、[performSelector: onThread: ]
    source1:包括 基于Port的線(xiàn)程間通信、系統(tǒng)事件捕捉
  • CFRunLoopTimerRef——timer事件,包括我們?cè)O(shè)置的定時(shí)器事件、[performSelector: withObject: afterDelay:]
  • CFRunLoopObserverRef——監(jiān)聽(tīng)者,Runloop狀態(tài)變更的時(shí),會(huì)通知監(jiān)聽(tīng)者進(jìn)行函數(shù)回調(diào),UI界面的刷新就是在監(jiān)聽(tīng)到Runloop狀態(tài)為BeforeWaiting時(shí)進(jìn)行的。

對(duì)于以上這幾個(gè)類(lèi)相互之間的關(guān)系,可以通過(guò)如下的圖來(lái)描繪

img

從圖中可看出,一個(gè)RunLoop對(duì)象里面包含了若干個(gè)RunLoopModeRunLoop內(nèi)部是通過(guò)一個(gè)集合容器_modes來(lái)裝這些RunLoopMode的。

RunLoopMode內(nèi)部核心內(nèi)容是4個(gè)數(shù)組容器,分別用來(lái)裝source0source1observertimerRunLoop對(duì)象內(nèi)部有一個(gè)_currentMode,它指向了該RunLoop對(duì)象的其中一個(gè)RunLoopMode,它代表的含義是RunLoop當(dāng)前所運(yùn)行的RunLoopMode,所謂“運(yùn)行”也就是說(shuō),RunLoop當(dāng)前只會(huì)執(zhí)行_currentMode所指向的RunLoopMode里面所包括的事件(source0、source1、observer、timer).RunLoop對(duì)象內(nèi)部還包括一個(gè)線(xiàn)程對(duì)象_pthread,這就是跟它一一對(duì)應(yīng)的那個(gè)線(xiàn)程對(duì)象。

RunLoop Source

Run Loop Source分為Source、Observer、Timer三種,他們統(tǒng)稱(chēng)為ModeItem

CFRunLoopSource

根據(jù)官方的描述,CFRunLoopSource是對(duì)input sources的抽象。CFRunLoopSource分source0和source1兩個(gè)版本,它的結(jié)構(gòu)如下:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; //?????? 用于標(biāo)記Signaled狀態(tài),source0只有在被標(biāo)記為Signaled狀態(tài),才會(huì)被處理
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;     /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};
source0

source0是App內(nèi)部事件,由App自己管理的UIEvent、CFSocket都是source0。當(dāng)一個(gè)source0事件準(zhǔn)備執(zhí)行的時(shí)候,必須要先把它標(biāo)記為signal狀態(tài).

App自己管理的UIEven,包括觸摸事件處理[performSelector: onThread: ],這個(gè)也可以通過(guò)代碼來(lái)驗(yàn)證一下。首先看一下觸摸事件,在ViewController里面重寫(xiě)方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"點(diǎn)擊屏幕");
}
image-20210512123111824

#9 0x00007fff2039038a in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()可以看出系統(tǒng)是通過(guò)一個(gè)CF的函數(shù)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__來(lái)調(diào)用UIKit進(jìn)行事件處理的

source0是非基于Port的。只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。

source1由RunLoop和內(nèi)核管理,source1帶有mach_port_t,可以接收內(nèi)核消息并觸發(fā)回調(diào),以下是source1的結(jié)構(gòu)體

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

Source1除了包含回調(diào)指針外包含一個(gè)mach port,Source1可以監(jiān)聽(tīng)系統(tǒng)端口和通過(guò)內(nèi)核和其他線(xiàn)程通信,接收、分發(fā)系統(tǒng)事件,它能夠主動(dòng)喚醒RunLoop(由操作系統(tǒng)內(nèi)核進(jìn)行管理,例如CFMessagePort消息)。官方也指出可以自定義Source,因此對(duì)于CFRunLoopSourceRef來(lái)說(shuō)它更像一種協(xié)議,框架已經(jīng)默認(rèn)定義了兩種實(shí)現(xiàn),如果有必要開(kāi)發(fā)人員也可以自定義,詳細(xì)情況可以查看官方文檔

RunLoop 的狀態(tài):

RunLoop有以下幾種狀態(tài):

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入 RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),  //即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2),  //即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),  //即將進(jìn)入 休眠
    kCFRunLoopAfterWaiting = (1UL << 6),  //剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),  //即將退出 RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU  // 以上所有狀態(tài)
};

下面我們寫(xiě)代碼來(lái)驗(yàn)證一下這些狀態(tài)的切換.首先寫(xiě)代碼測(cè)試一下,NStimer喚醒RunLoop:

void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //     創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}

RUN> ????????????

image-20210512124423553

可以看出,Runloop的狀態(tài)切換時(shí),都會(huì)被observer監(jiān)聽(tīng)到。

我們?cè)賱?chuàng)建一個(gè)UITextView,拖動(dòng)UITextView看看RunLoop狀態(tài)的切換情況

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }

            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }

            default:
                break;
        }
    });
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}

RUN> ????????????

image-20210512130047036

拖動(dòng)UITextView看看

2021-05-12 12:46:02.808851+0800 Interview03-RunLoop[2854:125671] kCFRunLoopExit - kCFRunLoopDefaultMode
2021-05-12 12:46:02.809032+0800 Interview03-RunLoop[2854:125671] kCFRunLoopEntry - UITrackingRunLoopMode
2021-05-12 12:46:04.280133+0800 Interview03-RunLoop[2854:125671] kCFRunLoopExit - UITrackingRunLoopMode
2021-05-12 12:46:04.280280+0800 Interview03-RunLoop[2854:125671] kCFRunLoopEntry - kCFRunLoopDefaultMode

可以看到RunLoop頻繁的在kCFRunLoopDefaultModeUITrackingRunLoopMode之間切換.

特別備注

本系列文章總結(jié)自MJ老師在騰訊課堂iOS底層原理班(下)/OC對(duì)象/關(guān)聯(lián)對(duì)象/多線(xiàn)程/內(nèi)存管理/性能優(yōu)化,相關(guān)圖片素材均取自課程中的課件。如有侵權(quán),請(qǐng)聯(lián)系我刪除,謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375

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