iOS - RunLoop

image.png

基本認(rèn)識(shí)

顧名思義诈茧,在程序運(yùn)行的過程中循環(huán)做一些事情榜晦。


image

在開發(fā)的過程中矿酵,我們接觸到的 NSTimer 相關(guān)奥此、GCD Async Main Queue障涯、事件響應(yīng)推姻、手勢(shì)識(shí)別靶擦、界面刷新游沿、網(wǎng)絡(luò)請(qǐng)求和自動(dòng)釋放池都是基于 RunLoop 實(shí)現(xiàn)削祈。

項(xiàng)目的主程序入口 main 函數(shù)會(huì)返回一個(gè) UIApplicationMain翅溺,在這個(gè)過程中就會(huì)開啟一個(gè) RunLoop 對(duì)象脑漫,這個(gè)對(duì)象就會(huì)循環(huán)處理一些事情,當(dāng)我們點(diǎn)擊一個(gè)可以交互的 UI 控件的時(shí)候咙崎,程序會(huì)做出響應(yīng)优幸,這都是 RunLoop 的功勞。

所以說 RunLoop 可以保持程序的正常運(yùn)行褪猛,能響應(yīng)各種事件网杆,并節(jié)省 CPU 資源,提高程序性能:沒有事件的時(shí)候待命伊滋,有事件的時(shí)候處理事情碳却。

RunLoop 對(duì)象

iOS 中有 2 套 API 訪問和使用 RunLoop,分別是 Foundation 中的 NSRunLoopCore Foudation 中的 CFRunLoopRef笑旺,前者是后者的 Objective-C 封裝昼浦。并且 CFRunLoopRef 是開源的,開源地址在筒主。下面就是獲取當(dāng)前的 RunLoop 對(duì)象:

[NSRunLoop currentRunLoop];
CFRunLoopGetCurrent();

RunLoop 和線程

每條線程都有一個(gè)唯一的一個(gè)與之對(duì)應(yīng)的 RunLoop 對(duì)象关噪,并且 RunLoop 保存在一個(gè)全局的 Dictionary 中,線程為 key乌妙,RunLoop 為 value使兔。
剛創(chuàng)建的線程是沒有 RunLoop 對(duì)象的,RunLoop 會(huì)在第一次獲取它的時(shí)候創(chuàng)建藤韵。RunLoop 會(huì)隨著線程的結(jié)束銷毀虐沥,主線程比較特殊,會(huì)自動(dòng)創(chuàng)建并獲取 RunLoop泽艘。

在源碼中欲险,CFRunLoopGetCurrent 的實(shí)現(xiàn)為:

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

我們看到最終調(diào)用的是 _CFRunLoopGet0 方法,該方法中有:

CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 先從字典中查找是否有對(duì)應(yīng)的 RunLoop 
__CFUnlock(&loopsLock);
if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t); // 沒查找到悉盆,創(chuàng)建
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
}
if (!loop) {
    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); // 將新創(chuàng)建的 RunLoop 保存到全局的字典當(dāng)中
    loop = newLoop;
}

這驗(yàn)證了 RunLoop 會(huì)存在一個(gè)全局的字典當(dāng)中這一說法盯荤。

pthreadPointer(t) 為線程。

RunLoop 相關(guān)的類

在 Core Foundation 中和 RunLoop 相關(guān)的有 5 個(gè)類:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopOberverRef

CFRunLoop 的底層結(jié)構(gòu)為:

typedef struct __CFRunLoop* CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
}

從結(jié)構(gòu)體可以看出焕盟,一個(gè) RunLoop 可以裝著多個(gè) mode秋秤,但實(shí)際只有一個(gè) mode 是 currentMode。

__CFRunLoopMode 的結(jié)構(gòu)為:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0; // 對(duì)應(yīng) CFRunLoopSourceRef 對(duì)象
    CFMutableSetRef _sources1; // 對(duì)應(yīng) CFRunLoopSourceRef 對(duì)象
    CFMutableArrayRef _observers; // 對(duì)應(yīng) CFRunLoopOberverRef 對(duì)象 
    CFMutableArrayRef _timers; // 對(duì)應(yīng) CFRunLoopTimerRef 對(duì)象
    CFMutableDictionaryRef _portToV1SourceMap;
    ...
};

所以脚翘,總體關(guān)系如下:


image

RunLoop 啟東時(shí)只能選一個(gè) Mode 作為 Current Mode灼卢,若要切換 Mode,只能退出當(dāng)前 RunLoop来农,重新選擇一個(gè) Mode 再進(jìn)入鞋真。
這樣做的好處是:不同組的 source0、source1沃于、timer涩咖、observer 相互隔離海诲,互不影響。

如果 Mode 中沒有任何 source0檩互、source1特幔、timer、observer 則 RunLoop 會(huì)立即退出闸昨。

常見的 Mode

  • kCFRunLoopDefaultMode (NSDefaultRunLoopMode)蚯斯,主線程是在這個(gè) Mode 下執(zhí)行的。

  • UITrackingRunLoopMode饵较,界面跟蹤 Mode拍嵌,用于 ScrollView 追蹤觸摸滑動(dòng),保證滑動(dòng)時(shí)不被其他 Mode 影響循诉。

其他的 Mode 開發(fā)中不常用横辆。

并且需要注意的是,主線程切換 Mode 并不會(huì)導(dǎo)致程序退出茄猫,切換 Mode 的操作還是在事件循環(huán)中進(jìn)行的龄糊,并不會(huì)打破事件循環(huán)。

那么創(chuàng)建一個(gè) RunLoop 并選擇一個(gè) Mode 后募疮,最終處理的就是 Mode 下的 source0、source1僻弹、timer阿浓、observer。

source0

一般指觸摸事件處理蹋绽,我們新建一個(gè)空白程序芭毙,在初始界面添加觸摸方法,并在注釋位置加斷點(diǎn):

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"%s", __func__); // 加斷點(diǎn)
    
}

進(jìn)入調(diào)試環(huán)境后借助 bt 命令打印函數(shù)調(diào)用棧:

image.png

在上圖 #9 的位置看到了 source0 的影子卸耘。

performSelector: onThread: 系列方法也是 source0 的一個(gè)范疇退敦。

source1
  • 基于 Port 的線程通信;
  • 系統(tǒng)事件的捕捉蚣抗;

如點(diǎn)擊事件侈百,一開始是由 source1 捕捉,然后分發(fā)給 source0 處理翰铡。

Timers

就是我們熟知的 NSTimer钝域,另,performSelector: withObject: afterDelay: 也屬于 Timer 范疇锭魔。

obervers
  • 用于監(jiān)聽 RunLoop 的狀態(tài)例证;
  • UI 刷新;
  • Autorelease pool迷捧;

如對(duì) UI 控件進(jìn)行顏色設(shè)置的時(shí)候织咧,并不會(huì)立即生效胀葱,監(jiān)聽器會(huì)在 RunLoop 休眠之前進(jìn)行 UI 刷新。自動(dòng)釋放池同理笙蒙。

有時(shí)候抵屿,我們也會(huì)手動(dòng)添加 observer,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), // 即將休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠狀態(tài)喚醒
    kCFRunLoopExit = (1UL << 7), // 即將退出 RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

我們寫個(gè)例子來監(jiān)聽這些狀態(tài):

// 創(chuàng)建 observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加到 RunLoop 中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放 observe
CFRelease(observer);

observeRunLoopActicities 為 C 語言函數(shù):

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

觸摸了一下屏幕發(fā)現(xiàn)打邮秩ぁ:


image.png

在觸摸函數(shù)調(diào)用之前晌该,RunLoop 的狀態(tài)為 kCFRunLoopBeforeSources 即即將處理 source。
我們將觸摸函數(shù)改為:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
        NSLog(@"This is a timer");
    }];
    
}

增加了一個(gè)定時(shí)器绿渣,運(yùn)行并觸摸屏幕打印結(jié)果為:


image

在處理定時(shí)器之前朝群,RunLoop 的狀態(tài)為 kCFRunLoopAfterWaiting 即喚醒狀態(tài)。

RunLoop 的運(yùn)行邏輯

  • 首先中符,通知 Observers 進(jìn)入 Loop姜胖;

  • 進(jìn)入 Loop 后,再次通知 Observers淀散,即將處理 Timers右莱;

  • 通知 Observers 即將處理 Sources;

    • 處理 blocks档插;
    • 處理 Source0慢蜓,并且可能會(huì)再次處理 blocks;
  • 如果沒有 Source1郭膛,通知 Observers 進(jìn)入休眠狀態(tài)晨抡;

  • 如果有 Source1,通知 Observers 結(jié)束休眠则剃,處理消息事件耘柱;
    a. 處理 Timer;
    b. 處理 GCD 的隊(duì)列棍现;
    c. 處理 Source1调煎;

  • 處理 blocks;

  • 根據(jù)前面的執(zhí)行結(jié)果己肮,決定如何操作:

    • 可能不退出 RunLoop 繼續(xù)從處理 Timer 開始士袄;
    • 若退出 RunLoop,會(huì)通知 Observers 退出 Loop谎僻;
  • 通知 Observers 退出 Loop窖剑;

執(zhí)行邏輯源碼解讀

CFRunLoop.c 中,SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) 為整個(gè) RunLoop 的入口戈稿。

去除細(xì)節(jié)和加鎖代碼西土,為:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {         

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 通知 Observers 進(jìn)入 Loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // 主要的運(yùn)行邏輯

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); // 通知 Observers 退出 Loop

    return result;
}

__CFRunLoopRun 中有非常復(fù)雜的邏輯:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ...
    ...
    int32_t retVal = 0; 
    do {
        ...
        // 通知 Observers 即將處理 Timbers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知 Observers 即將處理 Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 處理 blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 處理 Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm); //再次處理 blocks
          }
        ...
    
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            // 判斷有沒有 Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                // 如果有 Source1,跳轉(zhuǎn)到 handle_msg
                goto handle_msg;
            }
        }
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl); // 即將休眠
        ...
        do {
            ...
            // 等待別的消息喚醒當(dāng)前線程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        ...

        __CFRunLoopSetIgnoreWakeUps(rl);
        __CFRunLoopUnsetSleeping(rl);
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))         __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 通知 Observers 結(jié)束休眠
    handle_msg:
        ...
        ...
        // if - else if - ... - else 的部分是判斷如何醒來的
        if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // 被 Timers 喚醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())){
                    __CFArmNextTimerInMode(rlm, rl);
            }
        }
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被 Timers 喚醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            
        else if (livePort == dispatchPort) { // 被 GCD 喚醒
            ...
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); // 處理 GCD 相關(guān)
            ...
        } else { // 其余都被認(rèn)定是 Source1 喚醒
            ...
              sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply); // 處理 Source1
              ...
    } while (0 == retVal); // 整個(gè) do-while 就是循環(huán)處理事件的部分
    ...
    ...
}

需要注意的是鞍盗,在通知線程進(jìn)入休眠的狀態(tài)時(shí)候并非傳統(tǒng)意義上的阻塞需了,而是真正的進(jìn)入了休眠狀態(tài)跳昼,也就是內(nèi)核層面的休眠。

內(nèi)核層面的 API 和操作系統(tǒng)打交道肋乍,并不開放鹅颊,應(yīng)用層面的 API 是開放的,可以進(jìn)行 UI墓造、網(wǎng)絡(luò)層等編程堪伍。

RunLoop 的實(shí)際應(yīng)用

控制線程周期

最典型的開源框架 AFNetworking 就是用了 RunLoop 的技術(shù)來控制子線程的生命周期:創(chuàng)建線程后,一直讓線程處于內(nèi)存中不銷毀觅闽,在某一刻需要執(zhí)行任務(wù)的時(shí)候就讓子線程處理帝雇,在某一刻銷毀子線程的話就停止 RunLoop。

假如蛉拙,在控制器中有這樣一個(gè)需求尸闸,啟動(dòng)控制器的時(shí)候就開啟子線程,并進(jìn)行線程痹谐活在點(diǎn)擊停止按鈕的時(shí)候吮廉,就終止線程的 RunLoop,那么實(shí)現(xiàn)為:

#import "ViewController.h"
#import "VThread.h"

@interface ViewController ()

@property (nonatomic, strong) VThread* thread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.stopped = NO;
    __weak typeof(self) weakSelf = self;
    self.thread = [[VThread alloc] initWithBlock:^{
        NSLog(@"%s %@", __func__, [NSThread currentThread]);
        
        // 向 RunLoop 中添加 Observers畸肆、Timers宦芦、Sources
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode: NSDefaultRunLoopMode];
        
        while (weakSelf && !weakSelf.isStopped) {
            [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; // 永不超時(shí),RunLoop 永遠(yuǎn)執(zhí)行
        }
        
        NSLog(@"==== end ====");
    }];
    
}
- (IBAction)stopButtonDidClick:(id)sender {
    
    
    if (!self.thread) return;
    // 子線程執(zhí)行終止 RunLoop
    // YES 標(biāo)識(shí)表示等待 stopRunLoop 執(zhí)行完再繼續(xù)走下面的邏輯
    [self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone: YES];
}

- (void)dealloc {
    
    self.thread = nil;
    [self stopRunLoop];
}

// 終止子線程的 RunLoop
- (void)stopRunLoop {
    
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    // 清空線程
    self.thread = nil;

}
@end

NSTimer 失效問題

NSTimer 在默認(rèn)情況是 NSDefaultRunLoopMode 模式的轴脐,那么在復(fù)雜的 UI 控制其中踪旷,在滑動(dòng) UIScrollView 及其子類的時(shí)候模式會(huì)切換為 UITrackingRunLoopMode 模式,造成只能在NSDefaultRunLoopMode 模式下工作的 Timer 的停止工作豁辉,進(jìn)而失效。

NSTimer 的 scheduled.... 系列方法都是設(shè)置的默認(rèn)模式舀患,所以不建議使用徽级。

那么解決辦法就是:

NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        // ....
    }];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

kCFRunLoopCommonModes 并不是一個(gè)真正的全新的模式,僅僅作為標(biāo)記的作用聊浅,標(biāo)記著任何模式下都是通用的餐抢、可行的。
在底層結(jié)構(gòu)中低匙,CFRunLoop 的結(jié)構(gòu)體中:

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
}

_commonModes 包裝的就是 kCFRunLoopCommonModes 和 UITrackingRunLoopMode旷痕。

其他應(yīng)用

  • 監(jiān)控應(yīng)用卡頓
  • 性能優(yōu)化

這里的卡頓檢測(cè)主要是針對(duì)在主線程執(zhí)行了耗時(shí)的操作所造成的,這樣可以通過 RunLoop 來檢測(cè)卡頓:添加 Observer 到主線程 RunLoop 中顽冶,通過監(jiān)聽 RunLoop 狀態(tài)的切換的耗時(shí)欺抗,達(dá)到監(jiān)控卡頓的目的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末强重,一起剝皮案震驚了整個(gè)濱河市绞呈,隨后出現(xiàn)的幾起案子贸人,更是在濱河造成了極大的恐慌,老刑警劉巖佃声,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艺智,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡圾亏,警方通過查閱死者的電腦和手機(jī)十拣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來志鹃,“玉大人夭问,你說我怎么就攤上這事∨” “怎么了甲喝?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)铛只。 經(jīng)常有香客問我埠胖,道長(zhǎng),這世上最難降的妖魔是什么淳玩? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任直撤,我火速辦了婚禮,結(jié)果婚禮上蜕着,老公的妹妹穿的比我還像新娘谋竖。我一直安慰自己,他們只是感情好承匣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布蓖乘。 她就那樣靜靜地躺著,像睡著了一般韧骗。 火紅的嫁衣襯著肌膚如雪嘉抒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天袍暴,我揣著相機(jī)與錄音些侍,去河邊找鬼。 笑死政模,一個(gè)胖子當(dāng)著我的面吹牛岗宣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淋样,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼耗式,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纽什,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤措嵌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后芦缰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體企巢,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年让蕾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浪规。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡探孝,死狀恐怖笋婿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顿颅,我是刑警寧澤缸濒,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站粱腻,受9級(jí)特大地震影響庇配,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绍些,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一捞慌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柬批,春花似錦啸澡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至上沐,卻和暖如春皮服,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奄容。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留产徊,地道東北人昂勒。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像舟铜,于是被迫代替她去往敵國(guó)和親戈盈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355