iOSios专题iOS&Xcode奇技淫巧

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

2015-05-16  本文已影响9628人  董军1990

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

blcok简介

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

blcok基本语法
block基本理解

blcok在内存中的分析

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

上述中为什么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不同类型的变量

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

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

}

      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);

}

执行结果为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。

对象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的循环引用

解决办法同样是用__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 循环引用

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/     
上一篇下一篇

猜你喜欢

热点阅读