深入淺出block閉包

一句話總結(jié)block : 帶有局部變量的匿名函數(shù)

閉包在其它編程語言的名稱

編程語言 Block名稱
C+Block Block
Smalltalk Block
Ruby Block
LISP Lambda
Python Lambda
C++ 11 Lambda
Javascript Anonymous function

iOS閉包的聲明與定義

博主iOS開發(fā)哮伟,下面從iOS角度入手講解如何理解block閉包:
在OC中聲明一個block的形式如下:

返回值類型  (^block名)  (形參列表) 

在OC中定義一個block的形式如下:

^ 返回值類型  (形參列表)  {表達式}

舉一個賦值的例子:

int (^blk) (int a , int b) = ^ int (int a , int b) { return a + b;};

這是規(guī)范的寫法绸狐,有時我們也可以使用簡略后的寫法:

^ 返回值類型  (形參列表)  {表達式}
  (省略)     (如空施绎,省略)

上面的例子變成:

int (^blk) (int a , int b) = ^(int a , int b) {return a + b;};

但是搪哪,每次聲明block時都需要書寫這么長的表達式顯得不那么方便亲族,我們像使用函數(shù)指針類型時那樣崔慧,使用typedef來解決該問題:

typedef int (^blk_t) (int a , int b);

這樣在聲明一個block變量時拂蝎,就變?yōu)椋?/p>

blk_t blk;
blk = ^(int a , int b ) {return a + b;}

從C的角度看block閉包

block的實質(zhì)

clang(LLVM編譯器)的"-rewrite-objc"選項可將Objective-C的源代碼重寫成C++的源代碼,我們可以將帶有block的OC代碼轉(zhuǎn)成C++代碼惶室,看一看蘋果的實現(xiàn)温自。

clang -rewrite-objc 源代碼文件名

寫一段帶有block的代碼:

typedef int (^blk_t) (int a , int b);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        blk_t blk = ^ int (int a , int b) {
            return a + b;
        };
        
        int c = blk(1,2);
        NSLog(@"%d",c);
    }
    return 0;
}

轉(zhuǎn)換成C++代碼 (由于生成大量的輔助代碼,這里只展示關(guān)鍵代碼)

typedef int (*blk_t) (int a , int b);

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  return a + b;
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blk_t blk = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        int c = ((int (*)(__block_impl *, int, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 1, 2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_a3fdb5_mi_4,c);
    }
    return 0;
}

在C++源碼中皇钞,block最終被轉(zhuǎn)化成函數(shù)來處理悼泌,blk_t閉包被轉(zhuǎn)換成了結(jié)構(gòu)體,blk_t類型的blk變量初始化時調(diào)用了__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)夹界,blk中存儲的是構(gòu)造后的__main_block_impl_0結(jié)構(gòu)體的首地址馆里。
下面來分解下上面的代碼:

//block轉(zhuǎn)化后的結(jié)構(gòu)體類型 
struct __main_block_impl_0 {
  //成員結(jié)構(gòu)體變量 保存block需要調(diào)用的函數(shù)地址和一些輔助信息 
  struct __block_impl impl;
  //成員結(jié)構(gòu)體變量 保存block大小和一個保留字段
  struct __main_block_desc_0* Desc;
  //構(gòu)造函數(shù) 函數(shù)名是由編譯器根據(jù)block所處的函數(shù)名和block在該函數(shù)中出現(xiàn)的順序值 (此處0)所生成的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//記錄block需要調(diào)用的函數(shù)地址和一些輔助信息
struct __block_impl {
  //指向class_t結(jié)構(gòu)體實例 
  void *isa;
  //一般為0
  int Flags;
  //保留字段 為后續(xù)升級做準備
  int Reserved;
  //需要調(diào)用的函數(shù)地址指針
  void *FuncPtr;
};
//block大小描述結(jié)構(gòu)體
static struct __main_block_desc_0 {
  //保留字段 為后續(xù)升級做準備
  size_t reserved;
  //描述block大小的結(jié)構(gòu)體
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};//直接進行賦值
//block所需調(diào)用的函數(shù)的定義(static代表函數(shù)的作用域僅限于本文件,不必擔心與其他文件中的函數(shù)同名)
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  return a + b;
}

上面的函數(shù)參數(shù)表中可柿,多了一個struct __main_block_impl_0 *__cself參數(shù)鸠踪,這個參數(shù)類似于C++中的this,類似于OC中的self复斥,是用來指向調(diào)用函數(shù)本身對象的指針营密,因為我們可能會在對象的函數(shù)體內(nèi)進行一些對象的成員變量的獲取或修改或調(diào)用對象的其他成員函數(shù),因為block所調(diào)用的函數(shù)被解釋成了一個C語言形式全局函數(shù)目锭,這個函數(shù)并不知道是哪個對象在調(diào)用它评汰,所以我們需要將調(diào)用它的對象指針傳遞進去纷捞。即使這個函數(shù)沒有int a,int b這兩的參數(shù),參數(shù)表為空被去,struct __main_block_impl_0 *__cself參數(shù)也是必不可少的主儡,必須作為函數(shù)參數(shù)表中的第0個參數(shù)傳入。

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        //blk變量的初始化惨缆,調(diào)用__main_block_impl_0的構(gòu)造函數(shù)糜值,并將其轉(zhuǎn)化成了block
        blk_t blk = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        //將__main_block_impl_0類型的結(jié)構(gòu)體轉(zhuǎn)換成__block_impl類型結(jié)構(gòu)體,并獲取需要調(diào)用的函數(shù)指針踪央,調(diào)用函數(shù)
        int c = ((int (*)(__block_impl *, int, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 1, 2);
        //輸出返回值
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_a3fdb5_mi_4,c);
    }
    return 0;
}

因為__block_impl結(jié)構(gòu)體是__main_block_impl_0結(jié)構(gòu)體的成員變量臀玄,且是第一個成員變量,那么__main_block_impl_0的指針和__block_impl指針的值是相同的(如果不太明白可以去查下結(jié)構(gòu)體在內(nèi)存上的分配)畅蹂,所以在main函數(shù)中可以將__main_block_impl_0的指針強轉(zhuǎn)成__block_impl的指針健无。
在main中,去掉轉(zhuǎn)化部分液斜,便簡化為:

(*blk->impl.FuncPtr)(blk,1,2);

總結(jié):block類型的變量累贤,會被轉(zhuǎn)換成 : 結(jié)構(gòu)體 + 函數(shù)的形式,在內(nèi)存上的形式時是以結(jié)構(gòu)體變量進行存儲的少漆。結(jié)構(gòu)體當中保存有調(diào)用函數(shù)的指針臼膏,當block發(fā)生調(diào)用時,會從結(jié)構(gòu)體中將函數(shù)指針的值取出示损,根據(jù)函數(shù)指針來調(diào)用函數(shù)渗磅,以此便實現(xiàn)了block的調(diào)用。

block捕獲變量

在程序中检访,共有如下幾種類型的變量:

  • 局部變量
  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量
    block的變量完全符合上述類型變量始鱼。
1) block捕獲局部變量
typedef void (^blk_t1) (void);
int main(int argc, const char * argv[]) {
        //捕獲局部變量
        int d = 10;
        blk_t1 blk1 = ^ {
            NSLog(@"%d",d);
        };
        d = 11;
        blk1();
}

打印的結(jié)果為:

10

雖然局部變量d的值實在調(diào)用block之前修改的,但是block打印出的值仍未修改之前的脆贵,這說明block捕獲的是局部變量瞬時的值医清。 下面將源碼轉(zhuǎn)換成C++源碼:

//其他結(jié)構(gòu)同上面block實質(zhì)中代碼相同 這里展示不同部分
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int d;
  __main_block_impl_0(void *fp, struct __main_block_desc_1 *desc, int _d, int flags=0) : d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
int main(int argc, const char * argv[]) {
        int d = 10;
        blk_t1 blk1 = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, d));
        d = 11;
        ((void (*)(__block_impl *))((__block_impl *)blk1)->FuncPtr)((__block_impl *)blk1);
}
//簡化后
int main(int argc, const char * argv[]) {
        int d = 10;
        blk_t1 blk1 = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, d));
        d = 11;
        (*blk1->impl.FuncPtr)(blk1);
}

可以看到__main_block_impl_0結(jié)構(gòu)體中多出了一個int d成員變量,在main中d修改之前d就已經(jīng)作為參數(shù)傳遞給了__main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)了卖氨,這時__main_block_impl_0結(jié)構(gòu)體中的int d成員的值已經(jīng)賦值完畢会烙,在調(diào)用block時就會打印成員變量d的值。所以局部變量d的值的改變不會再影響到block中的d的值筒捺。

2)修改局部變量
typedef void (^blk_t1) (void);
int main(int argc, const char * argv[]) {
        //修改局部變量
        int d = 10;
        blk_t1 blk1 = ^ {
            d = 12;
            NSLog(@"%d",d);
        };
        blk1();
        NSLog(@"%d",d);
}

這樣是行不通的柏腻,直接報錯:

Variable is not assignable (missing __block type specifier)

提示我們?nèi)鄙?code>__block修飾符。更改后代碼如下:

typedef void (^blk_t1) (void);
int main(int argc, const char * argv[]) {
        //修改局部變量
        __block int d1 = 10;
        blk_t1 blk1 = ^ {
            d1 = 12;
            NSLog(@"%d",d1);
        };
        blk1();
        NSLog(@"%d",d1);
}

打印的結(jié)果為:

12
12

達到預(yù)期系吭,下面將源碼轉(zhuǎn)換成C++源碼:

struct __Block_byref_d1_0 {
  void *__isa;
__Block_byref_d1_0 *__forwarding;
 int __flags;
 int __size;
 int d1;
};

struct __main_block_impl_2 {
  struct __block_impl impl;
  struct __main_block_desc_2* Desc;
  __Block_byref_d1_0 *d1; // by ref
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, __Block_byref_d1_0 *_d1, int flags=0) : d1(_d1->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_2(struct __main_block_impl_2 *__cself) {
  __Block_byref_d1_0 *d1 = __cself->d1; // bound by ref

            (d1->__forwarding->d1) = 12;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_c7e4e3_mi_2,(d1->__forwarding->d1));
}

int main(int argc, const char * argv[]) {
        __attribute__((__blocks__(byref))) __Block_byref_d1_0 d1 = {(void*)0,(__Block_byref_d1_0 *)&d1, 0, sizeof(__Block_byref_d1_0), 10};
        blk_t1 blk2 = ((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_d1_0 *)&d1, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk2)->FuncPtr)((__block_impl *)blk2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_c7e4e3_mi_3,(d1.__forwarding->d1));
};
}

可以看到五嫂,通過__block修飾符修飾的變量,都會以結(jié)構(gòu)體的形式在block結(jié)構(gòu)體重存在村斟,而不是向之前直接以基本類型int d的方式存在贫导。struct __Block_byref_d1_0結(jié)構(gòu)體中保存了d1的值,和一些其它的輔助信息:

QQ20170723-130023@2x.png

struct __Block_byref_d1_0結(jié)構(gòu)體中的__Block_byref_d1_0 *__forwarding成員變量蟆盹,保存的是自身的地址值孩灯,在static void __main_block_func_2函數(shù)中,(d1->__forwarding->d1) = 12;使用forwarding來獲取到d1的值逾滥。原因在下文呈現(xiàn)峰档。

3)修改靜態(tài)變量
int main(int argc, const char * argv[]) {
        //修改靜態(tài)變量
        static int d2 = 10;
        blk_t1 blk3 = ^ {
            d2 = 13;
            NSLog(@"%d",d2);
        };
        blk3();
        NSLog(@"%d",d2);
}

打印的結(jié)果為:

13
13

與局部變量不同的是,在block內(nèi)部修改靜態(tài)變量是不需要添加__block修飾符的,可以直接修改靜態(tài)變量的值寨昙。原因如下:

struct __main_block_impl_3 {
  struct __block_impl impl;
  struct __main_block_desc_3* Desc;
  int *d2;
  __main_block_impl_3(void *fp, struct __main_block_desc_3 *desc, int *_d2, int flags=0) : d2(_d2) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_3(struct __main_block_impl_3 *__cself) {
  int *d2 = __cself->d2; // bound by copy

  (*d2) = 13;
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_60608c_mi_4,(*d2));
}

靜態(tài)變量會保存在內(nèi)存的數(shù)據(jù)段讥巡,它的地址是固定分配的,所以block可以直接根據(jù)靜態(tài)變量的地址去操作靜態(tài)變量舔哪,不需要做額外的信息保存操作欢顷。

4)修改靜態(tài)全局變量
static int d3;
int main(int argc, const char * argv[]) {
        //修改靜態(tài)全局變量
        blk_t1 blk4 = ^ {
            d3 = 14;
            NSLog(@"%d",d3);
        };
        blk4();
        NSLog(@"%d",d3);
}

打印結(jié)果為:

14
14

由于d3為靜態(tài)全局變量,d3擁有全局作用范圍捉蚤,所以直接在所有函數(shù)中使用d3就可以對d3進行操作抬驴,不必向靜態(tài)變量那樣使用指針來去找到變量。

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

static void __main_block_func_4(struct __main_block_impl_4 *__cself) {

  d3 = 14;
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_4c_fgfj11vj2t985qr4_0px2sbw0000gn_T_main_2fa84d_mi_6,d3);
}
4)修改全局變量
int d4;
int main(int argc, const char * argv[]) {
        //修改全局變量
        blk_t1 blk5 = ^ {
            d4 = 15;
            NSLog(@"%d",d4);
        };
        blk5();
        NSLog(@"%d",d4);
}

打印結(jié)果為:

15
15

由于修改全局變量和修改靜態(tài)全局變量在底層的實現(xiàn)方式是一模一樣的缆巧,這里就不再展示轉(zhuǎn)換后的源碼了布持。

block的生命周期

block可以向C語言變量一樣使用,block在OC中的形式也是以對象的形式存在的陕悬。那么block也是有自己的聲明周期的题暖,如果block的作用域是在一個函數(shù)內(nèi)的,那么block的生命周期就是函數(shù)調(diào)用期間在棧上存在的期間捉超。一旦函數(shù)執(zhí)行完畢胧卤,函數(shù)中的變量從棧幀中彈出,那么block的生命周期隨即結(jié)束狂秦。

有時灌侣,我們需要讓block的生命周期變長一些來滿足業(yè)務(wù)需要,我們會將block從棧上拷貝到堆上裂问,這時就涉及到一個問題侧啼,block中引用的變量怎么辦?靜態(tài)變量和局部變量還好堪簿,引用局部變量就變得比較棘手痊乾。下面就來回答上面使用__block修飾符修改局部變量所留下來的問題:

block 與 __block變量的實質(zhì):

名稱 實質(zhì)
block 棧上block的結(jié)構(gòu)體實例
__block變量 棧上__block變量的結(jié)構(gòu)體實例

在OC中,block被當做類來處理椭更,block有一下三種類:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

block不同類在內(nèi)存上的存儲區(qū):

設(shè)置對象的存儲區(qū)域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 數(shù)據(jù)段
_NSConcreteMallocBlock

內(nèi)存區(qū)域劃分如下:


image.png

分配在數(shù)據(jù)段的全局block哪审,可以在整個程序的作用域中通過指針來使用。棧上的block當函數(shù)調(diào)用完畢即銷毀虑瀑。堆上的block當沒有對象在對其進行引用時湿滓,也會被釋放滴须,但是一般的我們會將棧上的block拷貝到堆上,延長block的生命周期叽奥。

在OC中扔水,我們對一個對象調(diào)用copy方法,便會將對象拷貝到堆上去朝氓,并得到一個指向堆上對象的指針魔市。如果block中有__block類型的變量,那么__block類型的變量也會一同被復(fù)制到堆上去赵哲。

通過__block修飾的類型都會以結(jié)構(gòu)體的方式來存儲表示:

struct __Block_byref_d1_0 {
  void *__isa;
__Block_byref_d1_0 *__forwarding;
 int __flags;
 int __size;
 int d1;
};

當block拷貝時待德,會將棧中block中的struct __Block_byref_d1_0結(jié)構(gòu)體中__forwarding指針指向堆上的那個struct __Block_byref_d1_0結(jié)構(gòu)體,而不是棧上的枫夺。此后将宪,棧上的block和堆上的block都使用堆上的__block類型變量,以此來達到數(shù)據(jù)共享與一致性橡庞。這是因為涧偷,當棧上的block釋放后,堆上的block依然需要使用__block類型的變量毙死,所以需要在堆上開辟空間來存放__block類型變量燎潮,而堆上的__block變量又必須與棧上的block對象保持一致,所以就干脆讓棧上的block和堆上的block都對堆上的__block變量進行操作扼倘。

由block導致的內(nèi)存泄露問題

在一些使用引用計數(shù)來進行內(nèi)存管理的垃圾回收機制确封,block很容易行程循環(huán)引用而導致內(nèi)存泄露。

舉個例子:

int main(int argc, const char * argv[]) {
         //產(chǎn)生循環(huán)引用 導致內(nèi)存泄露 
        CopyBlock *copyBlock = [[CopyBlock alloc] init];
        blk_t3 blk6 = ^ (id obj){
            NSLog(@"%@",copyBlock);
        };
        copyBlock.blk = [blk6 copy];
}

blk6 調(diào)用copy后復(fù)制到堆區(qū)再菊,并對copyBlock 進行了引用持有爪喘。而copyBlock 對象調(diào)用alloc在堆上申請空間,并對blk6在堆上的對象進行持有纠拔,現(xiàn)在blk6堆上的對象與copyBlock相互持有秉剑,循環(huán)引用,導致這兩個對象使用占有空間稠诲,不會釋放侦鹏,垃圾回收機制無法回收,導致內(nèi)存泄露臀叙。

可能有人會問略水,為什么要讓blk6調(diào)用copy方法,因為如果blk6不調(diào)用copy方法劝萤,那么blk6是就在棧上的一個變量渊涝,而調(diào)用后,會在堆上對棧上的blk6進行一個拷貝,堆棧上現(xiàn)在同時有blk6跨释,以為棧上的blk6當函數(shù)調(diào)用完畢會自動回收胸私,我們控制不了,所以在堆上對blk6進行一個拷貝鳖谈,來使程序?qū)е聝?nèi)存泄露盖文。而我們大多數(shù)時候都是面對對象編程,會反復(fù)的在堆上申請空間蚯姆,block變量也會反復(fù)在堆上申請空間進行保存,所以才這么做來演示內(nèi)存泄露洒敏。

如果我的文章對你有用龄恋,不妨在github上給我一個star吧!
代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凶伙,一起剝皮案震驚了整個濱河市郭毕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌函荣,老刑警劉巖显押,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異傻挂,居然都是意外死亡乘碑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門金拒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兽肤,“玉大人,你說我怎么就攤上這事绪抛∽收。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵幢码,是天一觀的道長笤休。 經(jīng)常有香客問我,道長症副,這世上最難降的妖魔是什么店雅? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮贞铣,結(jié)果婚禮上底洗,老公的妹妹穿的比我還像新娘。我一直安慰自己咕娄,他們只是感情好亥揖,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般费变。 火紅的嫁衣襯著肌膚如雪摧扇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天挚歧,我揣著相機與錄音扛稽,去河邊找鬼。 笑死滑负,一個胖子當著我的面吹牛在张,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矮慕,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼帮匾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痴鳄?” 一聲冷哼從身側(cè)響起瘟斜,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痪寻,沒想到半個月后螺句,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡橡类,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年蛇尚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顾画。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡佣蓉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亲雪,到底是詐尸還是另有隱情勇凭,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布义辕,位于F島的核電站虾标,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灌砖。R本人自食惡果不足惜璧函,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望基显。 院中可真熱鬧蘸吓,春花似錦、人聲如沸撩幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宪萄,卻和暖如春艺谆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拜英。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工静汤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人居凶。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓虫给,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侠碧。 傳聞我的和親對象是個殘疾皇子抹估,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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