iOS之block

来源:互联网 发布:免费精准客源软件 编辑:程序博客网 时间:2024/05/09 03:53

Block 是iOS在4.0之后新增的程序语法,严格来说block的概念并不算是基础程序设计的范围,对初学者来说也不是很容易了解,但是在iOS SDK 4.0之后,block几乎出现在所有新版的API之中,换句话说,如果不了解block这个概念就无法使用SDK 4.0版本以后的新功能。

Block定义:

我们使用「^」运算符来表示一个block变量,而且在block的定义最后面要加上「;」来表示一个完整的语句,下面是一个block的例子:

    int multiplier = 7 ;
    int (^myBlock)( int ) = ^( int num)
    {
        return num * multiplier;
    };
在很多情况下,我们并不需要将block声明为变量,反之我们可以直接在需要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中qsort_b函数,这是一个类似传统的qsort_t函数,但是直接使用block做为它的参数:

    char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
    qsort_b (myCharacters, 3 ,
             sizeof ( char *),
             ^( const void *l, const void *r)//block部分
                {
                    char *left = *( char **)l;
                    char *right = *( char **)r;
                    return strncmp (left, right, 1 );
                }                            //end
   );

一般来说,在block内只能读取在同一个作用域的变量而且没有办法修改在block外定义的任何变量,此时若我们想要这些变量能够在block中被修改,就必须在前面加上__block的修饰符,以上面第一个例子中的 multiplier 来说,这个变量在 block 中是只读的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我们在 block 中修改 multiplier ,在编辑时就会产生错误,因此若想要在 block 中修改 multiplier ,就必须在 multiplier 前面加上 __block 的修饰词,请参考下面的例子:

   __block int multiplier = 7 ;
   int (^myBlock)( int ) = ^( int num)
                           {
                               if (num > 5 )
                               {
                                     multiplier = 7 ;
                               }
                               else
                               {
                                    multiplier = 10 ;
                              }
                              return num * multiplier;
                          };

Block的内存管理

block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。比如下面的例子。 我在view did load中创建了一个block:

  1. - (void)viewDidLoad
  2. {
  3. [superviewDidLoad];
  4.  
  5. int number = 1;
  6. _block = ^(){
  7.  
  8. NSLog(@number %d, number);
  9. };
  10. }
并且在一个按钮的事件中调用了这个block:

  1. - (IBAction)testDidClick:(id)sender {
  2. _block();
  3. }
此时我按了按钮之后就会导致程序崩溃,解决这个问题的方法就是在创建完block的时候需要调用copy的方法。copy会把block从栈上移动到堆上,那么就可以在其他地方使用这个block了~ 修改代码如下:

  1. _block = ^(){
  2. NSLog(@number %d, number);
  3. };
  4.  
  5. _block = [_blockcopy];
同理,特别需要注意的地方就是在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy。不过代码看上去相对奇怪一些:

  1. [array addObject:[[^{
  2. NSLog(@hello!);
  3. } copy] autorelease]];

循环引用


对于非ARC下, 为了防止循环引用, 我们使用__block来修饰在Block中使用的对象:
对于ARC下, 为了防止循环引用, 我们使用__weak来修饰在Block中使用的对象。原理就是:ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该变量的引用计数+1。
这一点其实是在第一点的一个小的衍生。当在block内部使用成员变量的时候,比如

  1. @interface ViewController : UIViewController
  2. {
  3. NSString *_string;
  4. }
  5. @end

在block创建中:

  1. _block = ^(){
  2. NSLog(@string %@, _string);
  3. };
这里的_string相当于是self->_string;那么block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等self的dealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。
修改方案是新建一个__block scope的局部变量,并把self赋值给它,而在block内部则使用这个局部变量来进行取值。因为__block标记的变量是不会被自动retain的。

__block ViewController *controller = self;

_block = ^(){

NSLog(@string %@, controller->_string);

};


0 0
原创粉丝点击