内存管理的思考方式:
自己生成的对象,自己持有。
非自己生成的对象,自己也能持有。
不在需要自己持有的对象时释放
非自己持有的对象无法释放
一.手动内存管理
1.基本原理
对象的创建,OC创建对象时,不会直接返回该对象,而是返回一个指向该对象的指针。
Class *a=[[Class alloc]init]; 在alloc时,系统会给Class的对象分配内存空间,并且反回了指向未初始化的对象的一个指针
未初始化的Class对象接收到init消息时,init返回指向已经初始化Class对象的一个指针,然后将其赋值给变量a
如果指针a和b同时指向堆中同一块内存地址
Class *a=[[Class alloc]init];//自己生成并持有对象
Class *b=a;
当a释放时,b就成了无头指针(无头指针式危险的)
id objc=[NSMutableArray array];//取得的对象存在,但自己并不持有对象。
[objc retain];//自己持有对象
2.OC的内存管理采用引用计数(retain count)即在对象内部保存一个数字,用来表示被引用的次数,init,new,copy,mutacopy都会使retain count加1
当我们销毁对象时,先调用release方法,当retain count为0时,系统才会调用dealloc方法销毁对象。
在指针赋值时,retain count是不会自动增加的
Class *a=[[Class alloc]init];//reatin count=1;
Class *b=a;
[b retain];//retain count=2;
[a delloc];
这样在执行到[a release ]时,retain count只是减了1,指针b仍然有效
3.内存泄露:
当生成Class对象时,指针a就拥有了对象的访问权,如果失去了对象的访问权,但有没有将retain count减到0,就会造成内存泄露,即分配出去的内存无法回收
Class *a=[[Class alloc]init];
a=nil
4.Autorelease pool
autorelease的具体使用方法为:
(1)生成并持有NSAutoreleasePool对象
(2)调用已经分配的对象的autorelease实例方法。
(3)废弃NSAutorelease对象
源代码表示如下:
NSAutoreleasePool *pool=[NSAutorelease alloc]init];
id obj=[NSObject alloc]init];
obj autorelease];
pool drain];//等同于 obj release
为了方便程序员管理内存,apple在OC中引用了自动释放池,在遵守一定规则的情况下,可以进行自动释放
Class *a=[[Class alloc]init]autorelease]; //retain count =1,但无需release
autorelease pool需要手动创建
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]int];但是通常情况下,在我们创建一个iPhone项目时,Xcode会自动创建autoreleasePool,这个pool就写在Main函数里面。
在NSAutoreleasePool中包含了一个可变数组,用来存放被生命为autorelease的对象,当NSAutoreleasePool自身被销毁时,他会遍历这个数组,release数组中得每一个成员(只是release,没有销毁对象),此时若对象的retain count 大于1,对象就不会被销毁,造成内存泄露。
默认的NSAutoreleasePool只有一个,但你可以在程序中创建NSAutoreleasePool,被标记为autorelease的对象会和最近的NSAutoreleasePool匹配
也可以嵌套使用NSAutoreleasePool,就像for循环那样。
但是使用NSAutoreleasePool管理内存是不推荐的,因为在一个NSAutoreleasePool里,如果有大量对象被标记为autorelease,在程序运行时,内存会剧增,直到NSAutoreleasePool被销毁时,才会释放。如果其中的对象足够多,就会发生内存警告,或者直接崩溃。
注意:
如果我们将主函数中得NSAutoreleasePool代码删掉,然后在自己的代码中将对象声明为autorelease,此时系统不会发生错误或者警告,此时如果用内存检测工具去检测内存时,会发现对象仍然被销毁了。
原因:其实在新生成一个Run Loop的时候,系统会自动创建一个NSAutoreleasePool,这个NSAutoreleasePool无法被删除。
但是做内存检测时,不要用NSString,因为OC对字符串进行了特殊处理。
例如 NSString *str=[[NSString alloc]stringWithString:@"444"];
在输出str的retain count的时候,你们发现retain count大于1。
5.手动管理内存Class *a=[[Class alloc]init];
Class *b=a;
[b retain]
[b release];
b=nil;
把一个指针赋值给另外一个指针时,a指针所指向的对象的引用次数并没有增加,也就是说,其retain count =1
然后 [b retain] retain count +1; 那么此时执行 a release 只是a指针放弃了该对象的访问权。对象的retain count减去1
对象并未被销毁,只有b release之后,才会将对象销毁掉,在对象销毁后,指针仍然存在,因此最后把指针赋空,release一个空指针式合法的
6.属性和内存管理
@property实际上是getter和setter方法,
nonatomic:如果你的程序里只有一个主线程,即不会在2个或者更多的线程上工作时访问同一个变量,那么你可以声明为nonatomic,这样不会去考虑线程安全问题。
但相反情况下,可以声明为atomic,也可以不声明,因为系统默认为nonatomic,这样就会加上一个锁,在同一时间,只会有一个变量访问这个该变量。
但是用锁是要付出代价的,一个声明为atomic的属性在设置和获取这个变量的时候要比声明nonatomic的慢,因此如果不打算编写多线程代码,最好把其声明为nonatomic
assign是系统默认的属性,它几乎适用于OC所有的变量类型,对于非对象类型的变量,assign是唯一的选择。但其声明对象是,只是创建了一个弱引用,因此声明对象时,最好不要用
关于assign合成那个的setter 类似于这样
-(void)setObjA:(ClassA *)a
{
objA=a;
}
声明retain特性的setter方法为
-(void)setObjA:(ClassA *)a{
if(objA!=a){
[objA release]
objA=a;
objA retain];
}
}
很明显,retain的setter中变量retain了一次,因此
在程序中self.objA=a;
只写了这一句,仍然需要release,才能保证retain count是正确的
但如果值谢了objA=a;
这里只是进行了一次浅复制,retain count并为增加,这样写的话,就不需要release
这两句的区别是,第一个是setter,第二个只是简单的指针赋值
copy的setter是这样的
-(void)setObjA:(ClassA*)a
{
ClassA * temp=objA;
objA=[a copyWithZone:nil];
[temp release];
}
复制必须经过实现copyWithZone这个方法(该方法生成并持有对象的副本),因此copy这个特性只适用于拥有这个方法的类型,即必须这个类支持复制,复制是把原来的对象release掉,
然后让指针指向一个新的对象的副本,因此即使在setter里面release了原来的对象,你仍然需要在后面release掉其副本。
二、自动内存管理(ARC)
1.Id类型用于隐藏对象类型的类名部分,相当于C语言中的void*
Id和对象类型在没有明确指定所有权修饰符时,默认为__strong强引用,持有强引用的变量在超出其作用域时被废弃。
Id __strong obj = [[NSObject alloc]init]; 自己生成并持有对象
2.__week修饰符
引用计数式内存管理必然会发生循环引用的问题。
下面说一个循环引用的例子
{id test1=[[Test alloc]init];
//test1持有test对象A的强引用
id test2=[[Test alloc]init];
//test2持有test对象B的强引用
[test1 setObject:test2 ];
//Test对象A的obj_成员变量持有对象B的强引用,此时持有Test对象B的强引用变量为Test对象A的obj_和test1
[test2 setObject:test1 ];
//test对象B的obj_成员变量持有test对象A的强引用。此时持有test对象A的强引用变量为Test对象B的obj_和Test1
}
/*
因为Test0变量超出其作用域,强引用失效,所以自动释放Test对象
因为Test1变量超出其作用域,强引用失效,所以自动释放Test对象
此时持有Test对象A的强引用的变量为TestB的objc_
此时持有Test对象B的强引用的变量为TestA的objc_
发生内存泄露
*/
所谓内存泄露就是应当废弃的对象在超出其生存周期后仍然存在。__week 修饰符可以避免循环引用 弱引用不持有对象,所以在超出其变量作用域时,对象即被释放。
其另一个优点为 当持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效,且被赋值为nil。
参考 :小议内存管理和Object-c高级编程