Objective-C:Block

1、Block基础

      可以将Object-c的代码块理解为C++语言的函数指针,通过代码块就能够像对待对象一样,指定要在方法和函数中传递的任意代码部分。

1.1 声明代码块

      代码块的声明与函数指针的声明类似,都定义了参数和返回值;不同的是函数指针使用"*",而代码块使用"^"。在声明代码块后,需要给其赋值将要执行内容,给赋值的代码块可以省略返回值类型,因为编译器可以从存储代码块的变量确定返回值类型;但是必须再次在括号中提供代码块的参数说明。

 1 void (^myBlock)(NSString *x);   //声明代码块变量
 2 myBlock = ^(NSString *x)      //给代码块变量赋值
 3 {
 4     NSLog(@”%@”, x);
 5 };
 6 myBlock(“hello world”);   //运行代码块,类似调用函数
 7 void (^myBlock)(NSString *x) = ^(NSString *x)
 8 {
 9     NSLog(@”%@”, x);
10 }

      在一个表达式内可以同时进行代码块变量的声明和初始化。这点也和使用普通的变量一样。你可以先声明变量,之后再初始化,也可以一次完成。

1.2 使用代码块

      使用代码块涉及三个方面:调用代码块、代码块作为函数参数、代码块作为方法参数。

       1) 调用代码块

      执行代码块与执行函数指针一样,都是在调用时传递相应参数,从而即可执行代码块体或函数体。

 1 int main(int argc, const char* argv[])
 2 {
 3      void (^myBlock)(NSString *x);   //声明代码块变量
 4      myBlock = ^(NSString *x)      //给代码块变量赋值
 5     {
 6          NSLog(@”%@”, x);
 7     };
 8     myBlock(“hello world”);   //运行代码块,类似调用函数
 9     return 0;
10 }

       2) 作为函数参数

     代码块在函数参数列表中的声明,与正常情况下的声明一样,无任何差别,如下所示:

1 void  fBlock( int  (^theBlock)(NSString *value) )
2 {
3      int result = theBlock(@”hello”);
4 }

 

     3) 作为方法参数

    将代码块作为参数传入到对象或类方法(不是函数的参数)时,语法稍有不同。如下所示。

1 -(void) method: (int (^)(NSString *)) block
2 {
3    int result = block(@”hello”);
4 } 

注意:

      我们是在代码块定义之后才传入了代码块参数的名称(保存代码块的变量在方法体内使用的名称),因此,通常在代码块定义时需要提供代码块变量名的位置却仅传入了^符号。

1.3 代码块作用域

代码块内程序既可以访问代码块内的局部变量,也可以访问代码块外的变量,但作用域不一样。

      1) 本地变量

     本地变量就是与代码块在同一范围内声明的变量,即本地变量是在代码块外定义的变量。

 1 int main(int argc, const char * argv[]) 
 2 {
 3     int a =1,b = 2;
 4     int (^mult)(void);  //声明代码块指针
 5     
 6     a =10;b = 20;
 7     mult=^{              //给代码块指针赋值
 8         return a*b;
 9     };
10     
11     a = 20;
12     b = 50;
13     
14     NSLog(@"%d",mult()); //调用代码块指针
15   
16     return 0;
17 }
18 2016-04-25 20:00:05.663 propertyProject[890:61223200

       因为本地变量是本地的,代码块会在定义时(赋值时)捕获创建点时的状态,即在创建代码块时会复制保存外部变量的状态(变量值),所以输出的是200,不是2,也不是1000.

      2) 全局变量

     代码块外部的全局变量(静态变量)是全局的,所以其可以在任何时候被修改,即在创建代码块是不复制全局变量并且全局变量可以代码块进行修改,从而反应到原来全局的变量中。

 1 int main(int argc, const char * argv[]) 
 2 {
 3     static int a =1,b = 2;
 4     int (^mult)(void);  //声明代码块指针
 5     
 6     a =10;b = 20;
 7     mult=^{              //给代码块指针赋值
 8         return a*b;
 9     };
10     
11     a = 20;
12     b = 50;
13     
14     NSLog(@"%d",mult()); //调用代码块指针
15   
16     return 0;
17 }
18 2016-04-25 20:00:05.663 propertyProject[890:612231000

        3) __block变量

      在创建代码块时会复制本地变量的值,从而将本地变量作为代码块中的局部变量(常量),如果想要修改它们的值,可以将本地变量声明为全局变量(static),或者是添加语言指令__block声明可读写的。

 1 int main(int argc, const char * argv[]) {
 2     __block int a =1;     //或为声明为:static int a =1
 3     int (^mult)(void);    //声明代码块指针
 4     mult=^{                 //给代码块指针赋值
 5         a = 22;
 6         return a;
 7     };
 8     mult();                //调用代码块指针
 9     NSLog(@"%d",a);
10     
11     return 0;
12 }
13 2016-04-25 20:42:01.208 propertyProject[1034:7774022

       4) 参数变量

      代码块的参数变量与函数的形式参数一样,都只是临时变量,对参数变量修改后,不能反应到原来的变量中。

       5) 局部变量

      局部变量是指在代码块中的定义的变量,与函数中定义的局部变量类似。

2、内存区域

      对于C++对象的存储区域有全局、栈、堆区域,由于Objective-C的block也是一种对象类型,所以其存储区域也有全局、栈和堆区域。

2.1栈区域

      类似C/C++语言,在局部区域定义的block也是存储在栈区域,也就是说,block只在其定义的范围有效。如下面这段代码就有危险:

 1 void (^block)();
 2 if()
 3 {
 4     block = ^{
 5         NSLog(@"Block A");
 6     };
 7 }
 8 else
 9 {
10     block = ^{
11         NSLog(@"Block B");
12     };
13 }
14 block();

      定义在if及else语句中的两个块都分配在栈内存中。编译器会给每个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能把分配给块的内存覆盖掉。于是,这两个块只能保证在对应的if或else语句范围内有效。这样写出来的代码可以编译,但是运行起来时而正确,时而错误。若编译器未覆盖待执行的block,则程序正常执行,若覆盖,则程序崩溃。

2.2 堆区域

      为解决上述例子中的问题,可将栈中的block拷贝(copy)到堆中,因为堆中的block在任意其它范围也有效。而且一旦复制到堆上,block就成了带引用计数的对象,所以后续的复制操作都不会真的执行复制,之时递增block对象的引用计数。如果不再使用这个block,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数时,则需要自己来调用release方法。当引用计数降为0后,"分配在堆上的block"会像其它对象一样,被系统回收;而"分配在栈上的block"则无须明确释放,因为栈内存本来就会自动回收。

      如下的代码就安全了,但如果是手动管理引用计数,还需进行释放:

 1     void (^block)();
 2     if(/* DISABLES CODE */ (1))
 3     {
 4         block = [^{
 5             NSLog(@"Block A");
 6         } copy];
 7     }
 8     else
 9     {
10         block = [^{
11             NSLog(@"Block B");
12         } copy];
13     }
14     block();

2.3 全局区域

      全局block不会捕捉任何状态(比如外围的变量等),运行时也无须有状态来参与,block所使用的整个内存区域在编译期已经完全确定了,因此,全局block可以声明在全局内存里,而不需要在每次用到的时候在栈中创建。另外,全局block的拷贝操作是个空操作,因为全局block决不可能为系统所回收。

如下例子,这种block就相当是一个单利:

1 void (^block)() =^{
2     NSLog(@"hello world");
3 };
4 
5 int main(int argc, const char * argv[]) {
6     @autoreleasepool { 
7         block();
8     }
9 }

      由于运行该block所需的全部信息都能在编译期确定,所以可把它做成全局block。这完全是种优化技术:若把如此简单的block当成复杂的block来处理,那就会在复制及丢弃该block时执行一些无谓的操作。

3、使用问题

3.1 内存泄露体现

      block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是:

1 self.someBlock = ^(Type var){
2     [self dosomething];
3     self.otherVar = XXX;
4     或者_otherVar = ...
5 };

 

如下所示:

 1 @interface myClass : NSObject<NSCopying>
 2 @property(nullable) NSString* name;
 3 @property void (^block)(void);
 4 -(void) myMethod;
 5 @end
 6 @implementation myClass
 7 -(void) myMethod
 8 {
 9     self.block = ^()
10     {
11         self.name = @"hello"//会被编译器捕捉到并及时提醒:Projects/propertyProject/myClass.m:28:9: Capturing 'self' strongly in this block is likely to lead to a retain cycle
12     };
13 }
14 @end

注意:

       在[3]文章中介绍:在block代码中没有显式地出现"self",也会出现循环引用。但经测试如果不使用self是不会出现循环引用提醒的,即在上述例子中,若使用"_name = @"hello""表达式,是不会有警告的。

3.2 解决办法

       1) ARC环境下

      ARC环境下可以通过使用__weak来声明一个中间变量,并将self赋值给这个中间变量,而在block中通过这个中间变量来访问self的成员属性,通过这种方式告诉block,不要在block内部对self进行强制strong引用,如下所示的例子编译器将不会出现提醒:

1 -(void) myMethod
2 {
3     __weak typeof(self) weakSelf=self;
4     self.block = ^()
5     {
6         weakSelf.name = @"hello";
7     };
8 }

 

      2) MRC环境下

     解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了!

3.3 委托delegate

      在委托问题上也容易出现循环引用问题,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!

4、参考文献

      [1] objective-C基础教程

      [2] Effective Objective-C 2.0

      [3] Block以及对应的使用方法

      [4] 谈Objective-C block的实现

      [5] iOS中block实现的探究

posted @ 2016-04-28 20:16  xiuneng  阅读(880)  评论(0编辑  收藏  举报