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

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 還是比較顧名思義的一個東西饥瓷,說白了就是一種循環(huán),只不過它這種循環(huán)比較高級痹籍。一般的 while 循環(huán)會導(dǎo)致 CPU 進入忙等待狀態(tài)呢铆,而 RunLoop 則是一種“閑”等待,這部分可以類比 Linux 下的 epoll蹲缠。當沒有事件時棺克,RunLoop 會進入休眠狀態(tài),有事件發(fā)生時线定, RunLoop 會去找對應(yīng)的 Handler 處理事件娜谊。RunLoop 可以讓線程在需要做事的時候忙起來,不需要的話就讓線程休眠斤讥。

img

沒有Runloop的程序

我們通過Xcode新建一個命令行項目纱皆,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!");之后,就會通過 return 0;推出程序周偎,這是一種線性的執(zhí)行流程抹剩。

我們再新建一個iOS項目,你看到的main.m文件是這個樣子的

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

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

我們會進入app的界面蓉坎,然后app就不會退出了澳眷,會一直運行著。

在命令行工程里面的main.m里面蛉艾,是沒有加Runloop的钳踊,而iOS工程的main.m里面,其實在UIApplicationMain()這個方法中勿侯,系統(tǒng)加上了Runloop拓瞪,讓程序可以一直循環(huán)運行下去不退出。

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

  • 保證程序的基本運行.程序一啟動就會開一個主線程助琐,主線程一開起來就會跑一個主線程對應(yīng)的RunLoop,RunLoop保證主線程不會被銷毀祭埂,也就保證了程序的持續(xù)運行

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

  • 節(jié)省 CPU 資源,提高程序性能: 該做事時做事,沒有事的時候就休息.(程序運行起來時,當什么操作都沒有做的時候,RunLoop就告訴CPU蛆橡,現(xiàn)在沒有事情做舌界,我要去休息,這時CPU就會將其資源釋放出來去做其他的事情泰演,當有事情做的時候RunLoop就會立馬起來去做事情)

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

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

RunLoop對象

iOS中提供了兩套API來訪問RunLoop:

  • Foundation : NSRunLoop : OC 框架
  • Core Foundation : CFRunLoopRef : C 語言框架

NSRunLoopCFRunLoopRef都代表Runloop對象垃喊,NSRunLoop是基于CFRunLoopRef的一層OC包裝猾普,CFRunLoopRef開源的

我們下載好源代碼后新建一個項目,把源代碼拖到項目中.

Runloop對象的獲取
  • Foundation
    [NSRunloop currentRunLoop];獲得當前線程的RunLoop對象
    [NSRunLoop mainRunLoop];獲得主線程的Runloop對象
  • Core Foundation
    CFRunLoopGetCurrent();獲得當前線程的RunLoop對象
    CFRunLoopGetMain();獲得主線程的Runloop對象

CFRunLoop.c文件 -> 然后找到CFRunLoopGetCurrent函數(shù) -> 進入_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ù)線程取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    ????????????
    static CFMutableDictionaryRef __CFRunLoops = NULL; //字典
    // 獲取 runloop 對象 參數(shù):傳入一個 字典 和 key (線程)
    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是在第一次獲取的時候創(chuàng)建的,并且RunLoop和 線程 是 一一對應(yīng)的關(guān)系,RunLoop是存放在一個全局字典中:以線程作為key,RunLoop作為value.

Runloop與線程

為什么聊Runloop一定要搭上線程?我們知道缔御,程序里的每一句代碼抬闷,都會在線程(主線程/子線程)里面被執(zhí)行,上面四種獲得Runloop對象的代碼也不例外耕突,一定是跑在線程里面的笤成。之前我們說到,Runloop是為了讓程序不退出眷茁,其實更準確地說炕泳,是為了保持某個線程不結(jié)束,只要還有未結(jié)束的線程上祈,那么整個程序就不會退出培遵,因為線程是程序的運行的調(diào)度的基本單元。

線程與Runloop的關(guān)系是一對一的登刺,一個新創(chuàng)建的線程籽腕,是沒有Runloop對象的,當我們在該線程里第一次通過上面的API獲得Runloop時纸俭,Runloop對象才會被創(chuàng)建皇耗,并且通過一個全局字典將Runloop對象和該線程存儲綁定在一起,形成一對一關(guān)系揍很。

Runloop會在線程結(jié)束時銷毀郎楼,主線程的Runloop已經(jīng)自動獲取過(創(chuàng)建),子線程默認沒有開啟RunLoop(直到你在該線程獲取它)窒悔。RunLoop對象創(chuàng)建后呜袁,會被保存在一個全局的Dictionary里,線程作為key简珠,Runloop對象作為value阶界。

Runloop對象底層結(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對應(yīng)的線程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;//存儲的是字符串,記錄所有標記為common的mode
    CFMutableSetRef _commonModeItems;//存儲所有commonMode的item(source、timer膘融、observer)
    CFRunLoopModeRef _currentMode;//當前運行的mode
    CFMutableSetRef _modes;//存儲的是CFRunLoopModeRef
    //???????????? 核心組成 ????????????
    struct _block_item *_blocks_head;//doblocks的時候用到
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

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

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名稱
    Boolean _stopped;  //mode是否被終止
    char _padding[3];
    //幾種事件
    //???????????? 核心組成 ????????????
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers; //通知
    CFMutableArrayRef _timers;//定時器
    //???????????? 核心組成 ????????????
    CFMutableDictionaryRef _portToV1SourceMap;//字典  key是mach_port_t托启,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要監(jiān)聽的port,比如_wakeUpPort攘宙,_timerPort都保存在這個數(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 */
};

一個CFRunLoopMode對象有一個name屯耸,若干source0、source1蹭劈、timer疗绣、observer和若干port,可見事件都是由Mode在管理铺韧,而RunLoop管理Mode多矮。

Runloop相關(guān)的5個相關(guān)的類

  • CFRunLoopRef——這個就是Runloop對象
  • CFRunLoopModeRef——其內(nèi)部主要包括四個容器,分別用來存放source0哈打、source1塔逃、observer以及timer
  • CFRunLoopSourceRef——分為source0source1
    source0:包括 觸摸事件處理、[performSelector: onThread: ]
    source1:包括 基于Port的線程間通信料仗、系統(tǒng)事件捕捉
  • CFRunLoopTimerRef——timer事件湾盗,包括我們設(shè)置的定時器事件、[performSelector: withObject: afterDelay:]
  • CFRunLoopObserverRef——監(jiān)聽者立轧,Runloop狀態(tài)變更的時格粪,會通知監(jiān)聽者進行函數(shù)回調(diào),UI界面的刷新就是在監(jiān)聽到Runloop狀態(tài)為BeforeWaiting時進行的氛改。

對于以上這幾個類相互之間的關(guān)系帐萎,可以通過如下的圖來描繪

img

從圖中可看出,一個RunLoop對象里面包含了若干個RunLoopMode胜卤,RunLoop內(nèi)部是通過一個集合容器_modes來裝這些RunLoopMode的疆导。

RunLoopMode內(nèi)部核心內(nèi)容是4個數(shù)組容器,分別用來裝source0瑰艘,source1是鬼,observertimerRunLoop對象內(nèi)部有一個_currentMode紫新,它指向了該RunLoop對象的其中一個RunLoopMode均蜜,它代表的含義是RunLoop當前所運行的RunLoopMode,所謂“運行”也就是說芒率,RunLoop當前只會執(zhí)行_currentMode所指向的RunLoopMode里面所包括的事件(source0囤耳、source1、observer、timer).RunLoop對象內(nèi)部還包括一個線程對象_pthread充择,這就是跟它一一對應(yīng)的那個線程對象德玫。

RunLoop Source

Run Loop Source分為Source、Observer椎麦、Timer三種宰僧,他們統(tǒng)稱為ModeItem

CFRunLoopSource

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

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; //?????? 用于標記Signaled狀態(tài)嘁捷,source0只有在被標記為Signaled狀態(tài)惩坑,才會被處理
    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棵介。當一個source0事件準備執(zhí)行的時候,必須要先把它標記為signal狀態(tài).

App自己管理的UIEven,包括觸摸事件處理缓升、[performSelector: onThread: ]鼓鲁,這個也可以通過代碼來驗證一下。首先看一下觸摸事件仔沿,在ViewController里面重寫方法

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

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

source0是非基于Port的坐桩。只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件封锉。使用時绵跷,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理成福,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop碾局,讓其處理這個事件。

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)指針外包含一個mach port蕴潦,Source1可以監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程通信像啼,接收、分發(fā)系統(tǒng)事件潭苞,它能夠主動喚醒RunLoop(由操作系統(tǒng)內(nèi)核進行管理忽冻,例如CFMessagePort消息)。官方也指出可以自定義Source此疹,因此對于CFRunLoopSourceRef來說它更像一種協(xié)議僧诚,框架已經(jīng)默認定義了兩種實現(xiàn)遮婶,如果有必要開發(fā)人員也可以自定義,詳細情況可以查看官方文檔湖笨。

RunLoop 的狀態(tài):

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

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

下面我們寫代碼來驗證一下這些狀態(tài)的切換.首先寫代碼測試一下,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)切換時,都會被observer監(jiān)聽到慈省。

我們再創(chuàng)建一個UITextView,拖動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

拖動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對象/關(guān)聯(lián)對象/多線程/內(nèi)存管理/性能優(yōu)化臀防,相關(guān)圖片素材均取自課程中的課件。如有侵權(quán)边败,請聯(lián)系我刪除清钥,謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末放闺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缕坎,更是在濱河造成了極大的恐慌怖侦,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谜叹,死亡現(xiàn)場離奇詭異匾寝,居然都是意外死亡,警方通過查閱死者的電腦和手機荷腊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門艳悔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人女仰,你說我怎么就攤上這事猜年。” “怎么了疾忍?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵乔外,是天一觀的道長。 經(jīng)常有香客問我一罩,道長杨幼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任聂渊,我火速辦了婚禮差购,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汉嗽。我一直安慰自己欲逃,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布诊胞。 她就那樣靜靜地躺著暖夭,像睡著了一般锹杈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迈着,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天竭望,我揣著相機與錄音,去河邊找鬼裕菠。 笑死咬清,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奴潘。 我是一名探鬼主播旧烧,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼画髓!你這毒婦竟也來了掘剪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤奈虾,失蹤者是張志新(化名)和其女友劉穎夺谁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肉微,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡匾鸥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碉纳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勿负。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖劳曹,靈堂內(nèi)的尸體忽然破棺而出奴愉,到底是詐尸還是另有隱情,我是刑警寧澤铁孵,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布躁劣,位于F島的核電站,受9級特大地震影響库菲,放射性物質(zhì)發(fā)生泄漏账忘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一熙宇、第九天 我趴在偏房一處隱蔽的房頂上張望鳖擒。 院中可真熱鬧,春花似錦烫止、人聲如沸蒋荚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽期升。三九已至惊奇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間播赁,已是汗流浹背颂郎。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留容为,地道東北人乓序。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像坎背,于是被迫代替她去往敵國和親替劈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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