最近老问这个,但是每个人的理解不一样,其实我也不知道应该咋说
*基本概念
引用计数(Reference Count)
*内存释放的原则
手动管理内存有时候并不容易,因为对象的引用有时候是错综复杂的,对象之间可能互相交叉引用,此时需要遵循一个法则:谁创建,谁释放。
*属性参数
*自动释放池
在ObjC中也有一种内存自动释放的机制叫做“自动引用计数”(或“自动释放池”),与C#、Java不同的是,这只是一种半自动的机制,有些操作还是需要我们手动设置的。自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象在初始化时调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)
在开发中,我们常常都会使用到局部变量,局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool跟局部变量类似,当执行代码超过autorelease pool块时,所有放在autorelease pool的对象都会自动调用release。它的工作原理如下:
创建一个NSAutoreleasePool对象
在autorelease pool块的对象调用autorelease方法
释放NSAutoreleasePool对象
对于自动内存释放简单总结一下:
autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中;
自动释放池实质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象(例如如果一个对象的引用计数器>1则此时就无法销毁);
由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池;
ObjC中类库中的静态方法一般都不需要手动释放,内部已经调用了autorelease方法
ARC管理方法
iOS/OS X内存管理方法有两种:手动引用计数(Manual Reference Counting)和自动引用计数(Automatic Reference Counting)。从OS X Lion和iOS 5开始,不再需要程序员手动调用retain和release方法来管理Objective-C对象的内存,而是引入一种新的内存管理机制Automatic Reference Counting(ARC),简单来说,它让编译器来代替程序员来自动加入retain和release方法来持有和放弃对象的所有权。
在ARC内存管理机制中,id和其他对象类型变量必须是以下四个ownership qualifiers其中一个来修饰:
__strong(默认,如果不指定其他,编译器就默认加入)
__weak
__unsafe_unretained
__autoreleasing
所以在管理Objective-C对象内存的时候,你必须选择其中一个,下面会用一些列子来逐个解释它们的含义以及如何选择它们。
__strong ownership qualifier
如果我想创建一个字符串,使用完之后将它释放调用
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"对象的RC=1
NSLog(@"%@", text);
[text release]; //@"Hello, world"对象的RC=0
}
而如果是使用ARC方式的话,就text对象无需调用release方法,而是当text变量超过作用域时,编译器来自动加入[text release]方法来释放内存
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"对象的RC=1
NSLog(@"%@", text);
}
/*
* 当text超过作用域时,@"Hello, world"对象会自动释放,RC=0
*/
而当你将text赋值给其他变量anotherText时,MRC需要retain一下来持有所有权,当text和anotherText使用完之后,各个调用release方法来释放。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"对象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text; //@"Hello, world"对象的RC=1
[anotherText retain]; //@"Hello, world"对象的RC=2
NSLog(@"%@", anotherText);
[text release]; //@"Hello, world"对象的RC=1
[anotherText release]; //@"Hello, world"对象的RC=0
}
而使用ARC的话,并不需要调用retain和release方法来持有跟释放对象。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"对象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text; //@"Hello, world"对象的RC=2
NSLog(@"%@", anotherText);
}
/*
* 当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法, @"Hello, world"对象的RC=0
*/
除了当__strong变量超过作用域时,编译器会自动加入release语句来释放内存,如果你将__strong变量重新赋给它其他值,那么编译器也会自动加入release语句来释放变量指向之前的对象。例如:
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"]; //@"Hello, world"对象的RC=1
NSString *anotherText = text; //@"Hello, world"对象的RC=2
NSString *anotherText = [[NSString alloc] initWithFormat:@"Sam Lau"]; // 由于anotherText对象引用另一个对象@"Sam Lau",那么就会自动调用[anotherText release]方法,使得@"Hello, world"对象的RC=1, @"Sam Lau"对象的RC=1
}
/*
* 当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法,
* @"Hello, world"对象的RC=0和@"Sam Lau"对象的RC=0
*/
如果变量var被__strong修饰,当变量var指向某个对象objc,那么变量var持有某个对象objc的所有权
前面已经提过内存管理的四条规则:
我们总结一下编译器是按以下方法来实现的:
对于规则1和规则2,是通过__strong变量来实现,
对于规则3来说,当变量超过它的作用域或被赋值或成员变量被丢弃时就能实现
对于规则4,当RC=0时,系统就会自动调用
__weak ownership qualifier
其实编译器根据__strong修饰符来管理对象内存。但是__strong并不能解决引用循环(Reference Cycle)问题:对象A持有对象B,反过来,对象B持有对象A;这样会导致不能释放内存造成内存泄露问题。
引用Pro Multithreading and Memory Management for iOS and OS X的图
举一个简单的例子,有一个类Test有个属性objc,有两个对象test1和test2的属性objc互相引用test1和test2:
@interface Test : NSObject
@property (strong, nonatomic) id objc;
@end
{
Test *test1 = [Test new]; /* 对象a */
/* test1有一个强引用到对象a */
Test *test2 = [Test new]; /* 对象b */
/* test2有一个强引用到对象b */
test1.objc = test2; /* 对象a的成员变量objc有一个强引用到对象b */
test2.objc = test1; /* 对象b的成员变量objc有一个强引用到对象a */
}
/* 当变量test1超过它作用域时,它指向a对象会自动release
* 当变量test2超过它作用域时,它指向b对象会自动release
* 此时,b对象的objc成员变量仍持有一个强引用到对象a
* 此时,a对象的objc成员变量仍持有一个强引用到对象b
* 于是发生内存泄露
*/
如何解决?于是我们引用一个__weakownership qualifier,被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。例如:
__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
NSLog(@"%@", text);
由于text变量被__weak修饰,text并不持有@"Sam Lau"对象的所有权,@"Sam Lau"对象一创建就马上被释放,并且编译器给出警告,所以打印结果为(null)。
所以,针对刚才的引用循环问题,只需要将Test类的属性objc设置weak修饰符,那么就能解决。
@interface Test : NSObject
@property (weak, nonatomic) id objc;
@end
{
Test *test1 = [Test new]; /* 对象a */
/* test1有一个强引用到对象a */
Test *test2 = [Test new]; /* 对象b */
/* test2有一个强引用到对象b */
test1.objc = test2; /* 对象a的成员变量objc不持有对象b */
test2.objc = test1; /* 对象b的成员变量objc不持有对象a */
}
/* 当变量test1超过它作用域时,它指向a对象会自动release
* 当变量test2超过它作用域时,它指向b对象会自动release
*/
__unsafe_unretained ownership qualifier
__unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC为0时,变量并不设置为nil,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。例子如下:
__unsafe_unretained id obj0 = nil;
{
id obj1 = [[NSObject alloc] init]; // 对象A
/* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */
obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */
NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */
NSLog(@"B: %@", obj0);
/* 由于obj0是__unsafe_unretained,当它指向的对象RC=0时,它会继续保存对象的地址,所以两个地址相同 */
打印结果是内存地址相同:
如果将__unsafe_unretained改为weak的话,两个打印结果将不同
__weak id obj0 = nil;
{
id obj1 = [[NSObject alloc] init]; // 对象A
/* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */
obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */
NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */
NSLog(@"B: %@", obj0);
/* 由于obj0是__weak, 当它指向的对象RC=0时,它会自动设置为nil,所以两个打印结果将不同*/
__autoreleasing ownership qualifier
引入ARC之后,让我们看看autorelease pool有哪些变化。没有ARC之前的写法如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */
引入ARC之后,写法比之前更加简洁:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
相比之前的创建、使用和释放NSAutoreleasePool对象,现在你只需要将代码放在@autoreleasepool块即可。你也不需要调用autorelease方法了,只需要用__autoreleasing修饰变量即可。
引用Pro Multithreading and Memory Management for iOS and OS X的图
但是我们很少或基本上不使用autorelease pool。当我们使用XCode创建工程后,有一个app的入口文件main.m使用了它:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
roperty(属性)
有了ARC之后,新的property modifier也被引入到Objective-C类的property,例如:
@property (strong, nonatomic) NSString *text;
下面有张表来展示property modifier与ownership qualifier的对应关系
总结
要想掌握iOS/OS X的内存管理,首先要深入理解引用计数(Reference Count)这个概念以及内存管理的规则;在没引入ARC之前,我们都是通过retain和release方法来手动管理内存,但引入ARC之后,我们可以借助编译器来帮忙自动调用retain和release方法来简化内存管理和减低出错的可能性。虽然__strong修饰符能够执行大多数内存管理,但它不能解决引用循环(Reference Cycle)问题,于是又引入另一个修饰符__weak。被__strong修饰的变量都持有对象的所有权,而被__weak修饰的变量并不持有对象所有权。下篇我们介绍使用工具如何解决常见内存问题:悬挂指针和内存泄露。
软件运行时会分配和使用设备的内存资源,因此,在软件开发的过程中,需要进行内存管理,以保证高效、快速的分配内存,并且在适当的时候释放和回收内存资源。
一、Objective-C内存管理的对象
IOS开发中,内存中的对象主要有两类,一类是值类型,比如int、float、struct等基本数据类型,另一类是引用类型,也就是继承自NSObject类的所有的OC对象。前一种值类型不需要我们管理,后一种引用类型是需要我们管理内存的,一旦管理不好,就会产生非常糟糕的后果。
为什么值类型不需要管理,而引用类型需要管理呢?那是因为他们分配内存方式不一样。
值类型会被放入栈中,他们依次紧密排列,在内存中占有一块连续的内存空间,遵循先进后出的原则。引用类型会被放到堆中,当给对象分配内存空间时,会随机的从内存当中开辟空间,对象与对象之间可能会留有不确定大小的空白空间,因此会产生很多内存碎片,需要我们管理。
栈内存与堆内存从性能上比较,栈内存要优于堆内存,这是因为栈遵循先进后出的原则,因此当数据量过大时,存入栈会明显的降低性能。因此,我们会把大量的数据存入堆中,然后栈中存放堆的地址,当需要调用数据时,就可以快速的通过栈内的地址找到堆中的数据。
值类型和引用类型之间是可以相互转化的,把值类型转化为引用类型的过程叫做装箱,比如把int包装为NSNumber,这个过程会增加程序的运行时间,降低性能。而把引用类型转为值类型的过程叫做拆箱,比如把NSNumer转为float,在拆箱的过程中,我们一定要注意数据原有的类型,如果类型错误,可能导致拆箱失败,因此会存在安全性的问题。手动的拆箱和装箱,都会增加程序的运行时间,降低代码可读性,影响性能。
在IOS开发过程中,栈内存中的值类型系统会自动管理,堆内存中的引用类型是需要我们管理的。每个OC对象内部都专门有四个字节来存储引用计数器,它是一个整数,表示对象被引用的次数,通过它可以判断对象是否被回收,如果引用计数为0,对象回收,不为0不回收。当对象执行alloc、new或者retain时,引用计数加1,release时,引用计数减1。
二、Objective-C管理内存的方式
Objective-c中提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。
1.MRC(人工引用计数),手动管理内存。
MRC模式下,所有的对象都需要手动的添加retain、release代码来管理内存。使用MRC,需要遵守谁创建,谁回收的原则。也就是谁alloc,谁release;谁retain,谁release。
当引用计数为0的时候,必须回收,引用计数不为0,不能回收,如果引用计数为0,但是没有回收,会造成内存泄露。如果引用计数为0,继续释放,会造成野指针。为了避免出现野指针,我们在释放的时候,会先让指针=nil。
2.ARC(自动引用计数),自动管理内存。
ARC是IOS5推出的新功能,通过ARC,可以自动的管理内存。在ARC模式下,只要没有强指针(强引用)指向对象,对象就会被释放。在ARC模式下,不允许使用retain、release、retainCount等方法。并且,如果使用dealloc方法时,不允许调用[super dealloc]方法。
ARC模式下的property变量修饰词为strong、weak,相当于MRC模式下的retain、assign。strong :代替retain,缺省关键词,代表强引用。weak:代替assign,声明了一个可以自动设置nil的弱引用,但是比assign多一个功能,指针指向的地址被释放之后,指针本身也会自动被释放。
三、与内存有关的修饰符
strong :强引用,ARC中使用,与MRC中retain类似,使用之后,计数器+1。
weak :弱引用 ,ARC中使用,如果只想的对象被释放了,其指向nil,可以有效的避免野指针,其引用计数为1。
readwrite : 可读可写特性,需要生成getter方法和setter方法时使用。
readonly : 只读特性,只会生成getter方法 不会生成setter方法,不希望属性在类外改变。
assign :赋值特性,不涉及引用计数,弱引用,setter方法将传入参数赋值给实例变量,仅设置变量时使用。
retain :表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。
copy :表示拷贝特性,setter方法将传入对象复制一份,需要完全一份新的变量时。
nonatomic :非原子操作,不加同步,多线程访问可提高性能,但是线程不安全的。决定编译器生成的setter getter是否是原子操作。
atomic :原子操作,同步的,表示多线程安全,与nonatomic相反。
四、MRC与ARC混编
MRC与ARC理论上是不能兼容的,也就是你如果创建的项目是ARC模式的,在你的代码中是不能使用release,否则会出现内存问题。现在大部分程序都会选择ARC的方式,但是很多第三方的框架是MRC模式,如果想把这些第三方的文件加到自己项目中,需要进行标识,否则编译的时候会出现错误。
在ARC的项目中,对MRC的文件可以添加编译选项-fno-objc-arc的标识;在MRC的项目中,对ARC的文件可以添加编译选项 -fobjc-arc的标识。
把MRC文件转为ARC,实际上是去掉文件中的retain、release,