Block基础和retain cycle(循环引用)

Block基础和retain cycle(循环引用)

blcok简介

Block 是c语言的扩展,并不是什么高新技术是从c扩展而来的,和swift语言的闭包需要注意的是由于 Objective-C在iOS中不支持GC机制。错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash.Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。

blcok基本语法
  • 1.如何定义block变量

         第一个block是一个int类型的返回值,并且有两个参数
         第二个block是没有返回值,没有参数的block
        int (^sumBlock)(init,int);
        void (^myBlock)()
    
  • 2.如何使用block来封装代码

           最基本的用法
          int (^sumBlock)(int,int) = ^(int a,int b){
                 return a- b;  
          };
          
           宏定义一个block
          typedef int (^MyBlock)(int, int); 
           利用宏定义来定义变量
          MyBlock sumBlock;
           定义一个block变量来实现两个参数相加
          sumBlock = ^(int a, int b) {
                 return a + b;
           };
           定义一个block变量来实现两个参数相减
          MyBlock minusBlock = ^(int a, int b) {
                 return a - b;
           };
           定义一个block变量来实现两个参数相乘
          MyBlock multiplyBlock = ^(int a, int b) {
                 return a * b;
           }; 
    
  • 3如何调用block

          NSLog(@"%d - %d - %d", multiplyBlock(2, 4),  sumBlock(10 , 9), minusBlock(10, 8));   
          这个依次输出是 8,19,2             
    
  • 4.block可以访问外部变量

          int   a  =  10;
          给局部变量加上__block之后就可以改变b局部变量的值,将取变量此刻运行时的值
          __block int b = 2;
          
          //定义一个block
          void (^block)(); 
          
          block = ^{
                默认情况下,block内部不能修改外面的局部变量
                a  =  20;
                给局部变量加上__block关键字,这个局部变量就可以在block内部修改
                b  =  25; 
          };
          
          block();
          
          NSlog("%d,%d",a,b);
    
block基本理解
  • 1.Block执行的代码其实在编译的时候就已经准备好了,就等着我们调用
  • 2.一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上

blcok在内存中的分析

block内存中的三个位置 NSGlobalBlock,NSStackBlock, NSMallocBlock

  • NSGlobalBlock : 和函数类似,位于text代码段

  • NSStackBlock : 栈内存,函数返回后Block将无效

  • NSMallocBlock : 堆内存

      宏定义一个block
      typedef long (^BlockSum)(int, int);
      BlockSum block1 = ^ long(int a,int b){
             return  a + b ;
      };
    
      //<__NSGlobalBlock__: 0x100001060>
      
      NSLog(@"%@",block1);
    
      int base = 100;
      BlockSum block2 = ^ long (int a,int b){
             return base + a + b;
      };
    
      //arc和非arc所在的内存位置不同
      //mrc
      // <__NSStackBlock__: 0x7fff5fbff7e8>/
      //arc
      //<__NSMallocBlock__: 0x10010deb0>
      
      NSLog(@"%@",block2);
    
      BlockSum block3 = [block2 copy];
      
      //<__NSMallocBlock__: 0x10010deb0>
      NSLog(@"%@",block3);
    

上述中为什么block1在NSGlobalBlock中,block2在NSStackBlock(mrc),NSMallocBlock(arc)中
因为block用到了外部的变量base,需要建立局部变量的快照,所以在(定义,不是运行)局部变量被拷贝到栈上(mrc),堆(arc)

ObjectC int base = 2;
base + = 2;
BlockSum sum = ^ long (int a,int b){
return base + a + b;
}
base ++ ;
NSLog("%ld",sum(1,2));
``
分析上述代码,因为有局部变量拷贝到栈里或者堆里,所以不会用运行时的变量base而是拷贝base所以
输出的结果为 7,不是8

Block的copy,retain,release操作

  • 对block retain操作并不会改变引用计数器,retainCount ,始终为1
  • NSGlobalBlock:retain、copy、release操作都无效;
  • Block_copy与copy等效,Block_release与release等效
  • NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的 错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
  • NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain
  • 尽量不要对Block使用retain操作

Block不同类型的变量

  • static 和基本数据类型

           static int base = 100;
           int base = 100;
           BlockSum sum = ^ long (int a,int b){
                  return a + b + base;
            };
           base = 0;
    
           NSLog(@"%ld\n",sum(1,2));
    

上述的类型如果是static的时候外部可以改变base变量,因为一直是一个内存地址,并没有建立局部变量的快照,不是在定义时copy的常量
如果是基本类型的话会建立一个拷贝,不是同一个地址所以值不会改变
所以static输出的是 3 ,基本数据类型是 103

  • static变量 如果block中也有变量的时候

           static int  base = 10;
           BlockSum sum = ^long (int a,int b){
                   base ++;
                   return base + a + b;
           }  
           base = 0;
           NSLog("%d\n,%ld\n,%d\n",base,sum(1,2),base);  
    

这段代码输出的结果为,0,4,1,这段代码说明block内部对外部static修饰的变量可以在内部进行修改,如果不加static或者block的会报错

  • Block变量,被__block修饰的变量,称作block变量, 基本类型的Block变量等效于全局变量、或静态变量

  • Block被另一个Block使用时,另一个Block被copy到堆上时,被使用的Block也会被copy。但作为参数的Block是不会发生copy的

  • arc的block所有的都在堆上边

  • mrc的看下边的实例

        int main(){
            int base = 10;
    
            BlockSum block1 = ^ long(int a,int b){
            return base + a + b;
        };
        //<__NSStackBlock__: 0x7fff5fbff7f8>
            NSLog(@"%@",block1);
            bar(block1);
    
            return 0;
    

}

      void bar(BlockSum block2){
      //  <__NSStackBlock__: 0x7fff5fbff7f8>
          NSLog(@"%@",block2);

      void (^block3) (BlockSum) = ^(BlockSum sum){
          NSLog(@"%@",sum);
          NSLog(@"%@",block2);
      };
      //   <__NSStackBlock__: 0x7fff5fbff7f8>
      //   <__NSStackBlock__: 0x7fff5fbff7f8>

          block3(block2);

          block3 = [block3 copy];
     //   <__NSStackBlock__: 0x7fff5fbff7f8>
     //   <__NSMallocBlock__: 0x100206780>
          block3(block2);

}

  • ObjC对象,不同于基本类型,Block会引起对象的引用计数变化

        @interface MyClass : NSObject {
             NSObject* _instanceObj;
          }
        @end
    
        @implementation MyClass
    
        NSObject* __globalObj = nil;
    
        - (id) init {
        if (self = [super init]) {
             _instanceObj = [[NSObject alloc] init];
        }
             return self;
        }
    
        - (void) test {
             static NSObject* __staticObj = nil;
             __globalObj = [[NSObject alloc] init];
             __staticObj = [[NSObject alloc] init];
    
             NSObject* localObj = [[NSObject alloc] init];
             __block NSObject* blockObj = [[NSObject alloc] init];
    
        typedef void (^MyBlock)(void) ;
            MyBlock aBlock = ^{
             NSLog(@"%@", __globalObj);
             NSLog(@"%@", __staticObj);
             NSLog(@"%@", _instanceObj);
             NSLog(@"%@", localObj);
             NSLog(@"%@", blockObj);
          };
         aBlock = [[aBlock copy] autorelease];
         aBlock();
    
            NSLog(@"%d", [__globalObj retainCount]);
            NSLog(@"%d", [__staticObj retainCount]);
            NSLog(@"%d", [_instanceObj retainCount]);
            NSLog(@"%d", [localObj retainCount]);
            NSLog(@"%d", [blockObj retainCount]);
          }
        @end
    
         int main(int argc, char *argv[]) {
           @autoreleasepool {
            MyClass* obj = [[[MyClass alloc] init]            autorelease];
            [obj test];
            return 0;
          }
         }
    

执行结果为1 1 1 2 1。

__globalObj和__staticObj在内存中的位置是确定的,所以Block copy时不会retain对象。

_instanceObj在Block copy时也没有直接retain _instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。

localObj在Block copy时,系统自动retain对象,增加其引用计数。

blockObj在Block copy时也不会retain。

  • 非ObjC对象,如GCD队列dispatch_queue_t。Block copy时并不会自动增加他的引用计数,这点要非常小心。

  • Block中使用的ObjC对象的行为

       @property (nonatomic, copy) void(^myBlock)(void);
    
        MyClass* obj = [[[MyClass alloc] init] autorelease];
           self.myBlock = ^ {
           [obj doSomething];
        };  
    

对象obj在Block被copy到堆上的时候自动retain了一次。因为Block不知道obj什么时候被释放,为了不在Block使用obj前被释放,Block retain了obj一次,在Block被释放的时候,obj被release一次。

retain cycle(循环引用的问题)

retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如:

      ASIHTTPRequest *request = [ASIHTTPRequest  requestWithURL:url];
      [request setCompletionBlock:^{
         NSString* string = [request responseString];
        }];  

在上边这个实例中request和Block循环引用,所以我们只需要打断其中的循环即可,
解决这个问题的办法是使用弱引用打断retain cycle:

       __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
          [request setCompletionBlock:^{
          NSString* string = [request responseString];
        }];

request被持有者释放后。request 的retainCount变成0,request被dealloc,request释放持有的Block,导致Block的retainCount变成0,也被销毁。这样这两个对象内存都被回收

与上面情况类似的是

             //self和block循环引用解决办法同上
                
          self.myBlock = ^{
             [self doSomething];
           }
           
           @property (nonatomic, retain) NSString* someVar;

           self.myBlock = ^ {
             NSLog(@"%@", _someVer);
           };
           
           NSString* str = _someVer;
           self.myBlock = ^ {
              NSLog(@"%@", str);
           };
           上述的循环引用是对象的属性的话,retain会reatin对象,所以产生self和block的循环引用
  • retain cycle不只发生在两个对象之间,也可能发生在多个对象之间,这样问题更复杂,更难发现

            ClassA* objA = [[[ClassA alloc] init] autorelease];
               objA.myBlock = ^{
               [self doSomething];
            };
               self.objA = objA;
    

解决办法同样是用__block打破循环引用

            ClassA* objA = [[[ClassA alloc] init] autorelease];

              MyClass* weakSelf = self;
              objA.myBlock = ^{
              [weakSelf doSomething];
            };
             self.objA = objA;                 

对上边的进行分析 self(retain 1) ----> objA(retain 1) ---->Block (retain 1)---->self 循环引用

  • 注意:MRC中__block是不会引起retain;但在ARC中__block则会引起retain。ARC中应该使用__weak或__unsafe_unretained弱引用。__weak只能在iOS5以后使用。

block对象被提前释放

看下面例子,有这种情况,如果不只是request持有了Block,另一个对象也持有了Block(下边的等号是一条虚线一条实线,block指向request 的是虚线)
--->request =======>Block<---- ObjA

这时request已被完全释放,但Block仍被objA持有,没有释放,如果这时触发了Block,在Block中将访问已经销毁的request,这将导致程序crash。为了避免这种情况,开发者必须要注意对象和Block的生命周期。

另一个常见错误使用是,开发者担心retain cycle错误的使用__block。比如

          __block kkProducView* weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
               weakSelf.xx = xx;
             });

将Block作为参数传给dispatch_async时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为dispatch_async并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后再release self。但这里使用__block,使dispatch_async没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash。

               // MyClass.m
           - (void) test {
                  __block MyClass* weakSelf = self;
                  double delayInSeconds = 10.0;
                  dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
                  dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
                  NSLog(@"%@", weakSelf);
              });

             // other.m
                 MyClass* obj = [[[MyClass alloc] init] autorelease];
                 [obj test];

这里用dispatch_after模拟了一个异步任务,10秒后执行Block。但执行Block的时候MyClass* obj已经被释放了,导致crash。解决办法是不要使用__block。

  大部分都是借鉴转载自:tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/     

你可能感兴趣的:(Block基础和retain cycle(循环引用))