block的使用与内存管理

Posted 芒果味ly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了block的使用与内存管理相关的知识,希望对你有一定的参考价值。

前言

ios开发也有一阵子了,项目中使用了大量的 block —这个从 iOS 4开始支持的语法,偶是从 iOS 6 开始做开发滴,因此上来就开始使用了,这玩意就是别的语言的闭包!

对 block 的初步理解

我一直把 block 作为一个匿名函数来理解,很多地方都跟函数很相似,更确切的说他本质上几乎就是个函数指针,他的生命周期跟函数一样,正因此如此,所以把 block 描述为属性时,我们都会使用 copy,目的在于将它从栈上拷贝到堆上!这样就能保证回调的时候能找到他,而不是早已经被释放掉了;既然使用了 copy,那么你就有义务管理好这块内存滴啦,可以使用 Block_release(aBlock),不过对于描述为属性的 block ,一般都是 self.theBlock = nil; 就行了!

下面简单谈下 block 的捕获吧,所谓捕获就是在block 块内部使用到的变量会被捕捉到快内,可以直接使用,为了安全起见,block 会对捕获的对象 retain,也正因为这个原因,才使得有的时候我们不敢使用 block,因为这很容易引起循环引用问题!下面简单介绍下捕获:

  1. 捕获一个局部的基本数据类型时(int ,float …): 
    block 会把这个局部变量 copy 一份,这个 copy 的过程是值得赋值,并且是 const 的,也就是说是只读的!若想在 block 块内改变需要使用 __block 修饰,这会改变 copy 的过程,copy 的是这个变量的地址,相当于 c 语言的地址传递,因此可以做到在 block 快内改变外部变量的效果! 举例说明:

        __block int a = 100;
    
        void (^test)(void) = ^()
            a++;
            NSLog(@"--in block :a = %d",a);
        ;
    
        NSLog(@"--before inkove block :a = %d",a);
        //--before inkove block :a = 100
        test();
        //--in block :a = 101        
        NSLog(@"--after inkove block :a = %d",a);
        //--after inkove block :a = 101
  2. 捕获全局的基本数据类型时,则不需要使用 __block 修饰就可以修改,并且修改之后直接影响该全局变量;举例说明:

        int g;
        int main(int argc, const char * argv[]) 
            @autoreleasepool 
    
                __block int a = 100;
    
                void (^test)(void) = ^()
                    a++;
                    g++;
                    NSLog(@"--in block :a = %d",a);
                ;
    
                NSLog(@"--before inkove block :a = %d,g = %d",a,g);
                //--before inkove block :a = 100,g = 0
                test();
                //--in block :a = 101,g = 1
                NSLog(@"--after inkove block :a = %d,g = %d",a,g);
                //--after inkove block :a = 101,g = 1
               
            return 0;
        
  3. 捕获一个 oc 对象时(NSObject)要分情况:

    • 当 block 是局部的,那么: 
      对于一个 oc 对象(不管是全局的还是局部的)而言,block 都不会保留这个对象,使用完毕当然也不会释放;原因是:block 的内存分配在了栈上,使用完毕就收回内存空间了,而他要捕获的 oc 对象的生命周期根本不小于它,所以它对 oc 对象作 retain 操作也没有意义,只会带来更多内存管理的麻烦!举例说明: 

          @implementation Maker
      
          - (void)dealloc
          
                  NSLog(@"%@:%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
                  [super dealloc];
          
      
          @end
      
          //test maker
           Maker *obj = [[Maker alloc]init];
           obj.m = 123;
           obj.num = @(999);
          //        [obj release]; 做了个实验,即使这里释放掉,block 块内仍然可以正常执行!不知为何!!!
      
          void (^test)(void) = ^()
              obj.m = 100;
              obj.num = @(888);
              NSLog(@"--in block :num=%@",(unsigned long)[obj retainCount],[obj num]);
          ;
      
          NSLog(@"--before inkove block :obj = %lu,num=%@",(unsigned long)[obj retainCount],[obj num]);
      
          test();
          NSLog(@"--after inkove block :obj = %lu,num=%@",[obj retainCount],[obj num]);
          [obj release];
          //Maker:dealloc

      通过观察 retainCount,发现调用这个局部 block 前后 maker 的引用计数并没有改变!令我惊奇的是即使在 block 上面就 release maker ,并且 maker 的 dealloc 也调用了,block 里面照样能够打印出正确的结果来!这使我很郁闷!

    • 当 block 是全局的: 
      Block会对OC对象retain!并且这个操作是不负责的,因为它没有做出相应的release操作!这也是使用Block引起循环引用的原因!另外,self也要理解为局部的,如果使用了self,那么self的引用计数就会加1!需要明确一点,只是self的引用计数变了,而self的成员变量的引用计数并没有改变! 
      一般都会使用__Block 弱化这个指针来解决,ARC中使用__Week来解决!

把 block 作为参数

系统的其实跟自己写的一样,比如UIView的Block方式开启动画: 
[UIView animateWithDuration:3 animations:^ 
NSLog(@”动画开始:self’s retainCount is %d”,self.retainCount); 
// NSLog(@”arr’s retainCount is %d”,this.arr.retainCount); 
NSLog(@”tempArr’s retainCount is %d”,tempArr.retainCount);

completion:^(BOOL finished) 

];

这里的self并没有被retain; 
下面的代码就不同了: 
[UIView animateWithDuration:3 animations:^ 
NSLog(@”动画开始:self’s retainCount is %d”,self.retainCount); 
// NSLog(@”arr’s retainCount is %d”,this.arr.retainCount); 
NSLog(@”tempArr’s retainCount is %d”,tempArr.retainCount);

completion:^(BOOL finished) 
    NSLog(@"动画结束:self's retainCount is %d",<span style="color:#990000;">self</span>.retainCount);
];

区别是在completion方法里使用了self,它就被retain了! 
想了一下这里还是可以理解的,因为这个动画不知道被指定多长时间,所以系统就不得不把这个Block 拷贝到堆上,那么这样一来Block的生命周期就长了,为了回调时能够找到对象,所以就retain了,从而保证了在访问对象时,对象的安全存在性!很遗憾,系统又是不分责任的retain,所以又打乱了内存管理原则!你或许应该使用__Block去弱化这个指针! 
平时会遇到很多类似的Block,网络请求就是其中之一,他们的共性都是处理完成之后的事情的,其实并不神秘,只不过Block被拷贝到堆上了而已。。。

弱化指针我一般都是这么写的: 
__block typeof(self)this = self;

《Block拷贝也有深浅之说》 
如果Block已经在堆上了,那么再次对Block拷贝就是浅拷贝,并不会真的再次拷贝一份到堆上,比如: 
void (^block)(void) = ^ printf(“Block”);; 
void (^blockHeap1)(void) = [block copy];//blockHeap1在堆中,这是深拷贝  
void (^blockHeap2)(void) = [blockHeap1 copy];//blockHeap2也在堆中,而且是浅拷贝 …


以上是关于block的使用与内存管理的主要内容,如果未能解决你的问题,请参考以下文章

inline-block和float的共性和区别

inline-block和float的共性和区别

Block的内存管理,看这里就够了

Block的内存管理,看这里就够了

Block匿名函数(转载)

block没那么难:block和变量的内存管理