Objective-C内存管理

ARC工做原理

手动内存管理的机理你们应该已经很是清楚了,简单来讲,只要遵循如下三点就能够在手动内存管理中避免绝大部分的麻烦:objective-c

若是须要持有一个对象,那么对其发送retain 若是以后再也不使用该对象,那么须要对其发送release(或者autorealse) 每一次对retain,alloc或者new的调用,须要对应一次release或autorealse调用设计模式

初学者可能仅仅只是知道这些规则,可是在实际使用时不免犯错。可是当开发者常用手动引用计数 Manual Referecen Counting(MRC)的话,这些规则将逐渐变为本能。你会发现少一个release的代码怎么看怎么别扭,从而减小或者杜绝内存管理的错误。能够说MRC的规则很是简单,可是同时也很是容易出错。每每很小的错误就将引发crash或者OOM之类的严重问题。api

在MRC的年代里,为了不不当心忘写release,Xcode提供了一个很实用的小工具来帮助可能存在的代码问题(Xcode3里默认快捷键Shift+A?不记得了),能够指出潜在的内存泄露或者过多释放。而ARC在此基础上更进一步:ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所作的只不过是在代码编译时为你自动在合适的位置插入releaseautorelease,就如同以前MRC时你所作的那样。所以,至少在效率上ARC机制是不会比MRC弱的,而由于能够在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。app

ARC机制

学习ARC很简单,在MRC时代你须要本身retain一个想要保持的对象,而如今不须要了。如今惟一要作的是用一个指针指向这个对象,只要指针没有被置空,对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被release一次。这对实例变量,synthesize的变量或者局部变量都是适用的。好比函数

NSString *firstName = self.textField.text;

firstName如今指向NSString对象,这时这个对象(textField的内容字符串)将被hold住。好比用字符串@“OneV"做为例子(虽然实际上不该该用字符串举例子,由于字符串的retainCount规则其实和普通的对象不同,你们就把它看成一个普通的对象来看吧…),这个时候firstName持有了@"OneV"。工具

一个strong指针

固然,一个对象能够拥有不止一个的持有者(这个相似MRC中的retainCount>1的状况)。在这个例子中显然self.textField.text也是@“OneV",那么如今有两个指针指向对象@"OneV”(被持有两次,retainCount=2,其实对NSString对象说retainCount是有问题的,不过anyway~就这个意思而已.)。学习

两个strong指向同一个对象

过了一下子,也许用户在textField里输入了其余的东西,那么self.textField.text指针显然如今指向了别的字符串,好比@“onevcat",可是这时候原来的对象已然是存在的,由于还有一个指针firstName持有它。如今指针的指向关系是这样的:优化

其中一个strong指向了另外一个对象

只有当firstName也被设定了新的值,或者是超出了做用范围的空间(好比它是局部变量可是这个方法执行完了或者它是实例变量可是这个实例被销毁了),那么此时firstName也再也不持有@“OneV",此时再也不有指针指向@"OneV",在ARC下这种情况发生后对象@"OneV"即被销毁,内存释放。atom

没有strong指向@

相似于firstNameself.textField.text这样的指针使用关键字strong进行标志,它意味着只要该指针指向某个对象,那么这个对象就不会被销毁。反过来讲,ARC的一个基本规则便是,只要某个对象被任一strong指针指向,那么它将不会被销毁。若是对象没有被任何strong指针指向,那么就将被销毁。在默认状况下,全部的实例变量和局部变量都是strong类型的。能够说strong类型的指针在行为上和MRC时代retain的property是比较类似的。设计

既然有strong,那确定有weak咯~weak类型的指针也能够指向对象,可是并不会持有该对象。好比:

__weak NSString *weakName = self.textField.text

获得的指向关系是:

一个strong和一个weak指向同一个对象

这里声明了一个weak的指针weakName,它并不持有@“onevcat"。若是self.textField.text的内容发生改变的话,根据以前提到的"只要某个对象被任一strong指针指向,那么它将不会被销毁。若是对象没有被任何strong指针指向,那么就将被销毁”原则,此时指向@“onevcat"的指针中没有strong类型的指针,@"onevcat"将被销毁。同时,在ARC机制做用下,全部指向这个对象的weak指针将被置为nil。这个特性至关有用,相信无数的开发者都曾经被指针指向已释放对象所形成的EXCBADACCESS困扰过,使用ARC之后,不管是strong仍是weak类型的指针,都再也不会指向一个dealloced的对象,从根源上解决了意外释放致使的crash

strong指向另外对象,内存释放,weak自动置nil

不过在大部分状况下,weak类型的指针可能并不会很经常使用。比较常见的用法是在两个对象间存在包含关系时:对象1有一个strong指针指向对象2,并持有它,而对象2中只有一个weak指针指回对象1,从而避免了循环持有。一个常见的例子就是oc中常见的delegate设计模式,viewController中有一个strong指针指向它所负责管理的UITableView,而UITableView中的dataSourcedelegate指针都是指向viewController的weak指针。能够说,weak指针的行为和MRC时代的assign有一些类似点,可是考虑到weak指针更聪明些(会自动指向nil),所以仍是有所不一样的。细节的东西咱们稍后再说。

一个典型的delegate设计模式

注意相似下面的代码彷佛是没有什么意义的:

__weak NSString *str = [[NSString alloc] initWithFormat:…];  
NSLog(@"%@",str); //输出是"(null)"

因为strweak,它不会持有alloc出来的NSString对象,所以这个对象因为没有有效的strong指针指向,因此在生成的同时就被销毁了。若是咱们在Xcode中写了上面的代码,咱们应该会获得一个警告,由于不管什么时候这种状况彷佛都是不太可能出现的。你能够把weak换成strong来消除警告,或者直接前面什么都不写,由于ARC中默认的指针类型就是strong

property也能够用strongweak来标记,简单地把原来写retainassign的地方替换成strong或者weak就能够了。

@property (nonatomic, strong) NSString *firstName; 
@property (nonatomic, weak) id  delegate;

ARC能够为开发者节省不少代码,使用ARC之后不再须要关心何时retain,何时release,可是这并不意味你能够不思考内存管理,你可能须要常常性地问本身这个问题:谁持有这个对象?

好比下面的代码,假设array是一个NSMutableArray而且里面至少有一个对象:

id obj = [array objectAtIndex:0];  
[array removeObjectAtIndex:0]; 
NSLog(@"%@",obj);

在MRC时代这几行代码应该就挂掉了,由于array中0号对象被remove之后就被当即销毁了,所以obj指向了一个dealloced的对象,所以在NSLog的时候将出现EXCBADACCESS。而在ARC中因为obj是strong的,所以它持有了array中的首个对象,array再也不是该对象的惟一持有者。即便咱们从array中将obj移除了,它也依然被别的指针持有,所以不会被销毁。

一点提醒

ARC也有一些缺点,对于初学者来讲,可能仅只能将ARC用在objective-c对象上(也即继承自NSObject的对象),可是若是涉及到较为底层的东西,好比Core Foundation中的malloc()或者free()等,ARC就鞭长莫及了,这时候仍是须要本身手动进行内存管理。在以后咱们会看到一些这方面的例子。另外为了确保ARC能正确的工做,有些语法规则也会由于ARC而变得稍微严格一些。

ARC确实能够在适当的地方为代码添加retain或者release,可是这并不意味着你能够彻底忘记内存管理,由于你必须在合适的地方把strong指针手动设置到nil,不然app极可能会oom。简单说仍是那句话,你必须时刻清醒谁持有了哪些对象,而这些持有者在何时应该变为指向nil

ARC必然是Objective-C以及Apple开发的趋势,从此也会有愈来愈多的项目采用ARC(甚至不排除MRC在将来某个版本被弃用的可能),Apple也一直鼓励开发者开始使用ARC,由于它确实能够简化代码并加强其稳定性。能够这么说,使用ARC以后,因为内存问题形成的crash基本就是过去式了(OOM除外 :P)

咱们正处于由MRC向ARC转变的节点上,所以可能有时候咱们须要在ARC和MRC的代码间来回切换和适配。Apple也想到了这一点,所以为开发这提供了一些ARC和非ARC代码混编的机制,这些也将在以后的例子中列出。另外ARC甚至能够用在C++的代码中,而经过遵照一些代码规则,iOS 4里也可使用ARC(虽然我我的认为在如今iOS 6都呼之欲出的年代已经基本没有须要为iOS 4作适配的必要了)、

总之,聪明的开发者总会尝试尽量的自动化流程,已减轻本身的工做负担,而ARC偏偏就为咱们提供了这样的好处:自动帮咱们完成了不少之前须要手动完成的工做,所以对我来讲,转向ARC是一件不须要考虑的事情。



引用关键字

ARC中关于对象的引用参照,主要有下面几关键字。使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。

 

  • __strong

变量声明缺省都带有__strong关键字,若是变量什么关键字都不写,那么缺省就是强参照。

 

  • __weak

上面已经看到了,这是弱参照的关键字。该概念是新特性,从 iOS 5/ Mac OS X 10.7 开始导入。因为该类型不影响对象的生命周期,因此若是对象以前就没有持有者,那么会出现刚建立就被破弃的问题,好比下面的代码。

 

C代码   收藏代码
  1. NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];    
  2. NSLog(@"string: %@", string); //此时 string为空   

 

若是编译设定OS版本 Deployment Target 设定为这比这低的版本,那么编译时将报错(The current deployment target does not support automated __weak references),这个时候,咱们可使用下面的 __unsafe_unretained。

弱参照还有一个特征,即当参数对象失去全部者以后,变量会被自动付上nil (Zeroing)。

 

  • __unsafe_unretained

该关键字与__weak同样,也是弱参照,与__weak的区别只是是否执行nil赋值(Zeroing)。可是这样,须要注意变量所指的对象已经被破弃了,地址还还存在,但内存中对象已经没有了。若是仍是访问该对象,将引发「BAD_ACCESS」错误。

 

  • __autoreleasing

该关键字使对像延迟释放。好比你想传一个未初始化的对像引用到一个方法当中,在此方法中实例化此对像,那么这种状况可使用__autoreleasing。他被常常用于函数有值参数返回时的处理,好比下面的例子。

  
C代码   收藏代码
  1. - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError {    
  2.     ....    
  3.     *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];    
  4. }    
  5.    
  6. ....    
  7. {    
  8.     NSError *error = nil;    
  9.     [self generateErrorInVariable:&error];    
  10.     NSLog(@"Error = %@", error);    
  11. }    
 

又如函数的返回值是在函数中申请的,那么但愿释放是在调用端时,每每有下面的代码。

 

C代码   收藏代码
  1. -(NSString *)stringTest    
  2. {    
  3.     NSString *retStr = [NSString stringWithString:@"test"];    
  4.    
  5.     return [[retStr retain] autorelease];    
  6. }    
  7.    
  8. // 使用ARC    
  9.    
  10. -(NSString *)stringTest    
  11. {    
  12.     __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];    
  13.    
  14.     return retStr;    
  15. }    
 

 

即当方法的参数是id*,且但愿方法返回时对象被autoreleased,那么使用该关键字。

 

补充一个 循环应用问题

首先delegate要使用assign而不是retain,这个问题你们经过看iOS的api就能够了,最典型的是tabView里面的delegate和datasource都是用的assign。
 那为何要使用assign而不是retain呢?
 首先,考虑类的设计模式,类与类只见的大致关系有继承和聚合的关系,当咱们使用聚合的时候该对象就拥有聚合的对象,这时候咱们就须要retain使引用计数器+1来控制该对象的内存管理,因此个人感受retain和copy的一项能力就是拥有该对象的内存管理权。
下面就得说delegate了,一个对象不必管理本身delegate的生命周期,或者说不必拥有该对象,因此咱们只要知道它的指针就能够了,用指针找到对象去调用方法,也就是委托实现的感受。

或者咱们换个角度,从内存管理方面也能够解释这个问题。delegate的生命周期不须要让该对象去控制,若是该对象对其使用retain极可能致使delegate所指向的对象没法正确的释放。
@循环引用
全部的引用计数系统,都存在循环应用的问题。例以下面的引用关系:
对象a建立并引用到了对象b.
对象b建立并引用到了对象c.
对象c建立并引用到了对象b.
这时候b和c的引用计数分别是2和1。当a再也不使用b,调用release释放对b的全部权,由于c还引用了b,因此b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。今后,b和c永远留在内存中。

 这 种状况,必须打断循环引用,经过其余规则来维护引用关系。好比,咱们常见的delegate每每是assign方式的属性而不是retain方式 的属性,赋值不会增长引用计数,就是为了防止delegation两端产生没必要要的循环引用。若是一个UITableViewController 对象a经过retain获取了UITableView对象b的全部权,这个UITableView对象b的delegate又是a, 若是这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。本身在设计使用delegate模式时,也要注意这点。

由于循环引用而产生的内存泄露也是Instrument没法发现的,因此要特别当心。

相关文章
相关标签/搜索