iOS block之三种block

前言

先说明一下,因为ARC下系统会对block做一些拷贝和释放操作,对深入于理解block无益,所以本篇文章所提到的栗子编译环境均为MRC。

首先引用《Objective-C高级编程》Blocks章节中的第一句话:Blocks是对C语言的扩充功能。而且OC是建立在C语言基础上之上,添加了面向对象机制的一门编程语言。

所以不要再说block的实现原理是C++的函数指针了,正确答案是:block的实现原理是C语言的函数指针。
函数指针即函数在内存中的地址,通过这个地址可以达到调用函数的目的。

Block是NSObject的子类,拥有NSObject的所有属性,所以block对象也有自己的生命周期,生存期间也会被持有和释放。


block有三种:

NSGlobalBlock 静态区(全局区)block,这是一种特殊的bloclk,因为不引用外部变量而存在。另外,作为静态区的对象,它的释放是有操作系统控制的,这一点我们最后再聊。
NSStackBlock 栈区block,位于内存的栈区,一般作为函数的参数出现。
NSMallocBlock 堆区block,位于内存的堆区,一般作为对象的property出现。

如果一个blcok引用了外部变量是栈block,则其不引用外部变量就成为了静态blcok。
如果一个block引用了外部变量是堆block,则其不引用外部变量就成为了静态block。


NSStackBlock 栈区block

说到栈操作,大家都明白,出栈和入栈,函数只有入栈后才能执行,出栈后就释放了。
栈block一般在函数内部定义,并在函数内部调用;或者在函数外部定义,作为函数的一个参数在函数内部调用。函数出栈时和其他变量或参数一起释放。

栈区block形式有2,如下:

栈区block形式1

- (void)xxx
{
    __block int i = 0;
    void (^block1)() = ^{
        //此处若不引用外部变量i,则block1是静态block,若引用,则为栈block
        i++;
        NSLog(@"i = %d",i);
    };

    block1();
}

栈区block形式2:

- (void)saveFile:(NSDictionary *)dic complete:(void(^)(BOOL success))complete
{
    //dic写入本地近作demo使用,实际开发要判断路径是否存在,以及dic中是否存在null值等
    NSString    * path = [NSHomeDirectory() stringByAppendingFormat:@"/Caches/dicInfo"];
    BOOL suc = [dic writeToFile:path atomically:YES];

    if (complete) {
        //若complete中不引用外部变量suc,则complete是静态block,此处complete为栈block
        complete (suc);
    }
}

NSMallocBlock 堆区block

堆区是内存的常驻区域,也叫永久存储区,block一般在函数中定义,最多是个栈block,什么时候才能打怪升级成为堆block呢?
在MRC时代你需要使用Block_copy()方法,才可以将blcok复制到堆中。

然而复制到堆中有何用处呢?
首先,作为一个对象,把它复制到堆中,想要使用它肯定要有一个指针指向它,而指向它的指针是作为property或静态变量出现的(如果不被引用也就没有了常驻于堆区的意义),而实际开发中多使用property引用。

在MRC中,如果一个类的block属性是使用copy修饰的,则不需要手动调用Block_copy将其复制到堆中。如果是用strong修饰的,则必须使用Block_copy()将其复制到堆中,并在释放时调用Block_release()方法将其释放,否则会因野指针导致程序崩溃。

@interface TestV : UIView

@property (nonatomic, strong) void(^block1)();
@property (nonatomic, copy) void(^block2)();

@end
- (void)testFunc
{
    __block int i = 0;

    void (^block1)() = ^{
        //此处若不引用外部变量i,则block1是静态block,若引用,则为堆block
        i++;
        NSLog(@"block1 -- i = %d",i);
    };
    void (^block2)() = ^{
        i --;
        NSLog(@"block2 -- i = %d",i);
    };

    TestV * view = [[TestV alloc] initWithFrame:CGRectMake(100, 100, kScreenWidth - 200, 40)];
    [self.view addSubview:view];

    view.block1 = Block_copy(block1);
    view.block2 = block2;

    NSLog(@"block1 -- %@",block1);
    NSLog(@"block2 -- %@",block2);
}

因此在MRC中,基本上会用copy关键字修饰block,既优雅又省代码,还避免了因为未调用Block_release而造成内存泄露,这大概是很多人在ARC项目中依旧使用copy修饰block的历史原因。

其实在ARC下使用strong或copy修饰block没啥区别,亲测有效,可能会遇到一些开发人员,他们看到ARC工程中出现strong修饰的block就会提出质疑,那只能说明他们没有亲自写两行代码测试过是否真的有区别。


NSGlobalBlock 静态block

既然存在于静态区,则只有当进程被杀死,进程所占用的内存空间被释放后,静态区的对象才会被释放。静态block并不依附于某个对象而存在,也并不为某个类对象单独享有,而是所有的该类对象都共同拥有这个block,都有这个block的使用权,不管有多少个类对象,在程序运行期间这个block始终只有一份,直到进程被杀死,内存被释放。

以下代码片段将有助于你的理解:

//在一个成员方法中定义一个静态block,然后打印出self的地址和block的地址
- (void)blockAction
{
    void(^block1)() = ^{
        //未引用外部变量,则为静态block
        NSLog(@"block1");
    };

    NSLog(@" -- self : %p",self);
    NSLog(@" -- %@ --",block1);
}
//初始化了两个对象,同时调用各自的成员方法
    [_v1 blockAction];
    [_v2 blockAction];
//打印结果如下:两个对象地址不同,两个block地址相同
xxx BlockDemo[22106:1969807]  -- self : 0x7f81da513f20
xxx BlockDemo[22106:1969807]  -- <__NSGlobalBlock__: 0x1057ca1a0> --
xxx BlockDemo[22106:1969807]  -- self : 0x7f81da512770
xxx BlockDemo[22106:1969807]  -- <__NSGlobalBlock__: 0x1057ca1a0> --

下一篇博文《block之循环引用》,我们将会讲block与循环引用。

以上观点均为个人研究、理解,如有不妥之处,欢迎指正,谢谢!

你可能感兴趣的:(iOS)