Block在ARC和MRC下的使用分析

/ 0评 / 0

一、明确两点

1.Block可以访问Block函数以及语法作用域以内的外部变量。也就是说:一个函数里定义了一个block,这个block可以访问该函数的内部变量(当然还包括静态,全局变量),即block可以使用和本身定义范围相同的变量。

2.Block其实是特殊的Objective-C对象,可以使用copy、release等来管理内存,但和一般的NSObject的管理方式有些不同,稍后会说明。

二、Block语法

Block很像函数指针,这从Block的语法上就可以看出。

Block的原型:

返回值 (^名称)(参数列表)

Block的定义

^ 返回值类型 (参数列表) { 表达式 }

其中返回值类型和参数列表都可以省略,最简单的Block就是:

^{ ; };

一般的定义就是:

返回值 (^名称)(参数列表) = ^(参数列表){代码段};

为了方便通常使用typedef定义:

typedef void (^blk) (void);

三、Block存储域

Block能够截获自动变量,自动变量的当前值会被拷贝到栈上作为常量,此时不能在Block内对自动变量进行赋值操作,如果有这种需求,则需要该变量是:

1.静态变量

2.全局变量

3.或者使用__block修饰符

根据Block中是否引用了自动变量,可以将Block存储区域分类:

1.__NSStackBlock__存储在栈上

2.__NSGlobalBlock__存储在全局数据区域(和全局变量一样)

3.__NSMallocBlock__存储在堆上

没有引用自动变量或者在全局作用域的Block为__NSGlobalBlock__,其他的基本上都是__NSStackBlock__。对__NSStackBlock__执行copy操作会生成__NSMallocBlock__。

一般来说出问题的Block大部分都是__NSStackBlock__,超过了__NSStackBlock__的作用域__NSStackBlock__就会销毁。

四、对Block执行retain,copy方法的效果

Block是C语言的扩展,C语法也可以使用Block的语法,对应的C语言使用Block_copy、Block_release。

无论是__NSStackBlock__,还是__NSGlobalBlock__,执行retain都不起作用。而__NSMallocBlock__执行retain引用计数+1。

对于copy操作,__NSStackBlock__会被复制到堆上得到一份新的__NSMallocBlock__,__NSStackBlock__还是存在的,而__NSGlobalBlock__执行copy操作不起作用。而对__NSMallocBlock__执行copy操作会引起引用计数加1。

五、什么时候要对__NSStackBlock__执行copy操作?

配置在栈上的Block也就是__NSStackBlock__类型的Block,如果其所属的变量作用域结束该Block就会废弃。这个时候如果继续使用该Block,就应该使用copy方法,将__NSStackBlock__拷贝为__NSMallocBlock__。当__NSMallocBlock__的引用计数变为0,该__NSMallocBlock__就会被释放。

如果是非ARC环境,需要显式的执行copy或者antorelease方法。

而当ARC有效的时候,实际上大部分情况下编译器已经为我们做好了,自动的将Block从栈上复制到堆上。包括以下几个情况:

1.Block作为返回值时,类似在非ARC的时候,对返回值Block执行[[returnedBlock copy] autorelease];

2.方法的参数中传递Block时

3.Cocoa框架中方法名中还有useringBlock等时

4.GCD相关的一系列API传递Block时

比如:[mutableAarry addObject:stackBlock];这段代码在非ARC环境下肯定有问题,而在ARC环境下方法参数中传递__NSStackBlock__会自动执行copy,所以就不会出现问题。

六、Block的循环引用

对于非ARC下,为了防止循环引用,我们使用__block来修饰在Block中使用的对象。

对于ARC下,为了防止循环引用,我们使用__weak来修饰在Block中使用的对象。

原理就是:ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该对象的引用计数+1。

七、代码验证

验证__NSStackBlock__

我们往可变数组中添加一个__NSStackBlock__。

在MRC环境下,__NSStackBlock__在自身作用域结束后就从栈中释放,所以我们再次使用它的时候,程序就会发生崩溃。

在ARC环境下,编译器会自动对__NSStackBlock__执行copy,拷贝一份到堆上生成一个新的__NSMallocBlock__,我们再次使用它的时候,程序正常运行。

#import <Foundation/Foundation.h>

void addBlockToArray(NSMutableArray *arrayM) {
    int a = 1;
    [arrayM addObject:^{
        printf("%d\n",a);
    }];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSMutableArray *arrayM = [NSMutableArray array];
        addBlockToArray(arrayM);
        void (^block)() = [arrayM objectAtIndex:0];
        block();
        
        NSLog(@"%@",block);
        
    }
    return 0;
}

QQ20151111-2@2x

验证__NSGlobalBlock__

我们往可变数组中添加一个__NSGlobalBlock__。

不管是在MRC还是ARC环境下,__NSGlobalBlock_都是存储在程序的数据区,所以程序在MRC或ARC环境下都能正常执行。

#import <Foundation/Foundation.h>

void addBlockToArray(NSMutableArray *arrayM) {

    [arrayM addObject:^{
        printf("global\n");
    }];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSMutableArray *arrayM = [NSMutableArray array];
        addBlockToArray(arrayM);
        void (^block)() = [arrayM objectAtIndex:0];
        block();
        
        NSLog(@"%@",block);
        
    }
    return 0;
}

QQ20151111-4@2x

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注