autoreleasepool原理分析

@autoreleasepool {
    // Code benefitting from a local autorelease pool.
}

1、原理分析

1.1攀例、__AtAutoreleasePool

下面我們先通過(guò)macOS工程來(lái)分析@autoreleasepool的底層原理沼本。 macOS工程中的main()函數(shù)什么都沒(méi)做,只是放了一個(gè)@autoreleasepool笑窜。

int main(int argc, const char * argv[]) {
    @autoreleasepool {}
    return 0;
}

通過(guò) Clang clang -rewrite-objc main.m 將以上代碼轉(zhuǎn)換為 C++ 代碼。

struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 構(gòu)造函數(shù)瓤檐,在創(chuàng)建結(jié)構(gòu)體的時(shí)候調(diào)用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() { // 析構(gòu)函數(shù)赂韵,在結(jié)構(gòu)體銷毀的時(shí)候調(diào)用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ 
    { __AtAutoreleasePool __autoreleasepool;  }
    return 0;
}

可以看到:

  • @autoreleasepool底層是創(chuàng)建了一個(gè)__AtAutoreleasePool結(jié)構(gòu)體對(duì)象;

  • 在創(chuàng)建__AtAutoreleasePool結(jié)構(gòu)體時(shí)會(huì)在構(gòu)造函數(shù)中調(diào)用objc_autoreleasePoolPush()函數(shù)挠蛉,并返回一個(gè)atautoreleasepoolobj(POOL_BOUNDARY存放的內(nèi)存地址祭示,下面會(huì)講到);

  • 在釋放__AtAutoreleasePool結(jié)構(gòu)體時(shí)會(huì)在析構(gòu)函數(shù)中調(diào)用objc_autoreleasePoolPop()函數(shù)谴古,并將atautoreleasepoolobj傳入绍移。

1.2、AutoreleasePoolPage

下面我們進(jìn)入Runtime objc4源碼查看以上提到的兩個(gè)函數(shù)的實(shí)現(xiàn)讥电。

// NSObject.mm
void * objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

可以得知蹂窖,
objc_autoreleasePoolPush()objc_autoreleasePoolPop()兩個(gè)函數(shù)其實(shí)是調(diào)用了AutoreleasePoolPage類的兩個(gè)類方法push()pop()。所以@autoreleasepool底層就是使用AutoreleasePoolPage類來(lái)實(shí)現(xiàn)的恩敌。

自動(dòng)釋放池的數(shù)據(jù)結(jié)構(gòu)

  • 自動(dòng)釋放池的主要數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool瞬测、AutoreleasePoolPage
  • 調(diào)用了 autorelease的對(duì)象最終都是通過(guò) AutoreleasePoolPage對(duì)象來(lái)管理的纠炮;

下面我們來(lái)看一下AutoreleasePoolPage類的定義:

class AutoreleasePoolPage 
{
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  // EMPTY_POOL_PLACEHOLDER:表示一個(gè)空自動(dòng)釋放池的占位符
#   define POOL_BOUNDARY nil                // POOL_BOUNDARY:哨兵對(duì)象
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;   // 用來(lái)標(biāo)記已釋放的對(duì)象
    static size_t const SIZE =              // 每個(gè) Page 對(duì)象占用 4096 個(gè)字節(jié)內(nèi)存
#if PROTECT_AUTORELEASEPOOL                 // PAGE_MAX_SIZE = 4096
        PAGE_MAX_SIZE;  // must be muliple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);  // Page 的個(gè)數(shù)

    magic_t const magic;                // 用來(lái)校驗(yàn) Page 的結(jié)構(gòu)是否完整
    id *next;                           // 指向下一個(gè)可存放 autorelease 對(duì)象地址的位置月趟,初始化指向 begin()
    pthread_t const thread;             // 指向當(dāng)前線程
    AutoreleasePoolPage * const parent; // 指向父結(jié)點(diǎn),首結(jié)點(diǎn)的 parent 為 nil
    AutoreleasePoolPage *child;         // 指向子結(jié)點(diǎn)恢口,尾結(jié)點(diǎn)的 child  為 nil
    uint32_t const depth;               // Page 的深度孝宗,從 0 開(kāi)始遞增
    uint32_t hiwat;
    ......
}

整個(gè)程序運(yùn)行過(guò)程中,可能會(huì)有多個(gè)AutoreleasePoolPage對(duì)象耕肩。從它的定義可以得知:

  • 自動(dòng)釋放池(即所有的AutoreleasePoolPage對(duì)象)是以棧為結(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成因妇;

  • 自動(dòng)釋放池與線程一一對(duì)應(yīng);

  • 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存猿诸,其中56個(gè)字節(jié)用來(lái)存放它內(nèi)部的成員變量婚被,剩下的空間(4040個(gè)字節(jié))用來(lái)存放autorelease對(duì)象的地址

其內(nèi)存分布圖如下:


圖片.png

1.2.1梳虽、POOL_BOUNDARY

在分析這些方法之前址芯,先介紹一下POOL_BOUNDARY

  • POOL_BOUNDARY的前世叫做POOL_SENTINEL窜觉,稱為哨兵對(duì)象或者邊界對(duì)象谷炸;
  • POOL_BOUNDARY用來(lái)區(qū)分不同的自動(dòng)釋放池,以解決自動(dòng)釋放池嵌套的問(wèn)題禀挫;
  • 每當(dāng)創(chuàng)建一個(gè)自動(dòng)釋放池旬陡,就會(huì)調(diào)用push()方法將一個(gè)POOL_BOUNDARY入棧,并返回其存放的內(nèi)存地址特咆;
  • 當(dāng)往自動(dòng)釋放池中添加autorelease對(duì)象時(shí)季惩,將autorelease對(duì)象的內(nèi)存地址入棧录粱,它們前面至少有一個(gè)POOL_BOUNDARY腻格;
  • 當(dāng)銷毀一個(gè)自動(dòng)釋放池時(shí)画拾,會(huì)調(diào)用pop()方法并傳入一個(gè)POOL_BOUNDARY,會(huì)從自動(dòng)釋放池中最后一個(gè)對(duì)象開(kāi)始菜职,依次給它們發(fā)送release消息青抛,直到遇到這個(gè)POOL_BOUNDARY

1.2.2酬核、push

    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) { // 出錯(cuò)時(shí)進(jìn)入調(diào)試狀態(tài)
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);  // 傳入 POOL_BOUNDARY 哨兵對(duì)象
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

當(dāng)創(chuàng)建一個(gè)自動(dòng)釋放池時(shí)蜜另,會(huì)調(diào)用push()方法。push()方法中調(diào)用了autoreleaseFast()方法并傳入了POOL_BOUNDARY哨兵對(duì)象嫡意。
下面我們來(lái)看一下autoreleaseFast()方法的實(shí)現(xiàn):

    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();     // 雙向鏈表中的最后一個(gè) Page
        if (page && !page->full()) {        // 如果當(dāng)前 Page 存在且未滿
            return page->add(obj);                 // 將 autorelease 對(duì)象入棧举瑰,即添加到當(dāng)前 Page 中;
        } else if (page) {                  // 如果當(dāng)前 Page 存在但已滿
            return autoreleaseFullPage(obj, page); // 創(chuàng)建一個(gè)新的 Page蔬螟,并將 autorelease 對(duì)象添加進(jìn)去
        } else {                            // 如果當(dāng)前 Page 不存在此迅,即還沒(méi)創(chuàng)建過(guò) Page
            return autoreleaseNoPage(obj);         // 創(chuàng)建第一個(gè) Page,并將 autorelease 對(duì)象添加進(jìn)去
        }
    }

autoreleaseFast()中先是調(diào)用了hotPage()方法獲得未滿的Page旧巾,從AutoreleasePoolPage類的定義可知耸序,每個(gè)Page的內(nèi)存大小為 4096個(gè)字節(jié),每當(dāng)Page滿了的時(shí)候鲁猩,就會(huì)創(chuàng)建一個(gè)新的Page坎怪。hotPage()方法就是用來(lái)獲得這個(gè)新創(chuàng)建的未滿的Page
autoreleaseFast()在執(zhí)行過(guò)程中有三種情況:

  • ① 當(dāng)前Page存在且未滿時(shí)廓握,通過(guò)page->add(obj)將autorelease對(duì)象入棧搅窿,即添加到當(dāng)前Page中;
    ② 當(dāng)前Page存在但已滿時(shí)隙券,通過(guò)autoreleaseFullPage(obj, page)創(chuàng)建一個(gè)新的Page戈钢,并將autorelease對(duì)象添加進(jìn)去;
    ③ 當(dāng)前Page不存在是尔,即還沒(méi)創(chuàng)建過(guò)Page殉了,通過(guò)autoreleaseNoPage(obj)創(chuàng)建第一個(gè)Page,并將autorelease對(duì)象添加進(jìn)去拟枚。

1.2.3薪铜、pop

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

pop()方法的傳參token即為POOL_BOUNDARY對(duì)應(yīng)在Page中的地址。當(dāng)銷毀自動(dòng)釋放池時(shí)恩溅,會(huì)調(diào)用pop()方法將自動(dòng)釋放池中的autorelease對(duì)象全部釋放(實(shí)際上是從自動(dòng)釋放池的中的最后一個(gè)入棧的autorelease對(duì)象開(kāi)始隔箍,依次給它們發(fā)送一條release消息,直到遇到這個(gè)POOL_BOUNDARY)脚乡。pop()方法的執(zhí)行過(guò)程如下:

  • ① 判斷token是不是EMPTY_POOL_PLACEHOLDER蜒滩,是的話就清空這個(gè)自動(dòng)釋放池滨达;
  • ② 如果不是的話,就通過(guò)pageForPointer(token)拿到token所在的Page(自動(dòng)釋放池的首個(gè)Page)俯艰;
  • ③ 通過(guò)page->releaseUntil(stop)將自動(dòng)釋放池中的autorelease對(duì)象全部釋放捡遍,傳參stop即為POOL_BOUNDARY的地址;
  • ④ 判斷當(dāng)前Page是否有子Page竹握,有的話就銷毀画株。

1.2.4、begin啦辐、end谓传、empty、full

下面再來(lái)看一下begin芹关、end续挟、emptyfull這些方法的實(shí)現(xiàn)侥衬。

  • begin的地址為:Page自己的地址+Page對(duì)象的大小56個(gè)字節(jié)诗祸;
  • end的地址為:Page自己的地址+4096個(gè)字節(jié);
  • empty判斷Page是否為空的條件是next地址是不是等于begin浇冰;
  • full判斷Page是否已滿的條件是next地址是不是等于end(棧頂)贬媒。
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }

2、查看自動(dòng)釋放池的情況

可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況:

extern void _objc_autoreleasePoolPrint(void);

3肘习、iOS 工程示例分析

在iOS工程中际乘,方法里的autorelease對(duì)象是什么時(shí)候釋放的呢?

有系統(tǒng)干預(yù)釋放和手動(dòng)干預(yù)釋放兩種情況漂佩。

  • 系統(tǒng)干預(yù)釋放是不指定@autoreleasepool脖含,所有autorelease對(duì)象都由主線程的RunLoop創(chuàng)建的@autoreleasepool來(lái)管理。
  • 手動(dòng)干預(yù)釋放就是將autorelease對(duì)象添加進(jìn)我們手動(dòng)創(chuàng)建的@autoreleasepool中投蝉。

下面還是在MRC環(huán)境下進(jìn)行分析养葵。

3.1、系統(tǒng)干預(yù)釋放

我們先來(lái)看以下 Xcode 11 版本的iOS程序中的main()函數(shù)瘩缆,和舊版本的差異关拒。

// Xcode 11
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
// Xcode 舊版本
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

新版本 Xcode 11 中的 main 函數(shù)發(fā)生了哪些變化?
舊版本是將整個(gè)應(yīng)用程序運(yùn)行放在@autoreleasepool內(nèi)庸娱,由于RunLoop的存在着绊,要return即程序結(jié)束后@autoreleasepool作用域才會(huì)結(jié)束,這意味著程序結(jié)束后main函數(shù)中的@autoreleasepool中的autorelease對(duì)象才會(huì)釋放熟尉。
而在 Xcode 11中归露,觸發(fā)主線程RunLoop的UIApplicationMain函數(shù)放在了@autoreleasepool外面,這可以保證@autoreleasepool中的autorelease對(duì)象在程序啟動(dòng)后立即釋放斤儿。正如新版本的@autoreleasepool中的注釋所寫 “Setup code that might create autoreleased objects goes here.”(如上代碼)剧包,可以將autorelease對(duì)象放在此處恐锦。

接著我們來(lái)看 “系統(tǒng)干預(yù)釋放” 情況的示例:

- (void)viewDidLoad {
    [super viewDidLoad];    
    Person *person = [[[Person alloc] init] autorelease];    
    NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];    
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];    
    NSLog(@"%s", __func__);
}

// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[Person dealloc]
// -[ViewController viewDidAppear:]

可以看到,調(diào)用了autorelease方法的person對(duì)象不是在viewDidLoad方法結(jié)束后釋放疆液,而是在viewWillAppear方法結(jié)束后釋放一铅,說(shuō)明在viewWillAppear方法結(jié)束的時(shí)候,調(diào)用了pop()方法釋放了person對(duì)象枚粘。
其實(shí)這是由RunLoop控制的馅闽,下面來(lái)講解一下RunLoop@autoreleasepool的關(guān)系飘蚯。

3.2馍迄、RunLoop 與 @autoreleasepool

iOS在主線程的RunLoop中注冊(cè)了兩個(gè)Observer

第1個(gè)Observer

  • 監(jiān)聽(tīng)了kCFRunLoopEntry事件局骤,會(huì)調(diào)用objc_autoreleasePoolPush()攀圈;

第2個(gè)Observer

  • ① 監(jiān)聽(tīng)了kCFRunLoopBeforeWaiting事件,會(huì)調(diào)用objc_autoreleasePoolPop()峦甩、objc_autoreleasePoolPush()赘来;
  • ② 監(jiān)聽(tīng)了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()凯傲。
圖片.png

所以犬辰,在iOS工程中系統(tǒng)干預(yù)釋放的autorelease對(duì)象的釋放時(shí)機(jī)是由RunLoop控制的,會(huì)在當(dāng)前RunLoop每次循環(huán)結(jié)束時(shí)釋放冰单。以上person對(duì)象在viewWillAppear方法結(jié)束后釋放幌缝,說(shuō)明viewDidLoadviewWillAppear方法在同一次循環(huán)里。

  • kCFRunLoopEntry:在即將進(jìn)入RunLoop時(shí)诫欠,會(huì)自動(dòng)創(chuàng)建一個(gè)__AtAutoreleasePool結(jié)構(gòu)體對(duì)象涵卵,并調(diào)用objc_autoreleasePoolPush()函數(shù)。
    -kCFRunLoopBeforeWaiting:在RunLoop即將休眠時(shí)荒叼,會(huì)自動(dòng)銷毀一個(gè)__AtAutoreleasePool對(duì)象,調(diào)用objc_autoreleasePoolPop()。然后創(chuàng)建一個(gè)新的__AtAutoreleasePool對(duì)象潮针,并調(diào)用objc_autoreleasePoolPush()谅海。
  • kCFRunLoopBeforeExit,在即將退出RunLoop時(shí)嫁乘,會(huì)自動(dòng)銷毀最后一個(gè)創(chuàng)建的__AtAutoreleasePool對(duì)象昆婿,并調(diào)用objc_autoreleasePoolPop()

3.3亦渗、手動(dòng)干預(yù)釋放

我們?cè)賮?lái)看一下手動(dòng)干預(yù)釋放的情況挖诸。

- (void)viewDidLoad {
    [super viewDidLoad];    
    @autoreleasepool {
        Person *person = [[[Person alloc] init] autorelease];  
    }  
    NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];    
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];    
    NSLog(@"%s", __func__);
}

// -[Person dealloc]
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[ViewController viewDidAppear:]

可以看到,添加進(jìn)手動(dòng)指定的@autoreleasepool中的autorelease對(duì)象法精,在@autoreleasepool大括號(hào)結(jié)束時(shí)就會(huì)釋放多律,不受RunLoop控制痴突。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狼荞,隨后出現(xiàn)的幾起案子辽装,更是在濱河造成了極大的恐慌,老刑警劉巖相味,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾积,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丰涉,警方通過(guò)查閱死者的電腦和手機(jī)拓巧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)一死,“玉大人肛度,你說(shuō)我怎么就攤上這事投慈。” “怎么了加袋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵职烧,是天一觀的道長(zhǎng)蝙砌。 經(jīng)常有香客問(wèn)我择克,道長(zhǎng),這世上最難降的妖魔是什么壹堰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任贱纠,我火速辦了婚禮谆焊,結(jié)果婚禮上浦夷,老公的妹妹穿的比我還像新娘辖试。我一直安慰自己辜王,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布罐孝。 她就那樣靜靜地躺著呐馆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莲兢。 梳的紋絲不亂的頭發(fā)上汹来,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音改艇,去河邊找鬼收班。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遣耍,可吹牛的內(nèi)容都是我干的闺阱。 我是一名探鬼主播炮车,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼舵变,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瘦穆?” 一聲冷哼從身側(cè)響起纪隙,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扛或,失蹤者是張志新(化名)和其女友劉穎悲伶,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體花沉,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年打毛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绑雄。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洽腺,死狀恐怖蘸朋,靈堂內(nèi)的尸體忽然破棺而出团南,到底是詐尸還是另有隱情吐根,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站术幔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勾缭。R本人自食惡果不足惜毒嫡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伐蒂。 院中可真熱鬧,春花似錦缕减、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春融虽,著一層夾襖步出監(jiān)牢的瞬間般又,已是汗流浹背茴迁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洒擦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓邦危,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親陵且。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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