iOS - RunLoop

image.png

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

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


image

在開(kāi)發(fā)的過(guò)程中,我們接觸到的 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è)過(guò)程中就會(huì)開(kāi)啟一個(gè) RunLoop 對(duì)象,這個(gè)對(duì)象就會(huì)循環(huán)處理一些事情,當(dāng)我們點(diǎn)擊一個(gè)可以交互的 UI 控件的時(shí)候,程序會(huì)做出響應(yīng),這都是 RunLoop 的功勞。

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

RunLoop 對(duì)象

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

[NSRunLoop currentRunLoop];
CFRunLoopGetCurrent();

RunLoop 和線程

每條線程都有一個(gè)唯一的一個(gè)與之對(duì)應(yīng)的 RunLoop 對(duì)象,并且 RunLoop 保存在一個(gè)全局的 Dictionary 中,線程為 key,RunLoop 為 value。
剛創(chuàng)建的線程是沒(méi)有 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); // 沒(méi)查找到,創(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)中這一說(shuō)法。

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 中沒(méi)有任何 source0、source1、timer、observer 則 RunLoop 會(huì)立即退出。

常見(jiàn)的 Mode

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

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

其他的 Mode 開(kāi)發(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)擊事件,一開(kāi)始是由 source1 捕捉,然后分發(fā)給 source0 處理。

Timers

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

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

如對(duì) UI 控件進(jìn)行顏色設(shè)置的時(shí)候,并不會(huì)立即生效,監(jiān)聽(tīng)器會(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è)例子來(lái)監(jiān)聽(tīng)這些狀態(tài):

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

observeRunLoopActicities 為 C 語(yǔ)言函數(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;
  • 如果沒(méi)有 Source1,通知 Observers 進(jìn)入休眠狀態(tài);

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

  • 處理 blocks;

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

    • 可能不退出 RunLoop 繼續(xù)從處理 Timer 開(kāi)始;
    • 若退出 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;
            // 判斷有沒(méi)有 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 的部分是判斷如何醒來(lái)的
        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)打交道,并不開(kāi)放,應(yīng)用層面的 API 是開(kāi)放的,可以進(jìn)行 UI、網(wǎng)絡(luò)層等編程。

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

控制線程周期

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

假如,在控制器中有這樣一個(gè)需求,啟動(dòng)控制器的時(shí)候就開(kāi)啟子線程,并進(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 失效問(wèn)題

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í)的操作所造成的,這樣可以通過(guò) RunLoop 來(lái)檢測(cè)卡頓:添加 Observer 到主線程 RunLoop 中,通過(guò)監(jiān)聽(tīng) RunLoop 狀態(tài)的切換的耗時(shí),達(dá)到監(jiān)控卡頓的目的。

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