Objective-C内存管理详细解释 – Objective-C开发教程

上一章Objective-C开发教程请查看:Objective-C快速枚举用法?
这一章我们谈论OC开发中的一个重要概念:内存管理,实际上,学习每一种语言都有必要了解其内存管理模式,这有助于你写出更好的代码。
Objective-C内存管理中最基本的一个概念就是引用计数(Reference Count),了解这个基本就明白了OC的内存管理机制了(当然还需要了解一下堆栈),OC主要提供两种方式管理内存:手动引用计数(MRR)和自动引用计数(ARC),目前Xcode中默认使用ARC自动引用计数管理内存。
引用计数(Reference Count)在OC编程中,对象一般是储存在堆中的,对象在堆内存中有一个地址P(指针),这样的对象又称为实例对象,OC对每个实例对象有一个引用计数,所谓引用也就是指针,也就是对指针的计数。
我们使用一个对象,一般都是使用一个指针储存该对象的地址,如NSObject *ptr = P,这个ptr储存指针值,它是在栈上的。指针计数也就是说,类似于在每个对象上都有一个count属性,用来统计指向该对象的指针数(也就是有多少人指向我?)
在OC中,如果一个对象的引用计数retainCount=0,则该对象会被释放。

Objective-C内存管理详细解释 – Objective-C开发教程

文章图片
如上图,A指针首先指向对象O,因此该对象的引用计数count=1;接着另一个指针B也指向O,则count=2;然后A不再指向O了,count=1,;当B也不再指向O的时候,count=0,此时该对象就会被释放了。
在OC编程中,引用计数的基本变化如下:
  • 当使用alloc、new、copy或mutableCopy,新得到的对象,其引用计数初始化为1。
  • 使用对象的retain方法,对象引用计数加1;使用release方法,对象引用计数减1。
要注意实例对象引用计数的变化,有可能会造成内存泄漏。
既然了解了OC的引用计数,那就不得不说一下OC中的属性特性了。
属性特性目前OC中提供的主要属性特性有:weak、strong、assign、retain和copy。
理解这几个属性特性,主要从两方面考虑:是否开辟新的内存空间;是否对对象的引用计数有所增加。
  • 首先是copy,它是对原先对象A的一个拷贝得到B,对于A对象的引用计数是没有影响的,因为新copy的指针并不指向A,而是指向B,此时B的引用计数为1。
  • weak是对原先指针的一个简单拷贝,但是指向对象的引用计数不会增加,也就说,系统是否该对象,与weak指针无关。对象释放后,指针置为nil。不能修饰基本数据类型。
  • strong也是对原先指针的一个简单拷贝,指向对象的引用计数加1。
  • assign是对原先指针的拷贝,对象引用计数不会增加,可以修饰基本数据类型。但是对象释放后,指针不会置为nil,所以建议,除非是修饰基本数据类型,否则其它不使用assign。
  • retain对原先指针的拷贝,引用计数加1。
除了copy,其它四个关键字都是一一对应的,weak弱引用 => strong强引用,assign弱引用 => retain强引用。
ARC下修饰对象的默认关键字为strong,记得修饰基本数据类型使用assign就对了,然后就要关注引用计数的变化了。
另外在iOS开发中,有一个常见的问题:delegate为什么用weak?
例如controller中,该controller有一个tableview的强引用(tableview引用计数count=1),设置tableview的delegate为controller自身,那么tableview也有一个对controller的强引用(controller的引用计数count=1)。
释放controller引用计数为1,controller不能被释放,tableview也不能释放。
释放的原则是:最开始的类被释放后,会导致其成员有一些对象的引用计数为0,那么就可以逐步释放下去。假设这个叫做向前释放,那么如果后面有一个类使用强引用指向前面的类,那么会导致前面的类的retainCount > 0,而不能释放,造成内存泄漏。(后面的类可都看做前面的类的成员变量)
解决的办法就是,如果需要在后面的类指向前面的类,那么可以使用weak修饰该指针。也就说,在tableview中的delegate要使用weak修饰。
MRR手动内存管理最新Xcode默认是使用ARC内存管理的,如果需要使用MRR,可以在PROJECT -> Build Settings中找到Objective-C Automatic Reference Couting,将其设置为NO。
MRR下,使用alloc、new、copy、mutableCopy,初始对象的引用计数为1,使用retainCount方法可以获取对象的引用计数,使用retain可以增加引用计数,使用release减少引用计数。
下面是一个MRR的实例:
#import < Foundation/Foundation.h>@interface SampleClass:NSObject - (void)sampleMethod; @end@implementation SampleClass - (void)sampleMethod { NSLog(@"Hello, World! \n"); }- (void)dealloc{ NSLog(@"释放对象"); [super dealloc]; }@endint main() { // 初始引用计数为1 SampleClass *sampleClass = [[SampleClass alloc]init]; [sampleClass sampleMethod]; NSLog(@"初始分配后引用计数: %d", [sampleClass retainCount]); [sampleClass retain]; NSLog(@"retian后引用计数: %d", [sampleClass retainCount]); [sampleClass release]; NSLog(@"release后引用计数: %d", [sampleClass retainCount]); [sampleClass release]; NSLog(@"在此之前将调用SampleClass dealloc"); // 将指针设置为nil,防止野指针 sampleClass = nil; return 0; }

ARC内存管理ARC内存管理是默认的方式,若无特殊情况,强烈建议使用ARC内存管理方式。
ARC内存管理是基于OC的NSObject对象提供的方法以及运行时,OC自动管理对象的引用计数的变化,而且,在ARC模式下,不能显式调用retain、release和dealloc等。
若要尽快释放对象,可将指针设置为nil,这还要看是否还有其它强引用指向该对象,如果没有则释放,否则需要等其它强引用释放才会释放该对象。
【Objective-C内存管理详细解释 – Objective-C开发教程】但是可以重写对象的dealloc方法,不过仍然不需要显式调用[super dealloc],重写该方法可以做一些对象成员的释放工作,例如释放C语言中的结构体。
另外@autoreleasepool,最新OC编程是不需要使用的。
下面是ARC的一个简单例子:
#import < Foundation/Foundation.h>@interface SampleClass:NSObject - (void)sampleMethod; @end@implementation SampleClass - (void)sampleMethod { NSLog(@"Hello, World! \n"); }- (void)dealloc{ NSLog(@"释放对象"); }@endint main() { SampleClass *sampleClass = [[SampleClass alloc]init]; [sampleClass sampleMethod]; sampleClass = nil; // 对象指针设置为nil,该对象的retainCount=0,即被自动释放 return 0; }

    推荐阅读