iOS开发中ARC的那点事

本文探讨了iOS开发中的Automatic Reference Counting (ARC),强调了ARC是编译器特性而非垃圾回收。内容涵盖ARC的本质、开启与关闭、修饰符(如__strong, __weak, __autoreleasing)以及ARC在Block中的应用,旨在帮助开发者更好地理解和掌握ARC内存管理技术。" 102703290,9117987,Dubbo调用超时处理策略分析,"['Dubbo', 'Java', 'RPC']
摘要由CSDN通过智能技术生成

iOS开发中ARC的那点事

       现在开发过程中,大部分项目都在ARC环境下开发的,下面我们就一起来总结一下iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点。详细的ARC的信息请参照苹果官方文档与教程

       我们主要从以下几个方面分析一下ARC:

      1.  ARC的本质

      2.  ARC的开启与关闭 

      3.  ARC的修饰符

      4.  ARC与Block 

     ARC的本质

ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。 

     Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.

ARC只是相对于MRC(Manual Reference Counting或称为非ARC,下文中我们会一直使用MRC来指代非ARC的管理方式)的一次改进,但它和之前的技术本质上没有区别。   

    ARC的开启与关闭 

     XCode4可以在创建工程时选择关闭ARC,XCode5在创建的工程是默认开启ARC,没有可以关闭ARC的选项。如果需要对特定文件开启或关闭ARC,可以在工程选项中选择Targets -> Compile Phases -> Compile Sources,在里面找到对应文件,添加flag:

    

  • 打开ARC:-fobjc-arc
  • 关闭ARC:-fno-objc-arc

如下图:


     ARC的修饰符

       ARC主要提供了4种修饰符,他们分别是:__strong,__weak,__autoreleasing,__unsafe_unretained。

      __strong 

      表示引用为强引用。对应在定义property时的"strong"。所有对象只有当没有任何一个强引用指向时,才会被释放。

      注意:如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要将强引用置nil。

     __weak

     表示引用为弱引用。对应在定义property时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。__weak一般用在delegate关系中防止循环引用或者用来修饰指向由Interface Builder编辑与生成的UI控件。

    __autoreleasing

    表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

     一个常见的误解是,在ARC中没有autorelease,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease“自动”的混淆。其实你只要看一下每个iOS App的main.m文件就能知道,autorelease不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。


以下两行代码的意义是相同的:

NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC

__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的情况下。

官方文档解释:__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.
比如常用的NSError的使用:
NSError *__autoreleasing error; 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) 
{ 
  NSLog(@"Error: %@", error); 
}

(在上面的writeToFile方法中error参数的类型为(NSError *__autoreleasing *))

注意,如果你的error定义为了strong型,那么,编译器会帮你隐式地做如下事情,保证最终传入函数的参数依然是个__autoreleasing类型的引用。

<span style="background-color: rgb(204, 204, 204);">NSError *error; 
NSError *__autoreleasing tempError = error; // 编译器添加 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) 
{ 
  error = tempError; // 编译器添加 
  NSLog(@"Error: %@", error); 
}</span>

所以为了提高效率,避免这种情况,我们一般在定义error的时候将其(老老实实地=。=)声明为__autoreleasing类型的:

NSError *__autoreleasing error;

在这里,加上__autoreleasing之后,相当于在MRC中对返回值error做了如下事情:

*error = [[[NSError alloc] init] autorelease];

*error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心*error指向对象的释放。

另外一点,在ARC中,所有这种指针的指针 (NSError **)的函数参数如果不加修饰符,编译器会默认将他们认定为__autoreleasing类型。

比如下面的两段代码是等同的:

- (NSString *)doSomething:(NSNumber **)value
{
        // do something  
}

- (NSString *)doSomething:(NSNumber * __autoreleasing *)value
{
        // do something  
}

除非你显式得给value声明了__strong,否则value默认就是__autoreleasing的。

最后一点,某些类的方法会隐式地使用自己的autorelease pool,在这种时候使用__autoreleasing类型要特别小心。

比如NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          // do stuff  
          if (there is some error && error != nil)
          {
                *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
          }

    }];
}

会隐式地创建一个autorelease pool,上面代码实际类似于:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          @autoreleasepool  // 被隐式创建
      {
              if (there is some error && error != nil)
              {
                    *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
              }
          }
    }];

    // *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(  
}    

为了能够正常的使用*error,我们需要一个strong型的临时引用,在dict的枚举Block中是用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式如下:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
  __block NSError* tempError; // 加__block保证可以在Block内被修改  
  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
  { 
    if (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil]; 
    }  

  }] 

  if (error != nil) 
  { 
    *error = tempError; 
  } 
} 

     ARC与Block

       

在MRC时代,Block会隐式地对进入其作用域内的对象(或者说被Block捕获的指针指向的对象)加retain,来确保Block使用到该对象时,能够正确的访问。

这件事情在下面代码展示的情况中要更加额外小心。

MyViewController *myController = [[MyViewController alloc] init…];

// 隐式地调用[myController retain];造成循环引用
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release]; // 注意,这里调用[myController release];是在MRC中的一个常规写法,并不能解决上面循环引用的问题
}];

在这段代码中,myController的completionHandler调用了myController的方法[dismissViewController...],这时completionHandler会对myController做retain操作。而我们知道,myController对completionHandler也至少有一个retain(一般准确讲是copy),这时就出现了在内存管理中最糟糕的情况:循环引用!简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用导致了myController和completionHandler最终都不能被释放。我们在delegate关系中,对delegate指针用weak就是为了避免这种问题。

不过好在,编译器会及时地给我们一个警告,提醒我们可能会发生这类型的问题:


对这种情况,我们一般用如下方法解决:给要进入Block的指针加一个__block修饰符。

这个__block在MRC时代有两个作用:

  • 说明变量可改
  • 说明指针指向的对象不做这个隐式的retain操作

一个变量如果不加__block,是不能在Block里面修改的,不过这里有一个例外:static的变量和全局变量不需要加__block就可以在Block中修改。

使用这种方法,我们对代码做出修改,解决了循环引用的问题:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};
//之后正常的release或者retain

在ARC引入后,没有了retain和release等操作,情况也发生了改变:在任何情况下,__block修饰符的作用只有上面的第一条:说明变量可改。即使加上了__block修饰符,一个被block捕获的强引用也依然是一个强引用。这样在ARC下,如果我们还按照MRC下的写法,completionHandler对myController有一个强引用,而myController对completionHandler有一个强引用,这依然是循环引用,没有解决问题:(

于是我们还需要对原代码做修改。简单的情况我们可以这样写:

__block MyViewController * myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;  // 注意这里,保证了block结束myController强引用的解除
};

在completionHandler之后将myController指针置nil,保证了completionHandler对myController强引用的解除,不过也同时解除了myController对myController对象的强引用。这种方法过于简单粗暴了,在大多数情况下,我们有更好的方法。

这个更好的方法就是使用weak。(或者为了考虑iOS4的兼容性用unsafe_unretained,具体用法和weak相同,考虑到现在iOS4设备可能已经绝迹了,这里就不讲这个方法了)(关于这个方法的本质我们后面会谈到)

为了保证completionHandler这个Block对myController没有强引用,我们可以定义一个临时的弱引用weakMyViewController来指向原myController的对象,并把这个弱引用传入到Block内,这样就保证了Block对myController持有的是一个弱引用,而不是一个强引用。如此,我们继续修改代码:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

这样循环引用的问题就解决了,但是却不幸地引入了一个新的问题:由于传入completionHandler的是一个弱引用,那么当myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运作了。在一般的单线程环境中,这种问题出现的可能性不大,但是到了多线程环境,就很不好说了,所以我们需要继续完善这个方法。

为了保证在Block内能够访问到正确的myController,我们在block内新定义一个强引用strongMyController来指向weakMyController指向的对象,这样多了一个强引用,就能保证这个myController对象不会在completionHandler被调用前释放掉了。于是,我们对代码再次做出修改:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;

  if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

到此,一个完善的解决方案就完成了,好了,就到这里吧,后序大家一起来研究学习。。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值