NSRunLoop

前言

RunLoop的初期學(xué)習(xí)總結(jié)袁铐,后續(xù)會持續(xù)研究更新学密。

一淘衙、Runloop定義及作用

1. 什么是Runloop?

RunLoop:顧名思義腻暮,消息運行循環(huán)彤守。首先我們看下蘋果API解釋:

The RunLoop class declares the programmatic interface to objects that manage input sources. An RunLoop object processes input for sources such as mouse and keyboard events from the window system, Port objects, and NSConnection objects. An RunLoop object also processes Timer events.
中文:RunLoop類是用來管理輸入源的編程接口對象。RunLoop對象輸入源來自桌面系統(tǒng)的鼠標(biāo)和鍵盤事件哭靖,端口對象具垫,NSConnection對象。一個RunLoop對象也用來處理計時器事件试幽。

RunLoop和線程關(guān)系如下圖筝蚕,Input sourcesTimer sources接受事件,然后通知線程進(jìn)行處理铺坞。

  • 每個線程都有一個RunLoop起宽,主線程的RunLoop會在App運行的時自動運行,子線程需要手動獲取運行康震,第一次獲取時,才會去創(chuàng)建宾濒。
  • 每個RunLoop都會以一個模式mode來運行腿短,可以使用NSRunLoop的方法運行在某個特定的mode。

2. Runloop的定義

Runloop實際上就是一個do...while循環(huán)绘梦,有任務(wù)時開始橘忱,無任務(wù)時休眠:

3. Runloop的作用

  • 保持程序的持續(xù)運行
  • 處理APP中的各種事件(觸摸、定時器卸奉、performSelector)
  • 節(jié)省cpu資源钝诚、提供程序的性能:該做事就做事,該休息就休息

二榄棵、Runloop Mode

1. Runloop Mode是什么

一個RunLoop包含了多個Mode凝颇,每個Mode又包含了若干個Source、Timer疹鳄、Observer拧略。每次調(diào)用 RunLoop的主函數(shù)時,只能指定其中一個Mode瘪弓,這個Mode被稱作CurrentMode垫蛆。
RunLoop只會運行在一個模式下。想要切換模式,就要暫停當(dāng)前模式袱饭,重新啟動一個運行模式川无。

2. Runloop Mode的五個Mode

系統(tǒng)默認(rèn)注冊了5個Mode:

  • kCFRunLoopDefaultMode:App的默認(rèn) Mode,通常主線程是在這個 Mode 下運行的虑乖。
  • UITrackingRunLoopMode:界面跟蹤 Mode懦趋,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響决左。
  • UIInitializationRunLoopMode:在剛啟動 App 時第進(jìn)入的第一個 Mode愕够,啟動完成后就不再使用。
  • GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode佛猛,通常用不到惑芭。
  • kCFRunLoopCommonModes:這是一個占位的 Mode,沒有實際作用继找。

主線程RunLoop有兩個預(yù)置Model:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode遂跟。

3. CommonModes

官方解釋:

commonModes
Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common” modes with CFRunLoopAddCommonMode(_: _:)

  • kCFRunLoopCommonModes是一個特別的模式,它是一個偽模式婴渡,可以在標(biāo)記為CommonModes的模式下運行幻锁。
  • 一個 Mode 可以將自己標(biāo)記為 Common 屬性(通過將其 ModeName 添加到 RunLoop 的 commonModes 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時边臼,RunLoop 都會自動將 _commonModeItems里的 Source哄尔、Observer、Timer 同步到具有 Common 標(biāo)記的所有 Mode 里柠并。

以tableViewCell中加入timer場景為例:
當(dāng)創(chuàng)建一個 Timer 加到 DefaultMode 時岭接,Timer 會一直得到回調(diào),但如果此時滑動TableView臼予,RunLoop 會將 mode 切換為 TrackingRunLoopMode鸣戴,此時timer停止回調(diào),因為同時只有一種Mode存在粘拾,當(dāng)停止滑動窄锅,mode切換回kCFRunLoopDefaultMode,timer又重新開始回調(diào)缰雇,但此時的timer回調(diào)已經(jīng)是被延遲的錯誤時間的回調(diào)入偷。

如果想讓tableView滑動時timer可以正常調(diào)用,一是手動將這個 timer 分別加入這兩個 Mode械哟,二是將 Timer 加入到CommonModes 里盯串。

NSLog(@"Current AllModes == %@", CFRunLoopCopyAllModes(CFRunLoopGetCurrent()));
    NSLog(@"Current Mode == %@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"Current AllModes -- %@", CFRunLoopCopyAllModes(CFRunLoopGetCurrent()));
    NSLog(@"Current Mode -- %@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
/** 打印
Current AllModes == (
    UITrackingRunLoopMode,
    GSEventReceiveRunLoopMode,
    kCFRunLoopDefaultMode
)
Current Mode == kCFRunLoopDefaultMode

Current AllModes -- (
    UITrackingRunLoopMode,
    GSEventReceiveRunLoopMode,
    kCFRunLoopDefaultMode,
    kCFRunLoopCommonModes
)
Current Mode -- kCFRunLoopDefaultMode
*/
  • current mode:runloop當(dāng)前運行模式。
  • common modes:存儲的被標(biāo)記為common modes的模式戒良。

打印CFRunLoopGetCurrent()會獲取更多信息体捏,后續(xù)進(jìn)行深入研究。

CFRunLoopGetCurrent()

三、Runloop底層原理

1. Runloop的item — 六大事件

  • block應(yīng)用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • 調(diào)用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 響應(yīng)source0__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • 響應(yīng)source1(端口信息)__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD主隊列__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer源__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

source0:執(zhí)行performSelectors方法几缭,假如你在主線程performSelectors一個任務(wù)到子線程河泳,這時候就是在代碼中發(fā)送事件到子線程的runloop,這時候如果子線程開啟了runloop就會執(zhí)行該任務(wù)年栓,注意該performSelector方法只有在你子線程開啟runloop才能執(zhí)行拆挥,如果你沒有在子線程中開啟runloop,那么該操作會無法執(zhí)行并崩潰某抓。一個selector執(zhí)行完后會自動從run loop里面移除纸兔,如UIEvent(Touch事件等),在一次觸發(fā)之后就會被runloop移除否副。
source1: 蘋果創(chuàng)建用來接受系統(tǒng)發(fā)出事件汉矿,當(dāng)手機發(fā)生一個觸摸,搖晃或鎖屏等系統(tǒng)备禀,這時候系統(tǒng)會發(fā)送一個事件到app進(jìn)程(進(jìn)程通信)洲拇,這也就是為什么叫基于port傳遞source1的原因,port就是進(jìn)程端口嘛曲尸,該事件可以激活進(jìn)程里線程的runloop赋续,比如你點擊一下app的按鈕或屏幕,runloop就醒過來處理觸摸事件另患。

2. 從堆棧信息探索源碼調(diào)用

通過 bt 命令打印出堆棧信息纽乱,我們可以看到來自于CoreFoundation中的 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ 其它item同理可自行驗證:

查看CFRunloop.c中的源碼

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

我們可以看到,源碼中先調(diào)起__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__之后才會調(diào)用 func 調(diào)用 timer 昆箕,所以又一次可以驗證鸦列,為什么 timer 需要加到 Runloop 中去了。

3. 通過主線程探究Runloop內(nèi)部機制

// 主線程循環(huán)

CFRunLoopRef mainRunloop = CFRunLoopGetMain();

// CFRunLoop.c
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) { 
        t = pthread_main_thread_np(); // 默認(rèn)給主線程
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

        // 主線程为严、mainLoop敛熬、通過dict進(jìn)行綁定:dict[@"pthread_main_thread_np"] = mainLoop
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    
    // 其它線程與runloop綁定
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&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
        __CFSpinUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

創(chuàng)建RunLoop

/*
_CFRuntimeCreateInstance 這個創(chuàng)建runloop實例方法是關(guān)鍵,
但是看不到內(nèi)部方法肺稀,所以我們主要探究RunLoop對象結(jié)構(gòu)關(guān)系第股。
*/
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
    if (NULL == loop) {
        return NULL;
    }
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

繼續(xù)找CFRunLoopRef也就是RunLoop結(jié)構(gòu)體組成:

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

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; // 線程
    uint32_t _winthread;
    CFMutableSetRef _commonModes; // Mode集合
    CFMutableSetRef _commonModeItems; // ModeItem集合
    CFRunLoopModeRef _currentMode; 
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

找到CFRunLoopModeRef也就是RunLoopMode結(jié)構(gòu)體組成:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

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;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    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 */
};

Runloop機制關(guān)系圖

通過上面源碼追蹤,我們可以總結(jié)如下關(guān)系:
1. Runloop線程是一對一的關(guān)系
2. RunloopRunloopMode是一對多的關(guān)系
3. RunloopModeRunloopSource是一對多的關(guān)系
4. RunloopModeRunloopTimer是一對多的關(guān)系
5. RunloopModeRunloopObserver是一對多的關(guān)系

四. 分析runloop addTimer

通過堆棧找到關(guān)鍵函數(shù)__CFRunLoopDoTimers话原、__CFRunLoopDoTimer夕吻,然后進(jìn)行源碼分析。

源碼分析:

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                // 收集timers
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }

    // 遍歷所有timers繁仁,然后__CFRunLoopDoTimer涉馅,DoTimer結(jié)束后給標(biāo)記did,然后release釋放黄虱。
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

// __CFRunLoopDoTimer中會走_(dá)_CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__回調(diào)稚矿,timer block回調(diào)開始執(zhí)行。
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {    /* DOES CALLOUT */
    ...省略...    

    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);

    ...省略...    
}

根據(jù)堆棧信息追蹤源碼(省略部分源碼),主要對關(guān)鍵部分進(jìn)行分析形成閉環(huán)探索runloop和timer的關(guān)系:[runloop addTimer] -> ...... -> __CFRunLoopDoTimers -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ -> block回調(diào)晤揣。當(dāng)然Observer桥爽、source也是同理。

RunLoop Mode:
OS下Run Loop的主要運行模式mode有:

  • NSDefaultRunLoopMode:默認(rèn)的運行模式昧识,除了NSConnection對象的事件钠四。
  • NSRunLoopCommonModes:是一組常用的模式集合,將一個input source關(guān)聯(lián)到這個模式集合上跪楞,等于將input source關(guān)聯(lián)到這個模式集合中的所有模式上缀去。在iOS系統(tǒng)中NSRunLoopCommonModes包含NSDefaultRunLoopMode、NSTaskDeathCheckMode甸祭、UITrackingRunLoopMode缕碎。
  • UITrackingRunLoopMode:用于跟蹤觸摸事件觸發(fā)的模式(例如UIScrollView上下滾動), 主線程當(dāng)觸摸事件觸發(fā)會設(shè)置為這個模式淋叶,可以用來在控件事件觸發(fā)過程中設(shè)置Timer阎曹。
  • GSEventReceiveRunLoopMode:用于接受系統(tǒng)事件,屬于內(nèi)部的RunLoop模式煞檩。
  • 自定義Mode:可以設(shè)置自定義的運行模式Mode处嫌,你也可以用CFRunLoopAddCommonMode添加到NSRUnLoopCommonModes中。

總結(jié)一下:
Run Loop 運行時只能以一種固定的模式運行斟湃,如果我們需要它切換模式熏迹,只有停掉它,再重新開其它凝赛,運行時它只會監(jiān)控這個模式下添加的Timer Source和Input Source注暗,如果這個模式下沒有相應(yīng)的事件源,RunLoop的運行也會立刻返回的墓猎。注意RunLoop不能在運行在NSRunLoopCommonModes模式捆昏,因為NSRunLoopCommonModes其實是個模式集合,而不是一個具體的模式毙沾,我可以添加事件源的時候使用NSRunLoopCommonModes骗卜,只要Run Loop運行在NSRunLoopCommonModes中任何一個模式,這個事件源都可以被觸發(fā)左胞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寇仓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烤宙,更是在濱河造成了極大的恐慌遍烦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躺枕,死亡現(xiàn)場離奇詭異服猪,居然都是意外死亡供填,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門罢猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捕虽,“玉大人,你說我怎么就攤上這事坡脐⌒顾剑” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵备闲,是天一觀的道長晌端。 經(jīng)常有香客問我,道長恬砂,這世上最難降的妖魔是什么咧纠? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮泻骤,結(jié)果婚禮上漆羔,老公的妹妹穿的比我還像新娘。我一直安慰自己狱掂,他們只是感情好演痒,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趋惨,像睡著了一般鸟顺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上器虾,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天讯嫂,我揣著相機與錄音,去河邊找鬼兆沙。 笑死欧芽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葛圃。 我是一名探鬼主播千扔,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼装悲!你這毒婦竟也來了昏鹃?” 一聲冷哼從身側(cè)響起尚氛,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诀诊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阅嘶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體属瓣,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡载迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抡蛙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片护昧。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粗截,靈堂內(nèi)的尸體忽然破棺而出惋耙,到底是詐尸還是另有隱情,我是刑警寧澤熊昌,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布绽榛,位于F島的核電站,受9級特大地震影響婿屹,放射性物質(zhì)發(fā)生泄漏灭美。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一昂利、第九天 我趴在偏房一處隱蔽的房頂上張望届腐。 院中可真熱鬧,春花似錦蜂奸、人聲如沸犁苏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傀顾。三九已至,卻和暖如春碌奉,著一層夾襖步出監(jiān)牢的瞬間短曾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工赐劣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嫉拐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓魁兼,卻偏偏與公主長得像婉徘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咐汞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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