iOS底層-- block

手動(dòng)目錄

  • 循環(huán)引用
  • block的類
  • Block的相關(guān)信息
    block本質(zhì)
    block如何捕獲外界變量逻淌?
    __block修飾的本質(zhì)
    block 捕獲外部變量的補(bǔ)充
  • block 堆/棧 轉(zhuǎn)移過(guò)程
    匯編分析
    源碼分析
  • block 簽名
  • block的三層拷貝
    _Block_copy (第一層拷貝)
    block_assign (第二層拷貝)
    __Block_byref_id_object_copy_131 (第三層拷貝)
    _main_block_dispose_0 釋放
  • Block總結(jié)
  • GCD的Block 是否需要weak疟暖?

循環(huán)引用

一般來(lái)說(shuō) 對(duì)于 不會(huì)自動(dòng)release的block 為了避免循環(huán)引用俐巴,一般采用中介者模式
比如 __weak typeof(self) weakSelf = self

中介者模式原理:
用weak之前:
self -> block -> self
用weak之后
weakSelf -> self -> block -> weakSelf
但是 weakSelf 由弱引用表來(lái)維護(hù)擎鸠,不會(huì)進(jìn)行計(jì)數(shù)器加減劣光。在dealloc的時(shí)候糟把,weakSelf被釋放糊饱, 就打破了循環(huán)引用

所有的block 都可以用weak來(lái)修飾嗎另锋? 答案是否定的:可能會(huì)造成 self被提前釋放

@property (nonatomic, strong) NSString *name;   

- (void)viewDidLoad {
    _name = @"asdasd";
    [super viewDidLoad];
   __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;        // 這一行加不加 都會(huì)造成self被提前釋放
        NSLog(@"%@",strongSelf.name);
    });
}  
- (void)dealloc {
    NSLog(@"dealloc");
}

操作:
跳轉(zhuǎn)到這個(gè)界面之后文判,2s內(nèi)進(jìn)行返回戏仓。
返回的時(shí)候會(huì)進(jìn)行正常的dealloc 打印亡鼠,在2s后 打印 NSLog(@"%@",weakSelf.name); 的時(shí)候就出現(xiàn)了問(wèn)題: 打印(null)橄唬。

這是為什么呢:
weakSelf是弱引用,因?yàn)閐ispatch_after 是到時(shí)才將block加入隊(duì)列抗蠢, 在block執(zhí)行之前迅矛,并沒(méi)有進(jìn)行strongSelf對(duì)其進(jìn)行持有潜叛,所有weakSelf會(huì)在dealloc的時(shí)候被釋放钠导, weakSelf、self票堵、_name 都會(huì)被釋放悴势,所以打印就會(huì)出現(xiàn)null措伐。
所以:weak并不是什么地方都可以使用的

在看另外一個(gè)例子:

typedef void(^HandleBlock)(void);
@property (nonatomic, copy) HandleBlock block;

//    self -> block -> self (這個(gè)self 雖然沒(méi)有被after copy捧存,但是被 self.block copy)
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",self.name);
        });
    };
    self.block();


//   strong ->  weakSelf -> self -> block -> strong (strong 是臨時(shí)變量昔穴,block任務(wù)執(zhí)行完畢,strong就被釋放)
__weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    self.block();

使用strong-weak-dance 解決引用對(duì)象可能被提前釋放的問(wèn)題泳唠。

block的類

block 有幾種笨腥? - 6 種
源碼 libclosure-74 中列舉出6種block

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

其中 上層常用的3種:
__NSGlobalBlock__(全局)脖母、__NSMallocBlock__(堆)窥摄、__NSStackBlock__(棧)
另外三種一般是系統(tǒng)級(jí)別去使用崭放。

我們用代碼來(lái)打印常用的三種:

種類一:全局block(block內(nèi)部不捕獲外部變量)            <__NSGlobalBlock__: 0x10696c450>  
void(^ block)(void) = ^{
    NSLog@(@"a");
};
NSLog(@"block = %@",block);  

id globalBlock = ^(NSString *name,NSInteger a){        //這個(gè)也是全局   因?yàn)樗麤](méi)有捕獲外部變量
        NSLog(@"globalBlock : name = %@",name);
};

種類二:堆block (block內(nèi)部捕獲臨時(shí)變量)                <__NSMallocBlock__: 0x600001dba220>
int a = 0;
void(^ block)(void) = ^{
    NSLog(@"a = %@",@(a));
};
NSLog(@"block = %@",block);

種類三:棧block (捕獲外部變量,并在copy之前)            <__NSStackBlock__: 0x7ffeed693978>
int a = 0;
NSLog(@"block = %@",^{
    NSLog(@"%@",@(a));
});

這里有需要注意的點(diǎn): 上面的int a = 0建峭; a是臨時(shí)變量
1亿蒸、對(duì)于種類二:如果a 是全局變量掌桩、全局靜態(tài)變量波岛、局部靜態(tài)變量,二的類型是 __NSGlobalBlock__贡蓖。
2煌茬、在捕獲臨時(shí)變量a的時(shí)候坛善,本身是棧block邻眷,
因?yàn)?臨時(shí)變量在棧上耗溜,超過(guò)作用域 會(huì)被銷毀,為了保證數(shù)據(jù)安全燎字,系統(tǒng)是 將其自動(dòng)進(jìn)行copy操作, 會(huì)將其拷貝到堆上笼蛛。這個(gè)時(shí)候蛉鹿,這個(gè)block變成了mallocBlock妖异。

簡(jiǎn)單點(diǎn)說(shuō):
block默認(rèn)是全局變量,但是在捕獲了棧上 的變量(臨時(shí)變量)响逢,那么它是棧block舔亭,但是棧block為了數(shù)據(jù)安全蟀俊,會(huì)自動(dòng)進(jìn)行copy操作,將其變成堆block矛洞。

Block的相關(guān)信息

block本質(zhì)

用代碼進(jìn)行clang (.m 文件也可以缚甩,但是clang出來(lái)的內(nèi)容太長(zhǎng)窑邦,我們用.c文件 操作)

// 創(chuàng)建一個(gè) C File 文件  BlockTest.c
#include <stdio.h>
int main() {
    void(^block)(void) = ^{
        printf("a");
    };
    block();
}

使用clang命令:clang -rewrite-objc BlockTest.c 得到以下信息

int main() {
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

// 簡(jiǎn)化我們需要的信息
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));        // 構(gòu)造函數(shù)
((void (*)))(block)->FuncPtr)((__block_impl *)block);        // 調(diào)用函數(shù)

// 在編譯后的文件中可以找到由以下信息
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;
  }
};

所以block 的本質(zhì) 可以說(shuō)是對(duì)象冈钦,深一層 可以說(shuō)是結(jié)構(gòu)體 (上面已經(jīng)打印了block的類名)

block如何捕獲外界變量?

還是用代碼進(jìn)行clang

int main() {
    int a = 10
    void(^block)(void) = ^{
        printf("a = %d",a);
    };
    block();
}
// 查看相關(guān)信息
int main() {
    int a = 10;
 //與上面相比 __main_block_impl_0  多了一個(gè)參數(shù)  a
    void(*block)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));             
    block)->FuncPtr)((__block_impl *)block);
}

//       結(jié)構(gòu)體中导盅, 多了一個(gè)參數(shù)a
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;            // 自動(dòng)生成一個(gè)變量來(lái)保存外界的變量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 在執(zhí)行代碼中白翻,將a進(jìn)行了copy操作
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy            // ------ 在這里 前面的a 和后面的a 地址不同绢片。
        printf("a = %d",a);
 }

在這里考慮一個(gè)問(wèn)題: a沒(méi)有進(jìn)行__block 修飾的情況下底循,進(jìn)行a ++ 會(huì)是什么結(jié)果?
假設(shè)能編譯通過(guò)的情況下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
        a ++;                        //  在這里進(jìn)行a++
        printf("a = %d",a);
 }

那么a++ 是對(duì) int a 中的a 進(jìn)行操作熙涤。和外部的a 完全沒(méi)有任何關(guān)系祠挫。a++中的a 是內(nèi)部的a ,在外界沒(méi)有被定義(不能訪問(wèn)內(nèi)部的a)倦炒,所以是不允許的逢唤。

結(jié)論:

  • 如何捕獲變量
    在block的結(jié)構(gòu)體中涤浇,生成了一個(gè)新的成員變量去保存外部變量。

  • 在沒(méi)有__block修飾的情況下著恩,為何不能進(jìn)行修改外部變量蜻展?
    如果在block中進(jìn)行修改屬性,實(shí)質(zhì)上是對(duì)內(nèi)部成員變量的修改伍茄,但是在外部施逾,是無(wú)法當(dāng)問(wèn)結(jié)構(gòu)體中生成的變量。所以無(wú)法修改曹仗。

__block修飾的本質(zhì)

還是用代碼進(jìn)行clang

int main() {
        __block int a = 10;
    void(^block)(void) = ^{
        printf("a = %d",a);
    };
    block();
   }
// 簡(jiǎn)化之后
int main() {
// 結(jié)構(gòu)體的初始化
    __Block_byref_a_0 a = {(void*)0,
                            (__Block_byref_a_0 *)&a,
                            0,
                            sizeof(__Block_byref_a_0),
                            10};            
// 注意第三個(gè)參數(shù)傳遞的是一個(gè)指針地址怎茫。
    void(*block)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));        
    block)->FuncPtr)((__block_impl *)block);
}

// 生成了一個(gè) 關(guān)于 a 的 __Block_byref_a_0 的結(jié)構(gòu)體
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

// __block修飾后 block的結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

// 這里進(jìn)行指針拷貝
  __Block_byref_a_0 *a = __cself->a; // bound by ref
        printf("a = %d",(a->__forwarding->a));
    }

結(jié)論:
--block 是生成了一個(gè) block_byref_ 的結(jié)構(gòu)體 用來(lái)保存原有變量的指針 和 值
傳遞給block的是一個(gè)指針轨蛤。

block 捕獲外部變量的補(bǔ)充

這部分內(nèi)容來(lái)源于另一片文章
其中有一部分內(nèi)容講解了 自動(dòng)變量/靜態(tài)變量/靜態(tài)全局變量/全局變量 多種變量在block中捕獲的情況

說(shuō)明:
自動(dòng)變量 --- 臨時(shí)變量(方法內(nèi)部定義的變量)
靜態(tài)變量 --- 方法內(nèi)部定義的static 變量
靜態(tài)全局變量 --- 方法外部定義的static變量
全局變量 ---- 類的成員變量

int a = 10;
static int  b = 20;

int main(int argc, char * argv[]) {
    static int  c = 30;
    int         d = 40;
    __block int e = 50;
    void(^block)(void) = ^{
        a++;    b++;    c++;    e++;
//        d++;   內(nèi)部無(wú)法操作
        printf("\na = %d  b = %d c = %d d = %d e = %d",a,b,c,d,e);
    };
    a++;    b++;    c++;    d++;    e++;
    printf("a = %d  b = %d c = %d d = %d e = %d",a,b,c,d,e);
    
    block();
    retrun 0;
}

這里的打印結(jié)果
外部 a = 11 b = 21 c = 31 d = 41 e = 51
內(nèi)部 a = 12 b = 22 c = 32 d = 40 e = 52

因?yàn)閐是在外部進(jìn)行++之前就進(jìn)行了值拷貝俱萍,其他的是進(jìn)行指針訪問(wèn)/直接 訪問(wèn)枪蘑,所以都有產(chǎn)生了相應(yīng)的變化岳颇。

clang 轉(zhuǎn)換之后的關(guān)鍵信息

// 簡(jiǎn)化處理
int main() {
    static int c = 30;
    int d = 40;
    __Block_byref_e_0 e = {(void*)0,
                            (__Block_byref_e_0 *)&e,
                            0,
                            sizeof(__Block_byref_e_0),
                            50};
    void(*block)(void) = (&__main_block_impl_0(__main_block_func_0,
                                               &__main_block_desc_0_DATA,
                                               &c,
                                               d,
                                               (__Block_byref_e_0 *)&e,
                                               570425344));
    a++; b++; c++; d++; (e.__forwarding->e)++;
    printf("a = %d  b = %d c = %d d = %d e = %d",a,b,c,d,(e.__forwarding->e));
    
    (block)->FuncPtr)((__block_impl *)block);
    
}

// block 新結(jié)構(gòu)         ??新增了 c(int *)颅湘、d(int)闯参、e(__Block_byref_ *)??
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *c;
  int d;
  __Block_byref_e_0 *e; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, int _d, __Block_byref_e_0 *_e, int flags=0) : c(_c), d(_d), e(_e->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_e_0 *e = __cself->e; // bound by ref
  int *c = __cself->c; // bound by copy
  int d = __cself->d; // bound by copy

        a++; b++; (*c)++; (e->__forwarding->e)++;

        printf("\na = %d  b = %d c = %d d = %d e = %d",a,b,(*c),d,(e->__forwarding->e));
    }

從上面的結(jié)果中可以知道以下信息

  • 1、全局變量(靜態(tài)/非靜態(tài)) 都不參與block內(nèi)部處理新博。 而是直接訪問(wèn)外部變量
    block結(jié)構(gòu)體中沒(méi)有新增a赫悄、b的成員變量
    static void __main_block_func_0 中也沒(méi)有對(duì)a馏慨、b的處理,只有對(duì)c倔撞、d樟澜、e的相應(yīng)處理

  • 2叮盘、靜態(tài)變量(c)柔吼、臨時(shí)變量(d)丙唧、__block修飾的臨時(shí)變量(e) 都會(huì)被block 編譯進(jìn)結(jié)構(gòu)體,并進(jìn)行間接訪問(wèn) -- 指針地址訪問(wèn)/值拷貝

  • 3培漏、變化類型
    靜態(tài)變量 內(nèi)部 是處理成相應(yīng)的指針 c => int *c
    臨時(shí)變量 只是做一個(gè)值存儲(chǔ) d => int d
    __block修飾的內(nèi)部變量處理成指針 e => __Block_byref_e_0 *e

  • 4胡本、指針地址拷貝/值拷貝
    靜態(tài)變量是指針地址拷貝 ----------------內(nèi)部 能對(duì)外部進(jìn)行修改 int *c = __cself->c; // bound by copy
    __block 是指針地址拷貝 ----------------內(nèi)部 能對(duì)外部進(jìn)行修改 __Block_byref_e_0 *e = __cself->e; // bound by ref
    臨時(shí)變量是值拷貝 ------------------------內(nèi)部不能對(duì)外部進(jìn)行修改 int d = __cself->d; // bound by copy

再換一個(gè)情況 侧甫,block內(nèi)部使用指針

NSMutableArray *arr = [NSMutableArray new];
    NSString *a = @"a";
    void(^block)(void) = ^{
        [arr addObject:@"1"];
//        arr = [NSMutableArray new];    這一行不被允許  
    };
    block();

用clang轉(zhuǎn)換后的結(jié)構(gòu) 類似于上面的 靜態(tài)變量

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *arr;                                      //    ?? 重點(diǎn)
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_arr, NSString *_str, int flags=0) : arr(_arr), str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableArray *arr = __cself->arr; // bound by copy              //  ??這里也是指針拷貝
......
}

block 捕獲變量總結(jié)

  • 1 披粟、全局變量 不捕獲,直接訪問(wèn)
  • 2惑艇、非全局變量滨巴,對(duì)捕獲的內(nèi)容進(jìn)行拷貝
    ===》捕獲的指針碰镜,對(duì)指針進(jìn)行拷貝,可以修改指針?biāo)?的內(nèi)容秽荤,不能改變指針指向(---靜態(tài)變量傳遞給Block是內(nèi)存地址值---) ----- 傳入可變數(shù)組柠横,可以改變數(shù)組內(nèi)容牍氛,不能對(duì)數(shù)組重指向。
    ===》捕獲的值紊扬,對(duì)值進(jìn)行拷貝蜒茄,不能修改這個(gè)值 ----- 傳入 int a檀葛,內(nèi)部只能使用屿聋,不能修改
  • 3藏鹊、_block 修飾的變量盘寡。生成了一個(gè) block_byref 的結(jié)構(gòu)體 用來(lái)保存原有變量的指針 和 值
    傳遞給block的是一個(gè)指針。
    block 捕獲的時(shí)機(jī): 并不是在執(zhí)行block的時(shí)候竿痰。

以下代碼是用來(lái)說(shuō)明 捕獲時(shí)機(jī)的

    NSMutableArray *arr = [NSMutableArray new];             // 1
//    __block NSMutableArray *arr = [NSMutableArray new];   // 1
    void(^block)(void) = ^{
        [arr addObject:@"1"];                               // 2
    };
    arr = [NSMutableArray new];                             // 3
    block();
    NSLog(@"arr = %@",arr);                                 // 4

// 無(wú)__block修飾的情況
在2位置, arr的地址:2 捕獲了 1 的地址    也就是說(shuō)  外部修改了arr的地址菇曲,內(nèi)部捕獲到的地址并沒(méi)有隨之改變

// 有__block修飾的情況
2 先捕獲了1 的地址常潮, 執(zhí)行 3的時(shí)候喊式, 2的地址隨之改變岔留。 2 其實(shí)是對(duì) 3的arr進(jìn)行操作检柬。   4打印的是 3的地址。

block 堆/棧 轉(zhuǎn)移過(guò)程

匯編分析

  • 無(wú)外部變量引用的block

    void(^block)(void) = ^{      // ?? 斷點(diǎn)在這
    };
      block();
    

    到了斷點(diǎn)之后何址,下符號(hào)斷點(diǎn)_Block_copy

    libsystem_blocks.dylib`_Block_copy:
    ->  0x1867f48c0 <+0>:   stp    x22, x21, [sp, #-0x30]!            // 步驟一: 斷點(diǎn)在這行 
        0x1867f48c4 <+4>:   stp    x20, x19, [sp, #0x10]
    ........
        0x1867f49a4 <+228>: ldp    x22, x21, [sp], #0x30
        0x1867f49a8 <+232>: ret                                      // 步驟二: 斷點(diǎn)在這行 
    

    步驟一:此時(shí)讀寄存器x0 register read x0po打印
    步驟二:斷點(diǎn)在步驟二的位置,在進(jìn)行一次打印

    // 步驟一 打印結(jié)果
    (lldb) register read x0
          x0 = 0x00000001002e8080  
    (lldb) po 0x00000001002e8080
    <__NSGlobalBlock__: 0x1002e8080>
    
    // 步驟二  打印結(jié)果   和步驟一  一致
    (lldb) register read x0
          x0 = 0x00000001002e8080 
    (lldb) po 0x00000001002e8080
    <__NSGlobalBlock__: 0x1002e8080>
    

    最后結(jié)果都是GlobalBLock

  • 有外部變量引用的情況
    換一個(gè)有引用外部變量的block在進(jìn)行上述操作

    int a = 10;
      void(^block)(void) = ^{
          NSLog(@"%d",a);
      };
      block();
    

    寄存器打印結(jié)果

    // 步驟一
    (lldb) register read x0
          x0 = 0x000000016f75bdc8
    (lldb) po 0x000000016f75bdc8
    <__NSStackBlock__: 0x16f75bdc8>
    
    // 步驟二
    (lldb) register read x0
          x0 = 0x0000000283a3c630
    (lldb) po 0x0000000283a3c630
    <__NSMallocBlock__: 0x283a3c630>
    

    打印結(jié)果 從 StackBlock 變成了 MallocBlock

源碼分析

源碼 libclosure-74 中來(lái)找相關(guān)信息
先看block定義的結(jié)構(gòu)

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE          // 依賴于   BLOCK_HAS_COPY_DISPOSE  (有BLOCK_HAS_COPY_DISPOSE 才會(huì)有這些 信息)
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE    // 依賴于   BLOCK_HAS_SIGNATURE  (有BLOCK_HAS_SIGNATURE 才會(huì)有這些 信息)
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

基本結(jié)構(gòu)為Block_layout 有沒(méi)有Block_descriptor_2原押、Block_descriptor_3诸衔,取決于 flags。
看flags結(jié)構(gòu)

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime                             正在釋放就缆,釋放標(biāo)記违崇,一般常用BLOCK_NEEDS_FREE 做 位與 操作 一同傳入flags 告知該block可釋放
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime                             存儲(chǔ)引用計(jì)數(shù)的值诊霹,是一個(gè)可選用參數(shù)
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime                             是否有效的標(biāo)志脾还,程序根據(jù)他來(lái)決定是否增加或減少引用計(jì)數(shù)的值
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler                            是否擁有拷貝輔助函數(shù)(a copy helper function),決定Block_descriptor_2
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code     是否擁有block C++的析構(gòu)函數(shù)
    BLOCK_IS_GC =             (1 << 27), // runtime                             標(biāo)志是否有垃圾回收  ---- OS X
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler                            標(biāo)志是否是全局block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE         與BLOCK_HAS_SIGNATURE相對(duì)鄙漏,判斷當(dāng)前block是否有簽名 用于runtime時(shí)動(dòng)態(tài)調(diào)用
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler                            是否有簽名
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler                            是否有擴(kuò)展  決定Block_descriptor_3
    
    /**         block 捕獲外部變量的類型
     BLOCK_FIELD_IS_OBJECT  = 3, 對(duì)象
     BLOCK_FIELD_IS_BLOCK   = 7, 是一個(gè)block變量
     BLOCK_FIELD_IS_BYREF   = 8, __block 修飾的結(jié)構(gòu)體
     BLOCK_FIELD_IS_WEAK    = 16,  __weak 修飾的變量
     BLOCK_BYREF_CALLER     = 128 處理Block_byref 內(nèi)部對(duì)象內(nèi)存的時(shí)候會(huì)加一個(gè)往外的標(biāo)記怔蚌,配合上面的枚舉提起使用
     */
};

在源碼中看 block_copy 做了那些事情

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {                            // 如果內(nèi)存需要自己管理   那么引用計(jì)數(shù)相應(yīng)增加    最開始是0
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {          //  如果是全局  不做任何操作
        return aBlock;
    }
    else {                          //     如果是棧block  進(jìn)行一次拷貝
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

在Block_copy 中 主要有3個(gè)判斷 ( 操作的內(nèi)容 椅野,后面加了備注 ) 籍胯,其中重點(diǎn)在else 里面

做了2件事:

  • 1杖狼、 malloc 申請(qǐng)內(nèi)存(堆區(qū))struct Block_layout *result = (struct Block_layout *)malloc(aBlock->descriptor->size);
  • 2、將原來(lái)的block 進(jìn)行memmove (從棧區(qū) 移動(dòng)到堆區(qū)) 并將結(jié)構(gòu)的相關(guān)信息進(jìn)行更新
    result->flags |= BLOCK_NEEDS_FREE | 2; // 標(biāo)記為 needs__free 并邏輯計(jì)數(shù)器為1
    result->isa = _NSConcreteMallocBlock; // 從棧block 編程了 堆block

block 簽名

在 方法中有簽名 =====iOS 底層--Class探索和方法執(zhí)行過(guò)程 ---> 3理朋、方法 中有關(guān)于方法簽名的相關(guān)內(nèi)容
在block源碼中也有簽名信息暗挑, - 其簽名 信息在BLOCK_DESCRIPTOR_3中炸裆,依賴于 flags 中的BLOCK_HAS_SIGNATURE鲜屏。 直接lldb打印比較麻煩,需要計(jì)算地址偏移惯殊。
這篇文章中找到簡(jiǎn)便方法---- 借助 aspects
aspects中 簽名信息的方法是私有务热,稍作修改己儒,去掉staic 并在闪湾。h中進(jìn)行申明,這樣才可以外部訪問(wèn)江醇。

NSString *getMethodSignatureTypeEncoding(NSMethodSignature *methodSignature){
    NSMutableString *str = @"".mutableCopy;
    const char *rtvType = methodSignature.methodReturnType;
    [str appendString:[NSString stringWithUTF8String:rtvType]];
    
    for (int i = 0; i < methodSignature.numberOfArguments; i ++) {
        const char *type = [methodSignature getArgumentTypeAtIndex:i];
        [str appendString:[NSString stringWithUTF8String:type]];
    }
    return [str copy];
}

void task() {
    id globalBlock1 = ^(NSString *str ,NSInteger a,NSArray *arr){
        NSLog(@"globalBlock1 : name = %@",str);
    };
    
    NSMethodSignature *signature1 = aspect_blockMethodSignature(globalBlock1, NULL);
    NSLog(@"%@",getMethodSignatureTypeEncoding(signature1));
}
// 打印結(jié)果  v@?@"NSString"q@"NSArray"

block 簽名信息
v@?@"NSString"q@"NSArray" -- 中間省略了參數(shù)占位長(zhǎng)度
v - 返回值類型 void - 無(wú)
@? - block 的簽名
@"NSString" 第一個(gè)參數(shù) NSString 類型 -- 表示 是一個(gè)NSString 對(duì)象
q 第二個(gè)參數(shù) NSInteger
@"NSArray" 第三 個(gè)參數(shù) NSArray 類型 -- 表示 是一個(gè)NSArray 對(duì)象

回顧方法簽名信息
v 返回值
@ 第一個(gè)參數(shù) 一個(gè)對(duì)象
: 表示 方法(SEL)

block和方法簽名有點(diǎn)區(qū)別
方法簽名 一般不指明是什么類型 只表示是一個(gè)對(duì)象 比如 v@:
block的簽名 參數(shù)中 不僅指明是一個(gè)對(duì)象陶夜,還指出對(duì)象是什么類型 比如 @"NSString"

block的三層拷貝

_Block_copy (第一層拷貝)

這個(gè)在上面棧/堆轉(zhuǎn)移過(guò)程已經(jīng)說(shuō)過(guò)了

block_assign (第二層拷貝)

在上面轉(zhuǎn)換的cpp文件中 還有一個(gè)方法需要注意
__main_block_copy_0
這里調(diào)用到 _Block_object_assign ,我們?cè)谠创a中看看究竟干了什么律适。

void _Block_object_assign(void *destArg, const void *object, const int flags) {
  const void **dest = (const void **)destArg;
  switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {

    case BLOCK_FIELD_IS_OBJECT:
      _Block_retain_object(object);
      *dest = object;
      break;

    case BLOCK_FIELD_IS_BLOCK:
      *dest = _Block_copy(object);
      break;
  
    case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
    case BLOCK_FIELD_IS_BYREF:
      *dest = _Block_byref_copy(object);
      break;
      
    case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
    case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      *dest = object;
      break;

    case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
    case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
      *dest = object;
      break;

    default:
      break;
  }
}

根據(jù)捕獲的對(duì)對(duì)象類型分別做不同的操作。其中關(guān)鍵的一個(gè) BLOCK_FIELD_IS_BYREF 類型---- __block修飾的類型

_Block_byref_copy(object);

static struct Block_byref *_Block_byref_copy1(const void *arg) {
  struct Block_byref *src = (struct Block_byref *)arg;

  if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
      // src points to stack
      struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
      copy->isa = NULL;
      // byref value 4 is logical refcount of 2: one for caller, one for stack
      copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
      copy->forwarding = copy; // patch heap copy to point to itself
      src->forwarding = copy;  // patch stack to point to heap copy
      copy->size = src->size;

      if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
          ........
      }
      else {
          // Bitwise copy.
          // This copy includes Block_byref_3, if any.
          memmove(copy+1, src+1, src->size - sizeof(*src));
      }
  }
  ........
  return src->forwarding;
}

關(guān)鍵部分

  • 1胳嘲、在堆區(qū)申請(qǐng)一塊和原來(lái)block_byref 相同大小的空間 (struct Block_byref *)malloc(src->size);
  • 2了牛、堆區(qū)的指針指向 copy copy->forwarding = copy;
  • 3鹰祸、原來(lái)的 block_byref(棧區(qū)) 也指向copy src->forwarding = copy;
    所以 __block 修飾的變量才有修改的能力密浑。

__Block_byref_id_object_copy_131 (第三層拷貝)

這個(gè)拷貝是對(duì)__block修飾的對(duì)象進(jìn)行拷貝尔破〗匠模拷貝到 Block_byref_2 -> BlockByrefKeepFunction byref_keep

最終是對(duì)這個(gè)對(duì)象進(jìn)行了 _Block_object_assign 操作耘擂。

struct __Block_byref_str_0 {
  void *__isa;
__Block_byref_str_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *str;                    // ??
};

也就是對(duì) 結(jié)構(gòu)體中的 NSString *str 進(jìn)行memmove(拷貝操作)醉冤。

__main_block_dispose_0 釋放

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

結(jié)構(gòu)很清晰蚁阳,就是對(duì)不同拷貝的類型 進(jìn)行release操作韵吨。

Block總結(jié)

  • 1移宅、 __block 做了什么
    在編譯時(shí)漏峰,對(duì)__block修飾的對(duì)象轉(zhuǎn)換成一個(gè) block_byref 的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面保存了捕獲對(duì)象的指針和值
  • 2倔喂、為什么__block 修飾的對(duì)象具有修改的能力席噩?
    簡(jiǎn)單點(diǎn)說(shuō):__block修飾的對(duì)象被從棧區(qū)拷貝到堆區(qū)贤壁。堆區(qū)是可以有程序員自己去控制、修改馒索。
    具體的過(guò)程為: block進(jìn)行了三次memmove(copy)绰上,
    1渠驼、block 本身的copy,將 block本身拷貝的堆區(qū) _block_copy.
    2疯趟、捕獲的對(duì)象的結(jié)構(gòu)體進(jìn)行memmove(copy)信峻,在這個(gè)過(guò)程中,在堆區(qū)申請(qǐng)內(nèi)存产镐,然后原來(lái)?xiàng)^(qū)的block_byref 指向這塊內(nèi)存踢步,其本身也指向這塊內(nèi)存,都指向同一塊內(nèi)存空間述雾,所以就有了值/地址修改的能力玻孟。
    3鳍征、保存的指針進(jìn)行memmove(copy),同時(shí)是按照步驟二進(jìn)行操作匣掸。目的是使對(duì)象可以進(jìn)行值的修改碰酝。

GCD的Block 是否需要weak戴差?

有些需要,有些不需要

  • 不需要:
    調(diào)度組(dispatch_group_async / dispatch_group_notify)
    柵欄(dispatch_barrier_async / dispatch_barrier_sync)
    dispatch_async
    dispatch_sync
    dispatch_after

同步和異步的是有區(qū)別的:
異步是將任務(wù)進(jìn)行包裝,在包裝的過(guò)程中饭入,進(jìn)行(copy)肛真、引用(invoke)、釋放(call_and_release)乾忱。
同步是任務(wù)不進(jìn)行copy窄瘟,對(duì)于調(diào)用者, 只是 “borrows”(借用)氏义,而不是對(duì)調(diào)用者進(jìn)行持有图云。

  • 需要:
    dispatch_source_timer(計(jì)時(shí)器)竣况。

源碼分析

  • dispatch_async分析

    dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
    {
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;
    
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
    }
    
    ------------------------------
    ?關(guān)鍵在這個(gè)方法  _dispatch_continuation_init?
    DISPATCH_ALWAYS_INLINE
    static inline dispatch_qos_t
    _dispatch_continuation_init(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, dispatch_block_t work,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
    {
    void *ctxt = _dispatch_Block_copy(work);                    // 對(duì)任務(wù)進(jìn)行copy
    
    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        dc->dc_flags = dc_flags;
        dc->dc_ctxt = ctxt;
        return _dispatch_continuation_init_slow(dc, dqu, flags);
    }
    
    dispatch_function_t func = _dispatch_Block_invoke(work)     // 引用這個(gè) 任務(wù)
    if (dc_flags & DC_FLAG_CONSUME) {                   
        func = _dispatch_call_block_and_release;                // 釋放
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
    }
    
    ------------------------------
    void
    _dispatch_call_block_and_release(void *block)
    {
    void (^b)(void) = block;
    b();
    Block_release(b);
    }
    

    源碼分析 async 調(diào)用步驟:
    1情萤、對(duì)block 進(jìn)行 copy _dispatch_Block_copy(work);
    2、對(duì)任務(wù)進(jìn)行引用(invoke) _dispatch_Block_invoke(work)
    3紫岩、釋放這個(gè)任務(wù) _dispatch_call_block_and_release
    4泉蝌、將block包裝成 dispatch_continuation_t

    釋放的條件分析
    dc_flags 入?yún)? //DC_FLAG_CONSUME = 0x004
    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    ====> 拆解: dc_flags = 0x004 | (0x010 | 0x100); = 1001 0100 (276)
    dc_flags & DC_FLAG_CONSUME
    ====> 拆解: 1001 0100 & 0000 0100 = 0x004
    條件成立,會(huì)調(diào)用 _dispatch_call_block_and_release

  • dispatch_after 分析
    同樣通過(guò)源碼

    static inline void
    _dispatch_after(dispatch_time_t when, dispatch_queue_t dq,
          void *ctxt, void *handler, bool block)
    {
      ....
    
      dispatch_continuation_t dc = _dispatch_continuation_alloc();
      if (block) {
          _dispatch_continuation_init(dc, dq, handler, 0, 0);
      } else {
          _dispatch_continuation_init_f(dc, dq, ctxt, handler, 0, 0);
      }
      .....
    }
    

    源碼分析:
    又回到了 _dispatch_continuation_init 的調(diào)用勋陪,還是和上面一樣诅愚,不過(guò)傳入的參數(shù) dc_flags 參數(shù)是0 劫映,調(diào)用 _dispatch_call_block_and_release 條件不成立,這只能說(shuō)明:此時(shí)的block是不能被釋放的雌桑,但是延時(shí)執(zhí)行是在指定時(shí)間后祖今,將block添加的相應(yīng)的線程去執(zhí)行。所以:dispatch_after 的block 不是當(dāng)時(shí)釋放耍目,而是指定時(shí)間后再去釋放邪驮。(這個(gè)地方源碼沒(méi)有分析出來(lái))

    通過(guò)API說(shuō)明 能找到相應(yīng)信息:
    The block to submit. This function performs a Block_copy and Block_release on behalf of the caller.

  • dispatch_sync 分析

    static inline void
    _dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
          dispatch_function_t func, uintptr_t dc_flags)
    {
      if (likely(dq->dq_width == 1)) {
          return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
      }
      ....
      _dispatch_introspection_sync_begin(dl);
      _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
              _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
    }
    

    源碼全部看下來(lái),沒(méi)有定義block進(jìn)行copy的操作衔掸。

    在官方文檔中看到這樣一段描述:

    Unlike with `dispatch_async`, no retain is performed on the target queue. 
    Because calls to this function are synchronous, it "borrows" the reference of the caller.
    Moreover, no `Block_copy` is performed on the block.
    
    大概意思就是:
    與dispatch_async不同俺抽,不在目標(biāo)隊(duì)列上執(zhí)行retain。因?yàn)閷?duì)這個(gè)函數(shù)的調(diào)用是同步的振愿,所以它“借用”了調(diào)用者的引用冕末。此外档桃,不在該塊上執(zhí)行塊復(fù)制。
    

    因?yàn)?code>block 不對(duì)調(diào)用者進(jìn)行持有藻肄,它只是“借用”嘹屯,所以不會(huì)造成循環(huán)引用州弟。

  • dispatch_barrier_async(柵欄) 分析

    void
    dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
    {
      dispatch_continuation_t dc = _dispatch_continuation_alloc();
      uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
      dispatch_qos_t qos;
    
      qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
      _dispatch_continuation_async(dq, dc, qos, dc_flags);
    }
    

    dispatch_barrier_async 類似于 dispatch_async , 將block包裝起來(lái)低零。
    dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
    條件成立,會(huì)調(diào)用 _dispatch_call_block_and_release

  • dispatch_group (調(diào)度組) 分析
    調(diào)度組分2個(gè):組里面的每個(gè)小任務(wù) 和 組任務(wù)完成之后的notify
    void
    dispatch_group_async_f(dispatch_group_t dg, dispatch_queue_t dq, void *ctxt,
          dispatch_function_t func)
    {
      dispatch_continuation_t dc = _dispatch_continuation_alloc();
      uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
      dispatch_qos_t qos;
    
      qos = _dispatch_continuation_init_f(dc, dq, ctxt, func, 0, dc_flags);
      _dispatch_continuation_group_async(dg, dq, dc, qos);
    }
    
    void
    dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
          dispatch_block_t db)
    {
      dispatch_continuation_t dsn = _dispatch_continuation_alloc();
      _dispatch_continuation_init(dsn, dq, db, 0, DC_FLAG_CONSUME);
      _dispatch_group_notify(dg, dq, dsn);
    }
    

    dispatch_group_async 和 dispatch_group_notify 都是回歸到 _dispatch_continuation_init
    根據(jù)傳入的參數(shù) dc_flags
    最后的結(jié)果都是回歸調(diào)用 _dispatch_call_block_and_release

block 變量捕獲 補(bǔ)充 2020.09.19
- (void)test {
    void (^block)(void) = ^{
        NSLog(@"self = %@",self);
    };
    block();
}

思考:這個(gè)block中的self 是否會(huì)被捕獲啃奴?

答案: 會(huì)气堕。 因?yàn)樵趖est方法中,默認(rèn)是有2個(gè)參數(shù):(self揖膜、_cmd), 所以在這里的self 其實(shí)是一個(gè)臨時(shí)變量壹粟。(具體編譯后的結(jié)構(gòu) 可通過(guò)clang命令驗(yàn)證)趁仙。

那再看這段代碼:

@property (nonatomic, copy) NSString *name;   /// 類的一個(gè)屬性

- (void)test {
    void (^block)(void) = ^{
        NSLog(@"name = %@",_name);
    };
    block();
}

在這段代碼中垦页, _name 不會(huì)被捕獲,但是 self會(huì)被捕獲盏袄。 使用 _name 實(shí)際上是 self->_name; 所以self被捕獲 了辕羽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刁愿,一起剝皮案震驚了整個(gè)濱河市到逊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枷踏,老刑警劉巖旭蠕,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旷坦,死亡現(xiàn)場(chǎng)離奇詭異秒梅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)疮丛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門誊薄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人切心,你說(shuō)我怎么就攤上這事绽昏∏渭梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵啼县,是天一觀的道長(zhǎng)季眷。 經(jīng)常有香客問(wèn)我子刮,道長(zhǎng)挺峡,這世上最難降的妖魔是什么橱赠? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任狭姨,我火速辦了婚禮苏遥,結(jié)果婚禮上田炭,老公的妹妹穿的比我還像新娘教硫。我一直安慰自己辆布,他們只是感情好谚殊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛤铜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丛肢。 梳的紋絲不亂的頭發(fā)上围肥,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音蜂怎,去河邊找鬼穆刻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杠步,可吹牛的內(nèi)容都是我干的氢伟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼诚些,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诬烹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起唬格,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤员舵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后韭邓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體女淑,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屈张,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了场绿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰盗。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖梦湘,靈堂內(nèi)的尸體忽然破棺而出捌议,到底是詐尸還是另有隱情瓣颅,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布粉怕,位于F島的核電站贫贝,受9級(jí)特大地震影響崇堵,放射性物質(zhì)發(fā)生泄漏鸳劳。R本人自食惡果不足惜赏廓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昔案,春花似錦踏揣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至抹缕,卻和暖如春卓研,著一層夾襖步出監(jiān)牢的瞬間哮幢,已是汗流浹背橙垢。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喂击,地道東北人翰绊。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像裁奇,于是被迫代替她去往敵國(guó)和親刽肠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348