iOS Blocks语法、变量及为什么选用copy修饰符

2018-05-31  本文已影响5人  明若晴空

Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数;

匿名函数就是没有名称的函数。

1、Block语法和Block变量

C语言中的变量(根据作用域不同区分的):

1、自动变量(局部变量);

2、函数的参数;

3、静态变量(静态局部变量);

4、静态全局变量;

5、全局变量;

其中,静态变量(静态局部变量)、静态全局变量、全局变量是可以在函数的多次调用之间传递的变量。

Block语法,形式如下:(^ 返回值类型 参数列表 表达式)

^(void)  (int event)

{

      Printf(“buttonId:%d event=%d\n”, i, event);

}

Block语法与一般C语言函数定义相比,有两点不同:

1、没有函数名;

2、带^;

^:插入记号,便于查找

Block语法可以省略返回值类型;也可以省略参数列表类型;

省略返回值类型和参数列表类型后的语法格式为:^{}

函数指针的声明示例:

int (*funcptr) (int) = &func;

Block类型变量的示例:

int (^blk) (int)

Block变量和一般的C语言变量完全相同,可作为以下用途使用:

1、自动变量;

2、函数参数;

3、静态(局部)变量;

4、静态全局变量;

5、全局变量;

2、截获自动变量值

demo如下:

int i = 10;

NSString *str = @"hello";

void (^blk) (void) = ^{

    NSLog(@"str = %@, i = %d", str, i);

};

str = @"I'm not ok";

i = 11;

blk();

NSLog(@"end");

最后打印的日志如下:

从上面的日志可以看出:Block语法使用的是之前定义时的自动变量的值,也就是截获自动变量的值,保存起来,在执行时使用。即使在block定义和执行期间修改过这些自动变量的值,也不会影响,它还是执行之前定义时所截获的自动变量值。

这就是自动变量值的截获。

3、__block说明符

int i = 10;

NSString *str = @"hello";

void (^blk) (void) = ^{

    i = 12;

    NSLog(@"str = %@, i = %d", str, i);

};

str = @"I'm not ok";

i = 11;

blk();

NSLog(@"end");

这个时候,i = 12会报错:

Variable is not assignable (missing __block type specifier)

如果将i声明为__block int i = 10;,就可以实现在block中对i进行赋值。

这种变量称为__block变量。

4、Block循环引用

避免循环引用的方法有三种:

1、使用__block

2、使用__weak

3、使用__unsafe_unretained

使用__block修饰符的优点:

1、可以动态的控制对象的持有周期;(在执行block时,可动态的决定是都将nil或者其他对象赋值给__block变量)

2、在ARC和MRC都可以使用;

3、可以修饰对象,也可以修饰基本数据类型;

使用__block修饰符的缺点:

1、为了避免循环引用,必须执行Block;如果只执行了Block语法,却不执行Block的使用时,无法避免循环使用。

__weak修饰的自动变量:不能在Block中被赋值;只能修饰对象,不能修饰基本数据类型。

unsafe_unretained是在iOS4以及OS X Snow Leopard的环境下,也就是不能使用weak的环境下使用

5、copy/release

ARC无效时,

Block从栈复制到堆,是通过copy实例方法实现;

手动释放Block,通过release实例方法来来释放;

在堆上,可通过retain实例方法持有;

而在栈上,使用retain方法持有是无效的;

因此推荐使用copy实例方法来持有Block,copy方法可实现将Block从栈复制到堆上。

默认情况下,Block创建时是在栈上的。

ARC无效时,__block可以被用来避免Block的循环使用。

因为,当Block从栈复制到堆上时,如果Block中使用的自动变量,有block说明符,那么就不会被retain。如果Block中使用的自动变量,没有block说明符,那么就会被retain。

注意:__block修饰符在ARC有效和无效时,区别很大,因此使用需注意。

6、为什么要选用copy来修饰Block?

首先,Block也是OC对象。

Block一共有三种类型:

1)、全局Block( _NSGlobalBlock):设置在程序的数据区(.data区);

2)、栈中的Block( _NSStackBlock):设置在栈上的;

3)、堆中的Block( _NSMallocBlock):设置在由malloc函数分配的内存块(堆)中。

1)、全局Block对象

有两种情况:

(1)在记述全局变量的地方存在Block语法:因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获,因此Block语法的内容也不会依赖于执行时的状态,就可以设置在与全局变量相同的数据区域,因此成为全局Block对象;(不截获)

(2)Block语法中的表达式,可能会截获自动变量的值,但是只要不使用截获的自动变量,这样的Block也是全局Block;(截获但不使用)

2)、栈中的Block

除了上面两种情况之外生成的Block都是_NSStackBlock对象,设置在栈上。

3)、堆中的Block

配置在全局变量上的Block,从变量的作用域外也可以通过指针安全的使用。

设置在栈上的Block,如果其所属的变量的作用域结束,这个Block也会被废弃。block变量也是配置在栈上的,如果其所属的变量的作用域结束,这个block变量也会被废弃。

Block提供了将Block和__block变量从栈上复制到堆上的方法来解决问题,这样“即使Block语法记述的变量作用域结束,堆上的Block也可以继续存在”。

在ARC有效时,编译器大多数情况下会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码。比如,将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码。

但是有些情况下,需要手动的使用copy方法生成代码,来将Block从栈上复制到堆上。

这些不能自动生成复制代码的情况有:

1、通过方法或函数的参数传递Block时,需要手动复制。

但是如果在方法或者函数中适当的复制了传递过来的参数,就不必在调用之前手动复制了。

比如在Cocoa框架的方法名中含有usingBlock的方法(如NSArray类的enumerateObjectsUsingBlock方法),再比如Grand Central Dispatch的API(如dispatch_async函数)。

如果不想判断这些情况,可以在所有情况下都进行copy,但是Block从栈上复制到堆上是非常消耗CPU的。

对于已经在堆上的Block或者全局Block调用copy方法,不会引起任何问题。

对于全局的Block进行copy时,什么也不做;

对于堆上的Block进行copy时,Block的引用计数加1.

上一篇 下一篇

猜你喜欢

热点阅读