iOS-底層原理28:block底層原理

本文主要介紹:
1讹语、block的本質(zhì)
2旅急、block捕獲變量
3趋惨、block的類型
4氢伟、__block原理

本質(zhì)

通過clang分析Block底層

step1: 定義block.c文件

#include "stdio.h"

int main(){

    void(^block)(void) = ^{
        printf("lbh");
    };
    return 0;
}

step2: 通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c,將block.c 編譯成 block.cpp或粮,其中block在底層被編譯成了以下的形式

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);
    return 0;
}

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("lbh");
}

//******簡化******
//__main_block_impl_0 是構(gòu)造函數(shù)导饲,在結(jié)構(gòu)體中
//參數(shù)__main_block_func_0 是閉包中具體實(shí)現(xiàn)
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//構(gòu)造函數(shù)

block->FuncPtr(block);//block調(diào)用執(zhí)行

相當(dāng)于block等于__main_block_impl_0,是一個(gè)函數(shù)氯材,第一個(gè)參數(shù)是__main_block_func_0帜消,它是個(gè)函數(shù),代碼塊的實(shí)現(xiàn)函數(shù)浓体。

step3: 查看__main_block_impl_0泡挺,是一個(gè)結(jié)構(gòu)體

//**block代碼塊的結(jié)構(gòu)體類型**
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的結(jié)構(gòu)體類型**
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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

struct __main_block_impl_0包含一個(gè)struct __block_impl類型的impl和一個(gè)struct __main_block_desc_0*類型的Desc
struct __block_impl包含一個(gè)isa指針命浴,說明block其實(shí)是一個(gè)對(duì)象娄猫。
static struct __main_block_desc_0中包含一個(gè)Block_size表示block占用內(nèi)存空間

構(gòu)造函數(shù)__main_block_impl_0將第一個(gè)參數(shù)__main_block_func_0傳給了FuncPtr,所以FuncPtr指向block具體實(shí)現(xiàn)函數(shù)的地址生闲。

block通過clang編譯后的源碼間的關(guān)系如下所示媳溺,以__block修飾的變量為例

總結(jié)block本質(zhì)上也是一個(gè)oc對(duì)象,他內(nèi)部也有一個(gè)isa指針碍讯。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象悬蔽。

捕獲基本數(shù)據(jù)類型

捕獲局部變量--auto

定義一個(gè)變量,并在block中調(diào)用

int main(){

    int a = 10;
    void(^block)(void) = ^{
        printf("lbh--%d", a);
    };
    
    a = 20;
    block();
    return 0;
}

輸出結(jié)果

lbh--10

底層編譯成如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
// : a(_a)  c++語法  會(huì)自動(dòng)將_a賦值給a   a = _a
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//代碼塊放在函數(shù)中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy  值拷貝

        printf("lbh--%d", a);
}

int main(){

    int a = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

    a = 20;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

在構(gòu)造函數(shù)創(chuàng)建block時(shí)捉兴,傳入的第三個(gè)參數(shù)是a蝎困,然后將a存入到結(jié)構(gòu)體__main_block_impl_0里的成員變量a中,調(diào)用block時(shí)倍啥,直接從結(jié)構(gòu)體中取出變量a的值 所以局部自動(dòng)變量是值拷貝禾乘, 如果此時(shí)在block內(nèi)部實(shí)現(xiàn)中作 a++操作,是有問題的虽缕,會(huì)造成編譯器的代碼歧義始藕,即此時(shí)的a是只讀的

捕獲局部變量--static

int main(int argc, char * argv[]) {
    
      static int a = 10;
       void(^block)(void) = ^{
           printf("lbh--%d", a);
       };
       
       a = 20;
       
       block();
    
    return 0;
}

輸出結(jié)果

lbh--20

底層編譯如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy

        printf("lbh--%d", (*a));
    }

int main(){

    static int a = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));

    a = 20;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

在通過構(gòu)造函數(shù)創(chuàng)建block時(shí),第三個(gè)參數(shù)傳的是&a,即變量a的地址伍派,然后將&a 存入結(jié)構(gòu)體__main_block_impl_0中的成員變量*a中江耀,調(diào)用block時(shí),從結(jié)構(gòu)體中取出a诉植,因?yàn)榇嫒氲氖?code>a的地址所以是指針拷貝祥国。

全局變量

static int a= 10;
int main(int argc, char * argv[]) {
    
       void(^block)(void) = ^{
           printf("lbh--%d", a);
       };
       
       a = 20;
       
       block();
    
    return 0;
}

輸出結(jié)果

lbh--20

底層編譯

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 void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("lbh--%d", a);
}

int main(){

    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    a = 20;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

在通過構(gòu)造函數(shù)創(chuàng)建block時(shí),并沒有以參數(shù)的形式將a傳進(jìn)去倍踪,在結(jié)構(gòu)體__main_block_impl_0中也沒有生成新的成員變量,在調(diào)用時(shí)直接訪問變量a索昂。

總結(jié)

局部變量都會(huì)被block捕獲建车,自動(dòng)變量是值捕獲,靜態(tài)變量為地址捕獲椒惨。全局變量則不會(huì)被block捕獲

問題: 為什么全局變量不需要捕獲缤至,而局部變量需要捕獲?

解答: 因?yàn)槿肿兞慷伎梢栽L問康谆,捕獲是多此一舉领斥,局部變量因?yàn)樽饔糜虻膯栴}需要捕獲,請(qǐng)看下面的例子

void (^block) (void);

void test()
{
    auto int a = 10;
    static int b = 20;
    
    block = ^{
        NSLog(@" a = %d b = %d", a,b);
    };

}

//static int a= 10;
int main(int argc, char * argv[]) {
    
    test();
    block();
    
    return 0;
}

a沃暗、b的作用域是在test函數(shù)中月洛,而block代碼塊封裝在另一個(gè)函數(shù)中,block調(diào)用時(shí)調(diào)用的是這個(gè)封裝的函數(shù)孽锥,超出了a嚼黔、b的作用域,如果不進(jìn)行變量捕獲惜辑,會(huì)出現(xiàn)訪問異常唬涧。

問題:下面代碼中self會(huì)不會(huì)被捕獲? 為什么盛撑?

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

看底層編譯

struct __LBHPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __LBHPerson__test_block_desc_0* Desc;
  LBHPerson *self;
  __LBHPerson__test_block_impl_0(void *fp, struct __LBHPerson__test_block_desc_0 *desc, LBHPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void _I_LBHPerson_test(LBHPerson * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__LBHPerson__test_block_impl_0((void *)__LBHPerson__test_block_func_0, &__LBHPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__LBHPerson__test_block_impl_0結(jié)構(gòu)體中增加了一個(gè)成員變量LBHPerson *self;碎节,所以self是會(huì)被捕獲的,因?yàn)閛c函數(shù)默認(rèn)是會(huì)有兩個(gè)參數(shù)(LBHPerson * self, SEL _cmd)抵卫,而參數(shù)是局部變量狮荔,局部變量是會(huì)被捕獲的。

問題: 如果在block中使用成員變量或者調(diào)用實(shí)例的屬性會(huì)有什么不同的結(jié)果

@interface LBHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LBHPerson
- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"%@",self.name);
        NSLog(@"%@",_name);
    };
    block();
}
@end

通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc LBHPerson.m 編譯成底層代碼

struct __LBHPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __LBHPerson__test_block_desc_0* Desc;
  LBHPerson *self;
  __LBHPerson__test_block_impl_0(void *fp, struct __LBHPerson__test_block_desc_0 *desc, LBHPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __LBHPerson__test_block_func_0(struct __LBHPerson__test_block_impl_0 *__cself) {
  LBHPerson *self = __cself->self; // bound by copy

//通過調(diào)用self的getter方法獲取屬性值
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9___44hp612k7gxwp82fznnkpm0000gn_T_LBHPerson_03f602_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
//通過self從其ivars中找到成員變量
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9___44hp612k7gxwp82fznnkpm0000gn_T_LBHPerson_03f602_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LBHPerson$_name)));
    }

__LBHPerson__test_block_impl_0結(jié)構(gòu)體中只捕獲了LBHPerson *self;介粘,在由代碼塊中封裝的函數(shù)中轴合,是通過self間接獲取的。

block 類型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // __NSGlobalBlock__  : NSBlock : NSObject
        void (^block)(void) = ^{
            NSLog(@"Hello");
        };
        
        NSLog(@"%@", [block class]);
        NSLog(@"%@", [[block class] superclass]);
        NSLog(@"%@", [[[block class] superclass] superclass]);
        NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    }
    return 0;
}

注意:在不同版本的xcode上可能會(huì)有差異碗短,但最終都繼承于NSObjcet

從上述打印內(nèi)容可以看出block最終都是繼承自NSBlock類型受葛,而NSBlock繼承于NSObjcet。那么block其中的isa指針其實(shí)是來自NSObject中的。這也更加印證了block的本質(zhì)其實(shí)就是OC對(duì)象总滩。

block的3種類型

通過代碼查看一下block在什么情況下其類型會(huì)各不相同

int c = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 1. 內(nèi)部沒有調(diào)用外部變量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用auto變量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
        
        // 3. 內(nèi)部調(diào)用局部static變量的block
        //static int b = 10;
        //void (^block3)(void) = ^{
        //    NSLog(@"Hello - %d",b);
        //};
        
        // 4. 內(nèi)部調(diào)用全局變量的block
        //void (^block4)(void) = ^{
        //    NSLog(@"Hello - %d",c);
        //};
        
        // 5. 直接調(diào)用的block的class
        NSLog(@"%@ %@ %@ %@ %@", [block1 class], [block2 class],[^{
            NSLog(@"%d",a);
        } class]);
        
    }
    return 0;
}

輸出結(jié)果

__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

block可以分為三種各類型

__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) //全局block
__NSStackBlock__ ( _NSConcreteStackBlock )    //棧區(qū)block
__NSMallocBlock__ ( _NSConcreteMallocBlock ) //堆區(qū)block

看下通過clang編譯結(jié)果

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

struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int a;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __main_block_impl_2 {
  struct __block_impl impl;
  struct __main_block_desc_2* Desc;
  int a;
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在底層C++代碼中纲堵,blockimpl.isa = &_NSConcreteStackBlock;,三個(gè)block的isa指針全部都指向_NSConcreteStackBlock類型地址闰渔。

問題: 為什么底層編譯結(jié)果和打印結(jié)果不一致

runtime運(yùn)行時(shí)過程中也許對(duì)類型進(jìn)行了轉(zhuǎn)變席函。最終類型當(dāng)然以runtime運(yùn)行時(shí)類型也就是我們打印出的類型為準(zhǔn)。

block在內(nèi)存中的存儲(chǔ)

通過下面一張圖看一下不同block的存放區(qū)域

數(shù)據(jù)段中的NSGlobalBlock直到程序結(jié)束才會(huì)被回收冈涧,不過我們很少使用到NSGlobalBlock類型的block茂附,因?yàn)檫@樣使用block并沒有什么意義。

NSStackBlock類型的block存放在棧中督弓,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放营曼,作用域執(zhí)行完畢之后就會(huì)被立即釋放,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉愚隧。

NSMallocBlock是在平時(shí)編碼過程中最常使用到的蒂阱。存放在堆中需要我們自己進(jìn)行內(nèi)存管理。

block是如何定義其類型

block是如何定義其類型狂塘,依據(jù)什么來為block定義不同的類型并分配在不同的空間呢录煤?首先看下面一張圖

1、沒有訪問auto變量是全局block
2荞胡、訪問了auto變量是棧區(qū)block
3妈踊、棧區(qū)block調(diào)用copy變成堆區(qū)block

注意:這是對(duì)MRC環(huán)境得出的結(jié)論,ARC打印結(jié)果會(huì)有差別泪漂,因?yàn)锳RC內(nèi)部會(huì)做很多事情

先關(guān)閉ARC環(huán)境响委,看下這個(gè)結(jié)論是否正確

// MRC環(huán)境!=蚜骸赘风!
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // Global:沒有訪問auto變量:__NSGlobalBlock__
        void (^block1)(void) = ^{
            NSLog(@"block1---------");
        };
        // Stack:訪問了auto變量: __NSStackBlock__
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"block2---------%d", a);
        };
        
        // Global:訪問局部static變量:__NSGlobalBlock__
        static int b = 10;
        void (^block3)(void) = ^{
            NSLog(@"block2---------%d", b);
        };
        
        NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class],[block3 class],[[block2 copy] class]);

    }
    return 0;
}

輸出結(jié)果

  • 沒有訪問auto變量的block是__NSGlobalBlock__類型的,存放在數(shù)據(jù)段中纵刘。
  • 訪問了auto變量的block是__NSStackBlock__類型的邀窃,存放在棧中。
  • __NSStackBlock__類型的block調(diào)用copy成為__NSMallocBlock__類型并被復(fù)制存放在堆中假哎。

NSGlobalBlock類型的我們很少使用到瞬捕,因?yàn)槿绻恍枰L問外界的變量,直接通過函數(shù)實(shí)現(xiàn)就可以了舵抹,不需要使用block肪虎。

但是__NSStackBlock__訪問了auto變量,并且是存放在棧中的惧蛹,上面提到過扇救,棧中的代碼在作用域結(jié)束之后內(nèi)存就會(huì)被銷毀刑枝,那么我們很有可能block內(nèi)存銷毀之后才去調(diào)用他,那樣就會(huì)發(fā)生問題迅腔,通過下面代碼可以證實(shí)這個(gè)問題装畅。

//MRC
void (^block)(void);
void test()
{
    // __NSStackBlock__
    int a = 10;
    block = ^{
        NSLog(@"block---------%d", a);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}

輸出結(jié)果

問題: 可以發(fā)現(xiàn)a的值變?yōu)榱瞬豢煽氐囊粋€(gè)數(shù)字。為什么會(huì)發(fā)生這種情況呢沧烈?

因?yàn)樯鲜龃a中創(chuàng)建的block是__NSStackBlock__類型的掠兄,因此block是存儲(chǔ)在棧中的,那么當(dāng)test函數(shù)執(zhí)行完畢之后锌雀,棧內(nèi)存中block所占用的內(nèi)存已經(jīng)被系統(tǒng)回收蚂夕,因此就有可能出現(xiàn)亂得數(shù)據(jù)。查看其c++代碼可以更清楚的理解腋逆。

為了避免這種情況發(fā)生婿牍,可以通過copy將NSStackBlock類型的block轉(zhuǎn)化為NSMallocBlock類型的block,將block存儲(chǔ)在堆中闲礼,以下是修改后的代碼

//MRC
void (^block)(void);
void test()
{
    // __NSStackBlock__ 調(diào)用copy 轉(zhuǎn)化為__NSMallocBlock__
    int age = 10;
    block = [^{
        NSLog(@"block---------%d", age);
    } copy];
    [block release];
}

看下此時(shí)的輸出結(jié)果

那么其他類型的block調(diào)用copy會(huì)改變block類型嗎牍汹?

所以在平時(shí)開發(fā)過程中MRC環(huán)境下經(jīng)常需要使用copy來保存block铐维,將棧上的block拷貝到堆中柬泽,即使棧上的block被銷毀,堆上的block也不會(huì)被銷毀嫁蛇,需要我們自己調(diào)用release操作來銷毀锨并。而在ARC環(huán)境下系統(tǒng)會(huì)自動(dòng)調(diào)用copy操作,使block不會(huì)被銷毀睬棚。

ARC幫我們做了什么

在ARC環(huán)境下第煮,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy操作,將block復(fù)制到堆上抑党。

將上面的例子放在ARC環(huán)境中包警,看下是否會(huì)被copy到堆區(qū)

//ARC
void (^block)(void);
void test()
{
    // __NSStackBlock__
    int a = 10;
    block = ^{
        NSLog(@"block---------%d", a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
        NSLog(@"== %@",[block class]);
    }
    return 0;
}

輸出結(jié)果

ARC下block被copy到堆區(qū)

block對(duì)對(duì)象變量的捕獲

block一般使用過程中都是對(duì)對(duì)象變量的捕獲,那么對(duì)象變量的捕獲同基本數(shù)據(jù)類型變量相同嗎底靠?

//ARC

@interface LBHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LBHPerson
- (void)dealloc
{
    NSLog(@"%s",__func__);
}
@end

typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            LBHPerson *person = [[LBHPerson alloc] init];
            person.name = @"liu";
            
            block = ^{
                NSLog(@"------block內(nèi)部%@",person.name);
            };
        } // 執(zhí)行完畢害晦,person沒有被釋放
        NSLog(@"--------");
    } // person 釋放
    return 0; 
}

運(yùn)行程序

person是小括號(hào)(116-123行)中的一個(gè)auto變量,按理說它的生命周期超出這個(gè)括號(hào)時(shí)就已經(jīng)結(jié)束暑中,但是在124行的斷點(diǎn)處壹瘟,person的dealloc方法并沒有執(zhí)行,繼續(xù)運(yùn)行

運(yùn)行到斷點(diǎn)126行鳄逾,發(fā)現(xiàn)person調(diào)用了dealloc方法稻轨,此時(shí)person才被銷毀丽惶,由于personauto變量团驱,block代碼塊中有使用了person引镊,所以block會(huì)捕獲person,即block對(duì)person有一個(gè)強(qiáng)引用的止,所以block不被銷毀的話,peroson也不會(huì)銷毀层坠。

查看源代碼

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

將上述代碼轉(zhuǎn)移到MRC環(huán)境下辐宾,在MRC環(huán)境下即使block還在,person卻被釋放掉了询筏。因?yàn)镸RC環(huán)境下block在楅叛撸空間,椣犹祝空間對(duì)外面的person不會(huì)進(jìn)行強(qiáng)引用逆屡。

//MRC環(huán)境下代碼
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            LBHPerson *person = [[LBHPerson alloc] init];
            person.name = @"liu";
            
            block = ^{
                NSLog(@"------block內(nèi)部%@",person.name);
            };
            
            [person release];
        } // person被釋放
        NSLog(@"--------");
    }
    return 0;
}

運(yùn)行結(jié)果

在126行的斷點(diǎn)處person調(diào)用了dealloc,被銷毀了踱讨。

block調(diào)用copy操作之后魏蔗,person不會(huì)被釋放。

block = [^{
   NSLog(@"------block內(nèi)部%d",person.age);
} copy];

前面提到過痹筛,只需要對(duì)椵褐危空間的block進(jìn)行一次copy操作,將椫愠恚空間的block拷貝到堆中谣旁,person就不會(huì)被釋放,說明堆空間的block可能會(huì)對(duì)person進(jìn)行一次retain操作滋早,以保證person不會(huì)被銷毀榄审。堆空間的block自己銷毀之后也會(huì)對(duì)持有的對(duì)象進(jìn)行release操作。

椄唆铮空間上的block不會(huì)對(duì)對(duì)象強(qiáng)引用搁进,堆空間的block有能力持有外部調(diào)用的對(duì)象,即對(duì)對(duì)象進(jìn)行強(qiáng)引用或去除強(qiáng)引用的操作

__weak

__weak添加之后昔头,person在作用域執(zhí)行完畢之后就被銷毀了

typedef void (^Block)(void);
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            LBHPerson *person = [[LBHPerson alloc] init];
            person.name = @"liu";
            
            __weak LBHPerson *waekPerson = person;
            block = ^{
                NSLog(@"------block內(nèi)部%@",waekPerson.name);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m編譯成底層源碼

注意: __weak修飾變量饼问,需要告知編譯器使用ARC環(huán)境及版本號(hào)否則會(huì)報(bào)錯(cuò)

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

__main_block_impl_0中也是用__weak去修飾的

__main_block_copy_0 和 __main_block_dispose_0

當(dāng)block中捕獲對(duì)象類型的變量時(shí),我們發(fā)現(xiàn)block結(jié)構(gòu)體__main_block_impl_0的描述結(jié)構(gòu)體__main_block_desc_0中多了兩個(gè)參數(shù)copydispose函數(shù)揭斧,查看源碼

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};


static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->waekPerson, (void*)src->waekPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->waekPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

copydispose函數(shù)中傳入的都是__main_block_impl_0結(jié)構(gòu)體本身

copy本質(zhì)就是__main_block_copy_0函數(shù)莱革,__main_block_copy_0函數(shù)內(nèi)部調(diào)用_Block_object_assign函數(shù)

dispose本質(zhì)就是__main_block_dispose_0函數(shù)__main_block_dispose_0函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)

_Block_object_assign函數(shù)調(diào)用時(shí)機(jī)及作用

當(dāng)block進(jìn)行copy操作的時(shí)候就會(huì)自動(dòng)調(diào)用__main_block_desc_0內(nèi)部的__main_block_copy_0函數(shù)未蝌,__main_block_copy_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)驮吱。

_Block_object_assign函數(shù)會(huì)自動(dòng)根據(jù)__main_block_impl_0結(jié)構(gòu)體內(nèi)部的person是什么類型的指針對(duì)person對(duì)象產(chǎn)生強(qiáng)引用或者弱引用萧吠。可以理解為_Block_object_assign函數(shù)內(nèi)部會(huì)對(duì)person進(jìn)行引用計(jì)數(shù)器的操作纸型,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__strong類型,則為強(qiáng)引用牧氮,引用計(jì)數(shù)+1踱葛,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__weak類型,則為弱引用光坝,引用計(jì)數(shù)不變尸诽。

_Block_object_dispose函數(shù)調(diào)用時(shí)機(jī)及作用

當(dāng)block從堆中移除時(shí)就會(huì)自動(dòng)調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)盯另。

_Block_object_dispose會(huì)對(duì)person對(duì)象做釋放操作性含,類似于release,也就是斷開對(duì)person對(duì)象的引用鸳惯,而person究竟是否被釋放還是取決于person對(duì)象自己的引用計(jì)數(shù)商蕴。

后續(xù)繼續(xù)補(bǔ)充

Block循環(huán)引用

  • 正常釋放:是指A持有B的引用,當(dāng)A調(diào)用dealloc方法芝发,給B發(fā)送release信號(hào)绪商,B收到release信號(hào),如果此時(shí)B的retainCount(即引用計(jì)數(shù))為0時(shí)后德,則調(diào)用B的dealloc方法

  • 循環(huán)引用:A部宿、B相互持有,所以導(dǎo)致A無法調(diào)用dealloc方法給B發(fā)送release信號(hào),而B也無法接收到release信號(hào)落蝙。所以A、B此時(shí)都無法釋放

如下圖所示:

相關(guān)例子

例1
//代碼一
NSString *name = @"LBH";
self.block = ^(void){
    NSLog(@"%@",self.name);
};
self.block();

//代碼二
UIView animateWithDuration:1 animations:^{
    NSLog(@"%@",self.name);
};

問題: 上述兩段代碼是否出現(xiàn)循環(huán)引用?

解答:
代碼一種發(fā)生了循環(huán)引用,因?yàn)樵?code>block內(nèi)部使用了外部變量name捐顷,導(dǎo)致block持有了self,而self原本是持有block的,所以導(dǎo)致了self和block的相互持有渣蜗。
代碼二中無循環(huán)引用托享,雖然也使用了外部變量,但是self并沒有持有animation的block碧查,僅僅只有animation持有self迄沫,不構(gòu)成相互持有

例2

新建一個(gè)頁面B,它是從頁面A push過來的

//類擴(kuò)展
typedef void(^LBHBlock)(void);
@interface ViewController ()
@property (nonatomic, copy) LBHBlock lbhblock;
@property (nonatomic, copy) NSString *name;
@end

//實(shí)現(xiàn)
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 循環(huán)引用
    self.name = @"lbh";

    [self test1];    
}

- (void)test1
{
   // __weak typeof(self) weakSelf = self;
    
    self.lbhblock = ^{
        NSLog(@"%@",self.name);
    };
}

- (void)dealloc{
    NSLog(@"dealloc 來了");
}

@end

從當(dāng)前的頁面B 返回到頁面A 逝她,頁面B的dealloc并沒有執(zhí)行宁昭,由于循環(huán)引用導(dǎo)致頁面無法釋放

解決循環(huán)引用

解決循環(huán)引用常見的方式有以下幾種:

1疆拘、weak-strong-dance
2、__block修飾對(duì)象(需要注意的是在block內(nèi)部需要置空對(duì)象,而且block必須調(diào)用
3、傳遞對(duì)象self作為block的參數(shù)白华,提供給block內(nèi)部使用
4潮太、使用NSProxy

weak-strong-dance

  • 如果block內(nèi)部并未嵌套block,直接使用__weak修飾self即可
- (void)test1
{
    __weak typeof(self) weakSelf = self;
    
    self.lbhblock = ^{
        NSLog(@"%@",weakSelf.name);
    };
}

此時(shí)的weakSelf 和 self 指向同一片內(nèi)存空間朋沮,且使用__weak不會(huì)導(dǎo)致self的引用計(jì)數(shù)發(fā)生變化纠亚,可以通過打印weakSelf和self的指針地址条篷,以及self的引用計(jì)數(shù)來驗(yàn)證

- (void)test2
{
    NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
     __weak typeof(self) weakSelf = self;
     NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
     
     self.lbhblock = ^{
         NSLog(@"%@",weakSelf.name);
     };
     NSLog(@"%p %p",weakSelf, self);
     NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self.lbhblock)));
     NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
}
  • 如果block內(nèi)部嵌套block,需要同時(shí)使用__weak 和 __strong
- (void)test3
{
    __weak typeof(self) weakSelf = self;
    
    self.lbhblock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%s %@",__func__,strongSelf.name);
        });
    };
    
    self.lbhblock();
}

運(yùn)行結(jié)果

【注意】:實(shí)際上嵌套的block內(nèi)部使用weakSelf并不一定會(huì)出現(xiàn)問題蚕冬,不過為了程序的嚴(yán)謹(jǐn)通常還是會(huì)使用strongSelf

__block修飾變量

這種方式同樣依賴于中介者模式,屬于手動(dòng)釋放忧侧,是通過__block修飾對(duì)象躺屁,主要是因?yàn)開_block修飾的對(duì)象是可以改變的

- (void)test4
{
    __block ViewController *vc = self;
    
    self.lbhblock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%s %@",__func__,vc.name);
            vc = nil;
        });
    };
    
    self.lbhblock();
}

運(yùn)行結(jié)果

【注意】 這里的block必須調(diào)用耐亏,如果不調(diào)用block,vc就不會(huì)置空李根,那么依舊是循環(huán)引用囱持,self和block都不會(huì)被釋放

對(duì)象self作為參數(shù)

主要是將對(duì)象self作為參數(shù),提供給block內(nèi)部使用,不會(huì)有引用計(jì)數(shù)問題

//聲明一個(gè)新的block
typedef void(^LBH2Block)(ViewController *);

@property (nonatomic, copy) LBH2Block lbh2block;

- (void)test5
{
    self.lbh2block = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%s %@",__func__,vc.name);
        });
    };
    self.lbh2block(self);
}

運(yùn)行結(jié)果

NSProxy 虛擬類

  • OC是只能單繼承的語言枯怖,但是它是基于運(yùn)行時(shí)的機(jī)制,所以可以通過NSProxy來實(shí)現(xiàn) 偽多繼承椒袍,填補(bǔ)了多繼承的空白

  • NSProxyNSObject是同級(jí)的一個(gè)類辨赐,也可以說是一個(gè)虛擬類,只是實(shí)現(xiàn)了NSObject的協(xié)議

  • NSProxy 其實(shí)是一個(gè)消息重定向封裝的一個(gè)抽象類,類似一個(gè)代理人式散,中間件,可以通過繼承它响驴,并重寫下面兩個(gè)方法來實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
使用場(chǎng)景

NSProxy的使用場(chǎng)景主要有兩種:

  • 實(shí)現(xiàn)多繼承功能
  • 解決了NSTimer&CADisplayLink創(chuàng)建時(shí)對(duì)self強(qiáng)引用問題,參考YYKitYYWeakProxy撕蔼。
循環(huán)引用解決原理

主要是通過自定義的NSProxy類的對(duì)象來代替self豁鲤,并使用方法實(shí)現(xiàn)消息轉(zhuǎn)發(fā)

下面是NSProxy子類的實(shí)現(xiàn)以及使用的場(chǎng)景

step1 自定義一個(gè)NSProxy的子類LBHProxy

@interface LBHProxy : NSProxy

- (id)transformObjc:(NSObject *)objc;

+ (instancetype)proxyWithObjc:(id)objc;

@end

@interface LBHProxy ()

@property(nonatomic, weak, readonly) NSObject *objc;

@end

@implementation LBHProxy

- (id)transformObjc:(NSObject *)objc{
   _objc = objc;
    return self;
}

+ (instancetype)proxyWithObjc:(id)objc{
    return  [[self alloc] transformObjc:objc];
}



//2.有了方法簽名之后就會(huì)調(diào)用方法實(shí)現(xiàn)
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.objc respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.objc];
    }
}

//1、查詢?cè)摲椒ǖ姆椒ê灻?- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    NSMethodSignature *signature;
    if (self.objc) {
        signature = [self.objc methodSignatureForSelector:sel];
    }else{
        signature = [super methodSignatureForSelector:sel];
    }
    return signature;
}

- (BOOL)respondsToSelector:(SEL)aSelector{
    return [self.objc respondsToSelector:aSelector];
}


@end

step2: 自定義Cat類和Dog類

//********Cat類********
@interface Cat : NSObject
@end

@implementation Cat
- (void)eat{
   NSLog(@"貓吃魚");
}
@end

//********Dog類********
@interface Dog : NSObject
@end

@implementation Dog
- (void)shut{
    NSLog(@"狗叫");
}
@end

step3: 通過LBHProxy實(shí)現(xiàn)多繼承功能

- (void)lbh_proxyTest{
    Dog *dog = [[Dog alloc] init];
    Cat *cat = [[Cat alloc] init];
    LBHProxy *proxy = [LBHProxy alloc];
    
    [proxy transformObjc:cat];
    [proxy performSelector:@selector(eat)];
    
    [proxy transformObjc:dog];
    [proxy performSelector:@selector(shut)];
}

通過LBHProxy解決定時(shí)器中self的強(qiáng)引用問題

self.timer = [NSTimer timerWithTimeInterval:1 target:[LBHProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

總結(jié)

循環(huán)應(yīng)用的解決方式從根本上來說就兩種,以self -> block -> self為例

  • 打破self 對(duì) block的強(qiáng)引用视译,可以block屬性修飾符使用weak,但是這樣會(huì)導(dǎo)致block還沒創(chuàng)建完就釋放了省古,所以從這里打破強(qiáng)引用行不通

  • 打破block對(duì)self的強(qiáng)引用,主要就是self的作用域和block作用域的通訊,通訊有代理蔼两、傳值抑胎、通知歧蒋、傳參等幾種方式,用于解決循環(huán)鲫咽,常見的解決方式如下:

    • weak-strong-dance

    • __block(block內(nèi)對(duì)象置空,且調(diào)用block)

    • 將對(duì)象self作為block的參數(shù)

    • 通過NSProxy的子類代替self

問題: block為什么需要調(diào)用?

解答:

block在底層是類型為__main_block_impl_0結(jié)構(gòu)體河绽,通過其同名構(gòu)造函數(shù)創(chuàng)建,第一個(gè)傳入的block的內(nèi)部實(shí)現(xiàn)代碼塊鉴未,即__main_block_func_0惰聂,用fp表示,然后賦值給impl的FuncPtr屬性换棚,然后在main中進(jìn)行了調(diào)用扮叨,這也是block為什么需要調(diào)用的原因戴质。如果不調(diào)用漾稀,block內(nèi)部實(shí)現(xiàn)的代碼塊將無法執(zhí)行,可以總結(jié)為以下兩點(diǎn)

  • 函數(shù)聲明:即block內(nèi)部實(shí)現(xiàn)聲明成了一個(gè)函數(shù)__main_block_func_0
  • 執(zhí)行具體的函數(shù)實(shí)現(xiàn):通過調(diào)用block的FuncPtr指針建瘫,調(diào)用block執(zhí)行

4.3 __block的原理

對(duì)a加一個(gè)__block崭捍,然后在block中對(duì)a進(jìn)行++操作

int main(){

    __block int a = 11;
    void(^block)(void) = ^{
        a++;
        printf("%d", a);
    };
    
     block();
    return 0;
}

底層編譯成如下

struct __Block_byref_a_0 {//__block修飾的外界變量的結(jié)構(gòu)體
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {//block的結(jié)構(gòu)體類型
  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) {//構(gòu)造方法
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//block內(nèi)部實(shí)現(xiàn)
  __Block_byref_a_0 *a = __cself->a; // bound by ref 指針拷貝,此時(shí)的對(duì)象a 與 __cself對(duì)象的a 指向同一片地址空間
        //等同于 外界的 a++
        (a->__forwarding->a)++;
        printf("%d", (a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

int main(){
    //__Block_byref_a_0 是結(jié)構(gòu)體啰脚,a 等于 結(jié)構(gòu)體的賦值殷蛇,即將外界變量a 封裝成對(duì)象
    //&a 是外界變量a的地址
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
    //__main_block_impl_0中的第三個(gè)參數(shù)&a,是封裝的對(duì)象a的地址
    void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
  • main中的a是通過外界變量封裝的對(duì)象

  • __main_block_impl_0中橄浓,將對(duì)象a的地址&a給構(gòu)造函數(shù)

  • __main_block_func_0內(nèi)部對(duì)a的處理是指針拷貝粒梦,此時(shí)創(chuàng)建的對(duì)象a與傳入對(duì)象的a指向同一片內(nèi)存空間

總結(jié):
  • 外界變量會(huì)生成__Block_byref_a_0結(jié)構(gòu)體,結(jié)構(gòu)體用來保存原始變量的指針和值

  • 將變量生成的結(jié)構(gòu)體對(duì)象的指針地址傳遞給block荸实,然后在block內(nèi)部就可以對(duì)外界變量進(jìn)行操作了

兩種拷貝對(duì)比如下

  • 值拷貝 - 深拷貝匀们,只是拷貝數(shù)值,且拷貝的值不可更改准给,指向不同的內(nèi)存空間泄朴,案例中普通變量a就是值拷貝

  • 指針拷貝 - 淺拷貝,生成的對(duì)象指向同一片內(nèi)存空間露氮,案例中經(jīng)過__block修飾的變量a就是指針拷貝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末祖灰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子畔规,更是在濱河造成了極大的恐慌局扶,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叁扫,死亡現(xiàn)場(chǎng)離奇詭異三妈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陌兑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門沈跨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兔综,你說我怎么就攤上這事饿凛。” “怎么了软驰?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵涧窒,是天一觀的道長横腿。 經(jīng)常有香客問我义黎,道長,這世上最難降的妖魔是什么聚至? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任慧瘤,我火速辦了婚禮戴已,結(jié)果婚禮上固该,老公的妹妹穿的比我還像新娘。我一直安慰自己糖儡,他們只是感情好伐坏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著握联,像睡著了一般桦沉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上金闽,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天纯露,我揣著相機(jī)與錄音,去河邊找鬼代芜。 笑死埠褪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜒犯。 我是一名探鬼主播组橄,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼罚随!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起羽资,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤淘菩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屠升,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潮改,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年腹暖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汇在。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脏答,死狀恐怖糕殉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情殖告,我是刑警寧澤阿蝶,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站黄绩,受9級(jí)特大地震影響羡洁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爽丹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一筑煮、第九天 我趴在偏房一處隱蔽的房頂上張望辛蚊。 院中可真熱鬧,春花似錦真仲、人聲如沸嚼隘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽飞蛹。三九已至,卻和暖如春灸眼,著一層夾襖步出監(jiān)牢的瞬間卧檐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工焰宣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霉囚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓匕积,卻偏偏與公主長得像盈罐,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闪唆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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