Object-C中的Block

来源:互联网 发布:js 获取手机版本号 编辑:程序博客网 时间:2024/05/16 16:58

1、什么是Block

Block是一种特殊的数据类型

2、Block的作用

  • 用于保存一段代码。可以在恰当的时间取出来调用
  • 功能类似于函数和方法

3、Block的格式

返回值(^block变量名)(形参列表) =  ^( 形参列表){};

这里写图片描述

  • 无参数无返回值

    void (^sunBlock)();sunBlock = ^{    NSLog(@"sunBlock");};sunBlock();
  • 有参数无返回值

    void(^sunBlock)(int,int);sunBlock = ^(int value1,int value2){    NSLog(@"%d",value1 + value2);};sunBlock(10,20);
  • 有参数有返回值

        int (^sunBlock)(int,int);sunBlock = ^(int value1,int value2){    return value1 + value2;};NSLog(@"%d",sunBlock(10,20));

4、typedef 和Block

利用typedef给block起别名,和指向函数的指针一样,block变量的名称就是别名

typedef int (^calculateBlock)(int,int);int main(int argc, const char * argv[]) {    calculateBlock sumBlock  = ^(int value1,int value2){        return value1 + value2;    };    NSLog(@"%d",sumBlock(20,10));    calculateBlock minusBlock  = ^(int value1,int value2){        return value1 - value2;    };    NSLog(@"%d",minusBlock(20,10));}

5、Block的底层实现

  • 原文件:
int main(int argc, const char * argv[]) {    ^{ };    return 0;}
  • 通过clang命令将OC转为C++代码来查看一下Block底层实现,clang命令使用方式为终端使用cd定位到main.m文件所在文件夹,然后利用clang -rewrite-objc main.m将OC转为C++,成功后在main.m同目录下会生成一个main.cpp文件

     struct __block_impl {    void *isa; //isa,指向所属类的指针,也就是block的类型    int Flags; //flags,标志变量,在实现block的内部操作时会用到    int Reserved; //Reserved,保留变量    void *FuncPtr; //block执行时调用的函数指针};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;  //__main_block_impl_0的isa指针指向了_NSConcreteStackBlock    impl.Flags = flags;    impl.FuncPtr = fp; //从main函数中看, __main_block_impl_0FuncPtr指向了函数__main_block_func_0    Desc = desc; //__main_block_impl_0Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。      }};    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {} static struct __main_block_desc_0 {      size_t reserved; //保留字段  size_t Block_size; //block大小(sizeof(struct __main_block_impl_0))} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};//以上代码在定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA,并给它赋值,以供在main函数中对__main_block_impl_0进行初始化。int main(int argc, const char * argv[]) {    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));    return 0;}

    1、__block_impl结构体,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象
    2、__main_block_impl_0结构体,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的,如果是全局的blcok,就根据变量名和出现序列进行命名。
    3、__main_block_impl_0中包含了俩个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。
    4、__main_block_func_0函数,其实对应的block的函数体,该函数接受了一个__cself参数,其实就是对应的block本身
    5、__main_block_desc_0结构体,其中比较有价值的信息是block的大小
    6、main函数对block的创建,可以看出执行block就是调用一个以block自身为参数的函数,这个函数对应着block的执行体。

6、 Block的分类

  • NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
  • NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
  • NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。

NSConcreteGlobalBlock 类型的 block 的实现

  • 原文件:
void (^testGlobalBlock)() = ^{    NSLog(@"hello block");};int main(int argc, const char * argv[]) {    testGlobalBlock();    return 0;}
  • 对其执行clang -rewrite-objc编译转换成C++实现,编译转换结构之后:
struct __testGlobalBlock_block_impl_0 {  struct __block_impl impl;  struct __testGlobalBlock_block_desc_0* Desc;  __testGlobalBlock_block_impl_0(void *fp, struct __testGlobalBlock_block_desc_0 *desc, int flags=0) {    impl.isa = &_NSConcreteGlobalBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};static void __testGlobalBlock_block_func_0(struct __testGlobalBlock_block_impl_0 *__cself) {    NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9_pvqt_jvs14545ml2f862g1hc0000gn_T_main_f42a68_mi_0);}static struct __testGlobalBlock_block_desc_0 {  size_t reserved;  size_t Block_size;} __testGlobalBlock_block_desc_0_DATA = { 0, sizeof(struct __testGlobalBlock_block_impl_0)};static __testGlobalBlock_block_impl_0 __global_testGlobalBlock_block_impl_0((void *)__testGlobalBlock_block_func_0, &__testGlobalBlock_block_desc_0_DATA);void (*testGlobalBlock)() = ((void (*)())&__global_testGlobalBlock_block_impl_0);int main(int argc, const char * argv[]) {    ((void (*)(__block_impl *))((__block_impl *)testGlobalBlock)->FuncPtr)((__block_impl *)testGlobalBlock);    return 0;}

可以看出testGlobalBlock的isa指向了_NSConcreteGlobalBlock,即在全局区域创建,block变量存储在全局数据存储区

NSConcreteStackBlock 类型的 block 的实现

  • 原文件:
int main(int argc, const char * argv[]) {    void (^testStackBlock)() = ^{        NSLog(@"hello block");    };    testStackBlock();    return 0;}
  • 对其执行clang -rewrite-objc编译转换成C++实现,编译转换结构之后:
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) {        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9_pvqt_jvs14545ml2f862g1hc0000gn_T_main_bb5942_mi_0);    }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[]) {    void (*testStackBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));    ((void (*)(__block_impl *))((__block_impl *)testStackBlock)->FuncPtr)((__block_impl *)testStackBlock);    return 0;}

可以看出testStackBlock的isa指向了_NSConcreteStackBlock,即在栈区创建。

NSConcreteMallocBlock 类型的 block 的实现

NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。

static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout *aBlock;    ...    aBlock = (struct Block_layout *)arg;    ...    // Its a stack block.  Make a copy.    if (!isGC) {        // 申请block的堆内存        struct Block_layout *result = malloc(aBlock->descriptor->size);        if (!result) return (void *)0;        // 拷贝栈中block到刚申请的堆内存中        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first        // reset refcount        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed        result->flags |= BLOCK_NEEDS_FREE | 1;        // 改变isa指向_NSConcreteMallocBlock,即堆block类型        result->isa = _NSConcreteMallocBlock;        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);            (*aBlock->descriptor->copy)(result, aBlock); // do fixup        }        return result;    }    else {        ...    }}

函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock。
block主要的一些学问就出在栈中block向堆中block的转移过程中了。

7、Block的应用

Block中访问局部变量

  • 在Block中访问局部变量
int main(int argc, const char * argv[]) {    int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:10
  • 在声明Block之后,调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之前的旧值
int main(int argc, const char * argv[]) {    int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNum = 20;    testNumBlock();    return 0;}打印结果:10
  • 在Block中不可以直接修改局部变量
int main(int argc, const char * argv[]) {    int testNum = 10;    void(^testNumBlock)() = ^{        testNum = 20; //报错        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}

通过clang命令将OC转为C++代码查看Block底层实现
OC原码:

int main(int argc, const char * argv[]) {    int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}

C++代码:

    //结构体的第三个元素是局部变量testNum的值    void(*testNumBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, testNum));    //结构体__main_block_impl_0的代码,其中testNum(_testNum)是构造函数的参数列表初始化形式,相当于testNum = _testNum。struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int testNum;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _testNum, int flags=0) : testNum(_testNum) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};//在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针testNumBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法    ((void (*)(__block_impl *))((__block_impl *)testNumBlock)->FuncPtr)((__block_impl *)testNumBlock);//__main_block_func_0方法代码如下,由此可见NSLog的testNum正是定义Block时为结构体传进去的局部变量testNum的值static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  int testNum = __cself->testNum; // bound by copy        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9_pvqt_jvs14545ml2f862g1hc0000gn_T_main_fbcaf1_mi_0,testNum);    }在Block定义时便是将局部变量的值传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值,同时内部的值也是不可修改的,因为main函数中的testNum和__main_block_func_0函数中的testNum并没有在同一个作用域,所以在block对testNum进行赋值是没有意义的,所以编译器给出了错误

Block内访问__block修饰的局部变量

  • 在局部变量前使用下划线下划线block修饰,在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值
__block int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNum = 20;    testNumBlock();打印结果:20
  • 在局部变量前使用下划线下划线block修饰,在Block中可以直接修改局部变量
int main(int argc, const char * argv[]) {    __block int testNum = 10;    void(^testNumBlock)() = ^{        testNum = 20;        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:20

通过clang命令将OC转为C++代码查看Block底层实现
OC原码:

int main(int argc, const char * argv[]) {    __block int testNum = 10;    void(^testNumBlock)() = ^{        testNum = 20;        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}

C++代码:

    //结构体的第三个元素是局部变量testNum的指针    void(*testNumBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_testNum_0 *)&testNum, 570425344));    在局部变量前使用__block修饰,在Block定义时便是将局部变量的指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block内访问全局变量

  • 在Block中可以访问全局变量
int testNum = 10;int main(int argc, const char * argv[]) {    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:10
  • 在声明Block之后、调用Block之前对全局变量进行修改,在调用Block时全局变量值是修改之后的新值
int testNum = 10;int main(int argc, const char * argv[]) {    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNum = 20;    testNumBlock();    return 0;}打印结果:20
  • 在Block中可以直接修改全局变量
int testNum = 10;int main(int argc, const char * argv[]) {    void(^testNumBlock)() = ^{        testNum = 20;        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:20

通过clang命令将OC转为C++代码查看Block底层实现
OC原码:

int testNum = 10;int main(int argc, const char * argv[]) {    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}

C++代码:

//结构体中并未保存全局变量testNum的值或者指针    void(*testNumBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//结构体__main_block_impl_0的代码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;  }};//在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针testNumBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法    ((void (*)(__block_impl *))((__block_impl *)testNumBlock)->FuncPtr)((__block_impl *)testNumBlock);//__main_block_func_0方法代码如下,由此可见NSLog的testNum还是全局变量testNum的值static void __main_block_func_0(struct __main_block_impl_0 *__cself) {        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9_pvqt_jvs14545ml2f862g1hc0000gn_T_main_a3d563_mi_0,testNum);    }全局变量所占用的内存只有一份,供所有函数共同调用,在Block定义时并未将全局变量的值或者指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的    

Block内访问静态变量

  • 在Block中可以访问静态变量
int main(int argc, const char * argv[]) {    static int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:10
  • 在声明Block之后、调用Block之前对静态变量进行修改,在调用Block时静态变量值是修改之后的新值
int main(int argc, const char * argv[]) {    static int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNum = 20;    testNumBlock();    return 0;}打印结果:20
  • 在Block中可以直接修改静态变量
int main(int argc, const char * argv[]) {    static int testNum = 10;    void(^testNumBlock)() = ^{        testNum = 20;        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}打印结果:20

通过clang命令将OC转为C++代码查看Block底层实现
OC原码:

int main(int argc, const char * argv[]) {    static int testNum = 10;    void(^testNumBlock)() = ^{        NSLog(@"%d",testNum);    };    testNumBlock();    return 0;}

C++代码:

    //结构体的第三个元素是静态变量testNum的指针    void(*testNumBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &testNum));//结构体__main_block_impl_0的代码struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int *testNum;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_testNum, int flags=0) : testNum(_testNum) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};//在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针testNumBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法    ((void (*)(__block_impl *))((__block_impl *)testNumBlock)->FuncPtr)((__block_impl *)testNumBlock);//__main_block_func_0方法代码如下,由此可见NSLog的testNum正是定义Block时为结构体传进去的静态变量testNum的指针static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  int *testNum = __cself->testNum; // bound by copy        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9_pvqt_jvs14545ml2f862g1hc0000gn_T_main_a43939_mi_0,(*testNum));    }在Block定义时便是将静态变量的指针传给Block变量所指向的结构体,因此在调用Block之前对静态变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block作为参数传递

typedef void(^TestBlock)();NSMutableArray *array;void test(){    int a = 10;    TestBlock blcok = ^{        NSLog(@"%d",a);    };    [array addObject:blcok];    NSLog(@"%@",blcok);}int main(int argc, const char * argv[]) {    array = [[NSMutableArray alloc]init];    test();    TestBlock blockk = [array lastObject];    blockk();    NSLog(@"%@",blockk);    return 0;}   结果:在ARC下: test2[2423:124143] <__NSMallocBlock__: 0x1004037f0> test2[2423:124143] 10 test2[2423:124143] <__NSMallocBlock__: 0x1004037f0>在非ARC下:程序崩溃 test2[2449:125851] <__NSStackBlock__: 0x7fff5fbff6f8>

1、在非ARC下,TestBlock的isa指向NSStackBlock,当函数退出后,相应的堆被销毁,block也就不存在了,在经过copy或retain之后,对象的类型从NSStackBlock变为了NSMallocBlock,在函数结束后依然可以访问,在非ARC环境下,copy或retain了block后一定要在使用后release,不然会有内存泄露,而且泄露点是在系统级,在Instruments里跟不到问题触发点,比较上火。

2、ARC情况下,系统会将捕获了外部变量的block进行了copy。所以返回类型为NSMallocBlock,在函数结束后依然可以访问


如果把blcok中的代码不再访问变量:

TestBlock blcok = ^{        NSLog(@"demo");    };结果:ARC和非ARC得结果一致test2[2484:128052] <__NSGlobalBlock__: 0x100005290>test2[2484:128052] demotest2[2484:128052] <__NSGlobalBlock__: 0x100005290>

Block作为返回值

  • 非ARC中
- (testBlcok) myTestBlock {    __block int val = 10;    return ^{        NSLog(@"val = %d", val);    };}结果:Xcode就会提示报错Returning block that lives on the local stack

在向外传递block的时候一定也要做到,传给外面一个在堆上的,autorelease的对象。

- (testBlcok) myTestBlock {    __block int val = 10;    return [[^{        NSLog(@"val = %d", val);    } copy] autorelease];}
  • ARC中
- (testBlcok) myTestBlock {    __block int val = 10;    return ^{        NSLog(@"val = %d", val);    };}结果:正常

在ARC环境下,当block作为参数返回的时候,block也会自动被移到堆上。

Block作为属性

ARC 和非ARC得声明一样

@property (strong, nonatomic) TestBlock *strongBlock;@property (copy, nonatomic) TestBlock *copyBlock;

7、Block在MRC及ARC下的内存管理

Block在MRC下的内存管理

  • 默认情况下,Block的内存存储在栈中,不需要开发人员对其进行内存管理
- (void)viewDidLoad {    [super viewDidLoad];    void(^testBlock)() = ^{        NSLog(@"------");    };    testBlock();}结果:当testBlock变量出了作用域,testBlock的内存会被自动释放
  • 在Block的内存存储在栈中时,如果在Block中引用了外面的对象,不会对所引用的对象进行任何操作
- (void)viewDidLoad {    [super viewDidLoad];    Student *stu = [[Student alloc]init];     void(^testBlock)() = ^{        NSLog(@"%@",stu);    };    testBlock();    [stu release];}结果:Student可以正常释放
  • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,这时需要对其进行release操作来管理内存
- (void)viewDidLoad {    [super viewDidLoad];    void(^testBlock)() = ^{        NSLog(@"testBlock");    };    testBlock();    Block_copy(testBlock);    Block_release(testBlock);}结果:Block正常释放
  • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,即使在Block自身调用了release操作之后,Block也不会对所引用的对象进行一次release操作,这时会造成内存泄漏
- (void)viewDidLoad {    [super viewDidLoad];    Student *stu = [[Student alloc]init];    void(^testBlock)() = ^{        NSLog(@"%@",stu);    };    testBlock();    Block_copy(testBlock);    Block_release(testBlock);    [stu release];}结果:Student无法正常被释放,因为其在Block中被进行了一次retain操作
  • 如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,为了不对所引用的对象进行一次retain操作,可以在对象的前面使用__block来修饰
- (void)viewDidLoad {    [super viewDidLoad];    __block Student *stu = [[Student alloc]init];    void(^testBlock)() = ^{        NSLog(@"%@",stu);    };    testBlock();    Block_copy(testBlock);    Block_release(testBlock);    [stu release];}结果:Student可以正常释放
  • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

    • 第一种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();@end----------------------------------------------@implementation Student-(void)dealloc{NSLog(@"%s",__func__);Block_release(_testBlock);[super dealloc];}@end-----------------------------------------------(void)viewDidLoad {[super viewDidLoad];Student *stu = [[Student alloc]init];stu.testBlock = ^{    NSLog(@"%@",stu);};stu.testBlock();[stu release];}结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Student对象进行一次retain操作,导致循环引用无法释放
    • 第二种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();-(void)resetBlock;@end----------------------------------------------@implementation Student-(void)resetBlock{self.testBlock = ^{    NSLog(@"%@",self);};}-(void)dealloc{NSLog(@"%s",__func__);Block_release(_testBlock);[super dealloc];}@end-----------------------------------------------(void)viewDidLoad {[super viewDidLoad];Student *stu = [[Student alloc]init];[stu resetBlock];[stu release];}结果:Student对象在这里无法正常释放,虽然表面看起来一个alloc对应一个release符合内存管理规则,但是实际在resetBlock方法实现中,Block内部对self进行了一次retain操作,导致循环引用无法释放
  • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是在对象的前面使用下划线下划线block来修饰,以避免Block对对象进行retain操作

    • 第一种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();@end----------------------------------------------@implementation Student-(void)dealloc{    NSLog(@"%s",__func__);    Block_release(_testBlock);    [super dealloc];}@end----------------------------------------------- (void)viewDidLoad {    [super viewDidLoad];     __block Student *stu = [[Student alloc]init];    stu.testBlock = ^{        NSLog(@"%@",stu);    };    stu.testBlock();     [stu release];    }    结果:Student可以正常释放
    • 第二种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();-(void)resetBlock;@end    ----------------------------------------------@implementation Student-(void)resetBlock{// 这里为了通用一点,可以使用__block typeof(self) stu = self;__block Student *stu = self;self.testBlock = ^{    NSLog(@"%@",stu);    };}-(void)dealloc{    NSLog(@"%s",__func__);    Block_release(_testBlock);    [super dealloc];}@end----------------------------------------------- (void)viewDidLoad {    [super viewDidLoad];     Student *stu = [[Student alloc]init];    [stu resetBlock];    [stu release];}结果:Student可以正常释放

    Block在ARC下的内存管理

  • 在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可
- (void)viewDidLoad {    [super viewDidLoad];    void(^testBlock)() = ^{        NSLog(@"testBlock");    };    testBlock();}结果:当Block变量出了作用域,Block的内存会被自动释放
  • 在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏
- (void)viewDidLoad {    [super viewDidLoad];    Student *stu = [[Student alloc]init];    void(^testBlock)() = ^{        NSLog(@"%@",stu);    };    testBlock();}结果:Student可以正常释放
  • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

    • 第一种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();@end----------------------------------------------@implementation Student-(void)dealloc{NSLog(@"%s",__func__);}@end-----------------------------------------------(void)viewDidLoad {[super viewDidLoad]; Student *stu = [[Student alloc]init]; stu.testBlock = ^{    NSLog(@"%@",stu);};stu.testBlock();}结果:因为testBlock作为Student的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放
    • 第二种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();- (void)resetBlock;@end----------------------------------------------@implementation Student- (void)resetBlock{    self.testBlock = ^{        NSLog(@"------%@", self);    };}-(void)dealloc{    NSLog(@"%s",__func__);}@end----------------------------------------------- (void)viewDidLoad {    [super viewDidLoad];    Student *stu = [[Student alloc]init];    [stu resetBlock];}结果:Student对象在这里无法正常释放,在testBlock方法实现中,Block内部对self进行了一次强引用,导致循环引用无法释放
  • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样避免了Block对对象进行强引用

    • 第一种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();@end----------------------------------------------@implementation Student-(void)dealloc{    NSLog(@"%s",__func__);}@end----------------------------------------------- (void)viewDidLoad {[super viewDidLoad];Student *stu = [[Student alloc]init];__weak typeof(stu) weakS = stu;stu.testBlock = ^{    NSLog(@"------%@", weakS);};stu.testBlock();// Student对象在这里可以正常被释放}
    • 第二种情况

      @interface Student : NSObject@property (nonatomic,copy) void(^testBlock)();-(void)resetBlock;@end----------------------------------------------@implementation Student-(void)resetBlock{    //这里为了通用一点,可以使用__weak typeof(self) weakP = self;    __weak Student *stu = self;    self.testBlock = ^{        NSLog(@"------%@", self);};}-(void)dealloc{    NSLog(@"%s",__func__);}@end-----------------------------------------------(void)viewDidLoad {    [super viewDidLoad];    Student *stu = [[Student alloc]init];    [stu resetBlock];}结果:Student可以正常释放

注: 关于下划线下划线block关键字在MRC和ARC下的不同

__block在MRC下有两个作用
1. 允许在Block中访问和修改局部变量
2. 禁止Block对所引用的对象进行隐式retain操作

__block在ARC下只有一个作用
1. 允许在Block中访问和修改局部变量