Block深入分析

雖然網(wǎng)上Block已經(jīng)被寫爛了,自己還是覺得棋电,無論如何自己抱著求真的態(tài)度自己看一遍源碼。之前介紹過Block的基本使用Block苇侵。這一片將深入介紹一下Block的具體實現(xiàn)离陶。

真沒想到自己一直認為自己做了這么久的iOS不應(yīng)該再去糾結(jié)Block這樣的知識點,回頭一看衅檀,知識還是得溫故而知新招刨。

測試代碼

分析思路:使用clang -rewrite-objc將含有Block的.m文件翻譯為.cpp。對照著翻譯之后的源碼進行分析哀军。

現(xiàn)在定義如下幾個block沉眶。

//沒有捕獲變量
void blockFunc0()
{
    void (^block)(void) = ^{
        NSLog(@"num ");
    };
    block();
}

//普通局部變量
void blockFunc1()
{
    int num = 100;
    void (^block)(void) = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}
//普通__block局部變量
void blockFunc2()
{
    __block int num = 100;
    void (^block)(void) = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

//全局變量
int num = 100;
void blockFunc3()
{
    void (^block)(void) = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

//靜態(tài)變量
void blockFunc4()
{
    static int num = 100;
    void (^block)(void) = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

上面是準備的測試代碼

普通局部變量(blockFunc1)

blockFunc1和blockFunc0翻譯之后的差距就只是__blockFunc0_block_impl_0和__blockFunc1_block_impl_0結(jié)構(gòu)體中多了num這個字段,其余的都一樣杉适。所以這幾直接從blockFunc1講起谎倔。

執(zhí)行完clang -rewrite-objc命令之后會得到不少的警告,最終翻譯出來的文件比較大猿推。因為翻譯之后方法的名字不會變片习,所以可以通過搜索相關(guān)的方法名就能快速找到翻譯之后方法對應(yīng)的位置。

blockFunc1翻譯為(注意對比翻譯前后的結(jié)果

void blockFunc1()
{
    int num = 100;
    void (*block)(void) = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

根據(jù)上面的結(jié)果可以提取出如下幾個重要的數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)結(jié)構(gòu)

__block_impl

定義block的結(jié)構(gòu)體蹬叭,根據(jù)定義可以知道Block實際上就是對象藕咏,保存了一個ISA指針。那么如果是對象的話就很順利成長的把block的內(nèi)存秽五,生命周期管理同對象關(guān)聯(lián)起來孽查。目前block_impl的isa指針有_NSConcreteStackBlock、_NSConcreteGlobalBlock坦喘、_NSConcreteMallocBlock

struct __block_impl {
  void *isa;//表明Block實際上是個對象
  int Flags;
  int Reserved;
  void *FuncPtr;
};

字段對應(yīng)的含義

字段名 含義
isa isa 指向?qū)嵗龑ο竺ぴ伲砻?block 也是一個 Objective-C 對象。block 有三種類型:_NSConcreteStackBlock瓣铣、_NSConcreteGlobalBlock答朋、_NSConcreteMallocBlock,isa 對應(yīng)有三種值棠笑。因為block是對像梦碗,則就有對象的內(nèi)存管理及生命周期管理。恰恰三個對應(yīng)的值也就是這個作用。
Flags 按位表示一些block的附加信息
Reserved 保留變量
FuncPtr 函數(shù)指針叉弦,指向具體的block實現(xiàn)的函數(shù)調(diào)用地址

__blockFunc1_block_desc_0

保存對block的一些描述信息丐一,比如保留字段大小藻糖,以及block結(jié)構(gòu)大小淹冰。并且這里定義__blockFunc1_block_desc_0_DATA,并且還初始化了巨柒∮K可以看到初始化的情況下reserved為0,Block_size就是__blockFunc1_block_impl_0(包含了兩種結(jié)構(gòu)體的結(jié)構(gòu)體洋满,下面會介紹)大小晶乔。

static struct __blockFunc1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockFunc1_block_desc_0_DATA = { 0, sizeof(struct __blockFunc1_block_impl_0)};

字段名 含義
reserved 保留字段的大小
Block_size block結(jié)構(gòu)大小

__blockFunc1_block_impl_0

保存了block相關(guān)的信息,是前面兩種結(jié)構(gòu)體的結(jié)合體牺勾,包含了捕獲的外部變量正罢。比如這里的num,初始化的時候會初始化block_imp內(nèi)部變量驻民,如函數(shù)指針翻具,block_imp的對象類型。

struct __blockFunc1_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc1_block_desc_0* Desc;
  int num;//定義的變量
  //初始化傳入?yún)?shù)有函數(shù)指針回还,block的秒速
  __blockFunc1_block_impl_0(void *fp, struct __blockFunc1_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

字段對應(yīng)的含義

字段名 含義
impl block_imp結(jié)構(gòu)體信息
Desc 對block的附加描述
*** 對外部捕獲的變量

方法翻譯

Block內(nèi)容

Block里面的具體內(nèi)容被翻譯為了__blockFunc1_block_func_0函數(shù)裆泳,注意int num = __cself->num; // bound by copy表明了num的值是值接拷貝過去的。這一點將會隨著捕獲外部變量的作用域不同而不同柠硕,后面會總結(jié)工禾。

static void __blockFunc1_block_func_0(struct __blockFunc1_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_84gy29_n1dvdwnqfkytdv4nw0000gn_T_BlockTest_581040_mi_1, num);
    }

Block調(diào)用

這就是OC中的blockFunc1最終被翻譯的結(jié)果。

void blockFunc1()
{
    int num = 100;
    void (*block)(void) = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

注意void在C++中的一些不同蝗柔,比如void*表示“任意類型的指針”或表示“該指針與一地址值相關(guān)闻葵,但是不清楚在此地址上的對象的類型”。癣丧,可以簡單的理解為泛型的表示笙隙。 最終block被翻譯為了函數(shù)指針。

因為在__blockFunc1_block_impl_0創(chuàng)建的時候設(shè)置了最后一個參數(shù)flags=0的默認值坎缭,所以上面在創(chuàng)建__blockFunc1_block_impl_0沒有傳入?yún)?shù)flags竟痰。

這里還需要注意的就是使用了C++中的結(jié)構(gòu)體強轉(zhuǎn)類型。對應(yīng)的代碼的就是__blockFunc1_block_func_0轉(zhuǎn)為__block_impl類型掏呼。因為C++中坏快,只要高地址的數(shù)據(jù)類型相同(也就是首地址相同)就可以實現(xiàn)強轉(zhuǎn)。因為在__blockFunc1_block_func_0結(jié)構(gòu)體中憎夷。第一個數(shù)據(jù)類型就是__block_impl所以可以實現(xiàn)強轉(zhuǎn)莽鸿。

__block局部變量(blockFunc2)

上面通過了第一個例子分析了整個過程。后面加與不加block其實實現(xiàn)內(nèi)容都一樣。下面列舉幾個不同點祥得。

如果使用__block修飾變量兔沃,則在生成為__blockFunc2_block_impl_0的時候?qū)ν獠孔兞康男揎椃灰粯印>唧w來講對應(yīng)到下面的__Block_byref_num_0 *num; // by ref级及。同時在初始化__blockFunc2_block_impl_0的時候num會初始化為_num->__forwarding乒疏。

struct __blockFunc2_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc2_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockFunc2_block_impl_0(void *fp, struct __blockFunc2_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

數(shù)據(jù)結(jié)構(gòu)

_block_imp的結(jié)構(gòu)同上面一樣。

__Block_byref_num_0

和上面不同饮焦,但是多了一個__Block_byref_num_0怕吴。他的定義如下。

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

其中各個字段含義如下:

字段名 含義
__isa 對象指針
__forwarding 指向自己的指針
__flags 標志位
__size 結(jié)構(gòu)體大小
num 外部變量

__blockFunc2_block_impl_0

相比之前多了一個__Block_byref_num_0字段县踢。該字段在初始化的時候就已經(jīng)賦值了转绷。

struct __blockFunc2_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc2_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockFunc2_block_impl_0(void *fp, struct __blockFunc2_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__blockFunc2_block_desc_0

static struct __blockFunc2_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockFunc2_block_impl_0*, struct __blockFunc2_block_impl_0*);
  void (*dispose)(struct __blockFunc2_block_impl_0*);
} __blockFunc2_block_desc_0_DATA = { 0, sizeof(struct __blockFunc2_block_impl_0), __blockFunc2_block_copy_0, __blockFunc2_block_dispose_0};

相對于blockFunc1的翻譯結(jié)果多了一個copy的方法以及一個dispose方法,并且在初始化的時候就確定了這兩個方法硼啤。

后面會知道這兩個方法起的作用就是內(nèi)存管理的作用议经。

__blockFunc2_block_copy_0
static void __blockFunc2_block_copy_0(struct __blockFunc2_block_impl_0*dst, struct __blockFunc2_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

找到了_Block_object_assign的函數(shù)聲明為void _Block_object_assign(void *, const void *, const int);。當block field是指針類型的時候就會發(fā)生拷貝谴返。可以很明確的看到進行拷貝的其實是__blockFunc2_block_impl_0結(jié)構(gòu)體中的__Block_byref_num_0煞肾。

__blockFunc2_block_dispose_0
static void __blockFunc2_block_dispose_0(struct __blockFunc2_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

同樣最終釋放的也是__blockFunc2_block_impl_0中的__Block_byref_num_0。所以__Block_byref_num_0這個對象非常重要亏镰。

方法翻譯

Block內(nèi)容

追憶這里直接傳遞的是__Block_byref_num_0結(jié)構(gòu)體指針扯旷。在這個結(jié)構(gòu)體指針里面保存了之前的外部變量。使用到外部變量的時候直接從結(jié)構(gòu)體里面獲取索抓。

static void __blockFunc2_block_func_0(struct __blockFunc2_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_84gy29_n1dvdwnqfkytdv4nw0000gn_T_BlockTest_581040_mi_2, (num->__forwarding->num));
    }

block調(diào)用

void blockFunc2()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 100};
    void (*block)(void) = ((void (*)())&__blockFunc2_block_impl_0((void *)__blockFunc2_block_func_0, &__blockFunc2_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    (num.__forwarding->num) = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

首先創(chuàng)建了一個__Block_byref_num_0結(jié)構(gòu)體钧忽。里面記錄了自己的指針以及結(jié)構(gòu)體大小和外部變量的值100。在給外部變量賦值的時候使用了(num.__forwarding->num) = 200;逼肯,也通過指針的方式復制耸黑。這樣就達到改變外部變量值得目的。

情況分析

根據(jù)上面的內(nèi)容篮幢,當在使用__block修飾變量之后涩堤,之前直接出現(xiàn)外部變量(這里的num)的地方都被__Block_byref_num_0這個結(jié)構(gòu)體所替換換队询。并且在此基礎(chǔ)上還多了copy以及dispose方法。最終也是通過__Block_byref_num_0實現(xiàn)的內(nèi)存管理。

copy和dispose的作用如下:

  • 當blockFunc2從棧上被copy到堆上時蕴坪,會調(diào)用__blockFunc2_block_copy_0將blockFunc2類型的成員變量num(具體來講應(yīng)該是__Block_byref_num_0)從棧上復制到堆上蹬挺,而這個時候__forwarding指針就會指向堆上的結(jié)構(gòu)體缭乘;

  • 當blockFunc2被釋放時身坐,相應(yīng)地會調(diào)用__blockFunc2_block_dispose_0來釋放blockFunc2類型的成員變量num(具體來講應(yīng)該是__Block_byref_num_0)。同樣__forwarding指針指向堆上的結(jié)構(gòu)體也就被釋放蛋叼。

  • 為什么要這么做呢焊傅?其實也是為了保證block能夠訪問有效的正確內(nèi)存區(qū)域剂陡。

因為blockFunc2函數(shù)中的局部變量num和函數(shù)__blockFunc2_block_impl_0不在同一個作用域中,調(diào)用過程中只是進行了值傳遞狐胎。當然鸭栖,在上面代碼中,我們可以通過指針來實現(xiàn)局部變量的修改握巢。不過這是由于在調(diào)用__blockFunc2_block_impl_0時晕鹊,blockFunc2函數(shù)棧還沒展開完成,變量num還在棧中镜粤。但是在很多情況下捏题,block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的玻褪。通常在這些情況下肉渴,block被執(zhí)行時,定義時所在的函數(shù)棧已經(jīng)被展開带射,局部變量已經(jīng)不在棧中了已經(jīng)被銷毀了同规,再用指針訪問就會報常見的壞內(nèi)存訪問。

因為block可以作為屬性窟社,并且也經(jīng)常作為參數(shù)傳遞券勺,而Block最終展開的是一個函數(shù),展開的函數(shù)里面的變量作用域和被block被調(diào)用的函數(shù)作用域是不同的灿里。通常在情況下关炼,block被執(zhí)行時,定義時所在的函數(shù)棧已經(jīng)被展開匣吊,局部變量已經(jīng)不在棧中了已經(jīng)被銷毀了儒拂,再用指針訪問就會報常見的壞內(nèi)存訪問。因此block有copy方法將其在堆上色鸳,所以在block被copy的同時社痛,將局部變量間接也copy放在堆上就能夠保證局部變量可以被block正常訪問到。具體來講可以看看下面這張圖命雀,最終是通過__forwarding指針來實現(xiàn)這個目的:

全局變量(blockFunc3)

這種情況下轉(zhuǎn)換的結(jié)果block和不捕獲任何變量的block結(jié)果是一樣的蒜哀。

struct __blockFunc0_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc0_block_desc_0* Desc;
  __blockFunc0_block_impl_0(void *fp, struct __blockFunc0_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int num = 100;//注意,這里已經(jīng)聲明了一個num為全局變量

struct __blockFunc3_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc3_block_desc_0* Desc;
  __blockFunc3_block_impl_0(void *fp, struct __blockFunc3_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

因為是全局變量吏砂,所以任何地方都可以修改撵儿。跟block沒什么關(guān)系(因為block最終也是轉(zhuǎn)換為函數(shù)來調(diào)用)。

static局部變量(blockFunc4)

這種情況下重點看一下如下幾點:

__blockFunc4_block_impl_0中保持的是int *num;也即是指針狐血。

struct __blockFunc4_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc4_block_desc_0* Desc;
  int *num;
  __blockFunc4_block_impl_0(void *fp, struct __blockFunc4_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block調(diào)用被轉(zhuǎn)換為:

void blockFunc4()
{
    static int num = 100;
    void (*block)(void) = ((void (*)())&__blockFunc4_block_impl_0((void *)__blockFunc4_block_func_0, &__blockFunc4_block_desc_0_DATA, &num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

注意這里聲明了static int num = 100;所以傳遞的是指針淀歇,和blockFunc1中最大的不同就是int num 變成了int *num 由整型變成了整型指針類型,注意:這個不再是傳值氛雪,而是傳地址房匆,這說明對應(yīng) static 修飾的自動變量值在被 block 截獲之后仍可以與外部自動變量保持同步,因為它們的地址是同一個。

Block內(nèi)存管理

Block一共有三種類型浴鸿,三面介紹的幾種block全是_NSConcreteGlobalBlock井氢。


_NSConcreteGlobalBlock

經(jīng)過測試只有當 block 字面量寫在全局作用域時,即為 global block岳链。而且僅此一種花竞,網(wǎng)上有人當 block 字面量不獲取任何外部變量時也是global block,但是經(jīng)過自己測試還是_NSConcreteStackBlock類型掸哑。

globalblock如下形式:

_NSConcreteStackBlock

這種類型block是最多的一種约急,處于內(nèi)存的棧區(qū),如果其變量作用域結(jié)束苗分,這個 block 就被廢棄厌蔽,block 上的 __block 變量也同樣會被廢棄。


block 提供了 copy 的功能摔癣,將 block 和 __block 變量從椗拷貝到堆,就是 _NSConcreteMallocBlock择浊。

_NSConcreteMallocBlock

當 block 從棿鞑罚拷貝到堆后,當棧上變量作用域結(jié)束時琢岩,仍然可以繼續(xù)使用 block

堆上的 block 類型為 _NSConcreteMallocBlock投剥,所以會將 _NSConcreteMallocBlock 寫入 isa。對應(yīng)到代碼上就是impl.isa = &_NSConcreteMallocBlock担孔。

ARC 下的 block

在開啟 ARC 時江锨,大部分情況下編譯器通常會將創(chuàng)建在棧上的 block 自動拷貝到堆上。
block 作為函數(shù)的參數(shù)傳遞時攒磨,編譯器不會自動調(diào)用 copy 方法

如下這幾種情況都不用手動拷貝

  • 當 block 作為函數(shù)返回值返回時泳桦,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法娩缰;
  • 當 block 被賦值給 __strong id 類型的對象或 block 的成員變量時灸撰,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法拼坎;
  • 當 block 作為參數(shù)被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時浮毯。這些方法會在內(nèi)部對傳遞進來的 block 調(diào)用 copy 或 _Block_copy 拷貝;

比如自動拷貝的情況:

/************ ARC下編譯器自動拷貝block ************/
typedef int (^blk_t)(int);
blk_t func(int rate)
{
    return ^(int count){return rate * count;};
}

如果沒有自動拷貝,因為外部傳入的參數(shù)放到的是棧上泰鸡,如果后面去調(diào)用這個返回的block肯定會發(fā)生異常债蓝,但是在ARC下面不會出問題,于是翻譯一下盛龄∈渭#可以查到如下代碼

blk_t func(int rate)
{
    blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp); 
}

由于 block 字面量是創(chuàng)建在棧內(nèi)存芳誓,通過 objc_retainBlock() 函數(shù)拷貝到堆內(nèi)存,讓 tmp 重新指向堆上的 block啊鸭,然后將 tmp 所指的堆上的 block 作為一個 Objective-C 對象放入 autoreleasepool 里面锹淌,從而保證了返回后的 block 仍然可以正確執(zhí)行。

需要手動執(zhí)行copy的block:

/************ ARC下編譯器手動拷貝block ************/
id getBlockArray()
{
    int val = 10;
    return [[NSArray alloc] initWithObjects: 
                            ^{NSLog(@"blk0:%d", val);}, 
                            ^{NSLog(@"blk1:%d", val);}, nil];
}

這里block最為了函數(shù)參數(shù)赠制,編譯器不會自動拷貝赂摆。所以在調(diào)用這個方法的時候會出現(xiàn)異常。

總結(jié)

Block原理總算是講完了钟些。其難點就是對于外部變量的捕獲場景比較多烟号。通過分析源碼,比較麻煩的就是加了__block的情況政恍。通過__forwarding指針達到棧和堆上面的切換汪拥。這里沒有列舉對象類型的例子原因是對象類型其實就是一個指針。簡單來講就是把上面的num換成指針類型而已抚垃,其余的規(guī)則都是一樣的喷楣。

__block的作用可以簡單總結(jié)為趟大,處理兩個函數(shù)作用域訪問訪問變量的時候鹤树,防止壞內(nèi)存訪問。

后面講了Block的幾種類型以及相關(guān)內(nèi)存管理逊朽,同堆棧一樣罕伯。最后將了ARC環(huán)境下Block的注意事項。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叽讳,一起剝皮案震驚了整個濱河市追他,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岛蚤,老刑警劉巖邑狸,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涤妒,居然都是意外死亡单雾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門她紫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硅堆,“玉大人,你說我怎么就攤上這事贿讹〗ヌ樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵民褂,是天一觀的道長茄菊。 經(jīng)常有香客問我疯潭,道長,這世上最難降的妖魔是什么面殖? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任袁勺,我火速辦了婚禮,結(jié)果婚禮上畜普,老公的妹妹穿的比我還像新娘期丰。我一直安慰自己,他們只是感情好吃挑,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布钝荡。 她就那樣靜靜地躺著,像睡著了一般舶衬。 火紅的嫁衣襯著肌膚如雪埠通。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天逛犹,我揣著相機與錄音端辱,去河邊找鬼。 笑死虽画,一個胖子當著我的面吹牛舞蔽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播码撰,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渗柿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脖岛?” 一聲冷哼從身側(cè)響起朵栖,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柴梆,沒想到半個月后陨溅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡绍在,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年门扇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揣苏。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悯嗓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卸察,到底是詐尸還是另有隱情脯厨,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布坑质,位于F島的核電站合武,受9級特大地震影響临梗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稼跳,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一盟庞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汤善,春花似錦什猖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至在旱,卻和暖如春摇零,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桶蝎。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工驻仅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人登渣。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓噪服,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绍豁。 傳聞我的和親對象是個殘疾皇子芯咧,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355