内存管理和ARC

内存管理关心的是清理(回收)不用的内存,以便内存能够再次利用。

提供给Objective-C程序员的基本内存管理模型有以下三种:

1)自动垃圾收集。(iOS运行环境并不支持垃圾收集,在这个平台开发程序时没有这方面的选项,只能用在Mac OS X 程序上开发。这个机制挺恶心的,用mac电脑的人知道,当内存不足的时候,机器基本上就是卡死了。)

2)手工引用计数和自动释放池。(这种方式,对程序员的要求很高。自己维护嘛,)

3)自动引用计数(ARC)。


Objective-C为每个对象提供一个内部计数器, 这个计数器跟踪对象的引用次数。 
所有类都继承自 NSObject 的对象。

retain和release方法:
当对象被创建或拷贝时候, 引用计数为1 。 
每次保持对象时候, 就发送一条retain消息, 使其引用计数加1 , 
如果不需要这个对象就是发送一个release消息使其引用计数减1 。
当对象的引用计数为0的时候, 系统就知道不再需要这个对象了, 就会释放它内存。 

一个对象的创建可以通过alloc分配内存或copy复制, 
这关系到的方法有: alloc, allocWithZone:
copy, copyWithZone, mutableCopy,mutableCopyWithZone, 这些方法都可以使引用计数为1 , 
retain会使引用计数加1 ,
release会使引用计数减1 。

重写dealloc方法
当对象包含其它对象时, 就得在 dealloc中自己释放它们
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #import <Foundation/Foundation.h>  
  2.   
  3. @interface Song : NSObject {  
  4.     NSString *title;  
  5.     NSString *artist;  
  6.     long int duration;  
  7. }  
  8.   
  9. //操作方法  
  10. - (void)start;    
  11. - (void)stop;     
  12. - (void)seek:(long int)time;  
  13.   
  14. //访问成员变量方法  
  15. @property NSString *title;  
  16. @property NSString *artist;  
  17. @property(readwritelong int duration;  
  18.   
  19. //构造函数  
  20. -(Song*) initWithTitle: (NSString *) title andArtist: (NSString *) artist andDuration:( long int )duration ;  
  21.   
  22.   
  23. @end  
实现如下:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #import "Song.h"  
  2.   
  3. @implementation Song  
  4.   
  5. @synthesize title;  
  6. @synthesize artist;  
  7. @synthesize duration;  
  8.   
  9. //构造函数  
  10. -(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist andDuration:(long int)newDuration {  
  11.     self = [super init];  
  12.     if ( self ) {  
  13.         self.title = newTitle;  
  14.         self.artist = newArtist;  
  15.         self.duration = newDuration;  
  16.     }  
  17.     return self;  
  18.       
  19. }  
  20.   
  21. - (void)start {  
  22.     //开始播放  
  23. }  
  24.   
  25. - (void)stop {  
  26.     //停止播放  
  27. }  
  28.   
  29. - (void)seek:(long int)time {  
  30.     //跳过时间  
  31. }  
  32.   
  33. -(void) dealloc {  
  34.     NSLog(@"释放Song对象...");  
  35.     [title release];  
  36.     [artist release];  
  37.     [super dealloc];  
  38. }  
  39.   
  40. @end  
调用的main函数
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #import <Foundation/Foundation.h>  
  2. #import "Song.h"  
  3.   
  4. int main (int argc, const charchar * argv[]) {  
  5.   
  6.     Song *song1 = [[Song alloc] initWithTitle:@"Big Big World" andArtist:@"奥斯卡.艾美莉亚" andDuration:180];  
  7.     Song *song2 = [[Song alloc] initWithTitle:@"It's ok" andArtist:@"atomic kitten" andDuration:280];  
  8.       
  9.     // print current counts  
  10.     NSLog(@"song 1 retain count: %i", [song1 retainCount] );  
  11.     NSLog(@"song 2 retain count: %i", [song2 retainCount] );  
  12.       
  13.     // increment them  
  14.     [song1 retain]; // 2  
  15.     [song1 retain]; // 3  
  16.     [song2 retain]; // 2  
  17.       
  18.     // print current counts  
  19.     NSLog(@"song 1 retain count: %i", [song1 retainCount] );  
  20.     NSLog(@"song 2 retain count: %i", [song2 retainCount] );  
  21.       
  22.     // decrement  
  23.     [song1 release]; // 2  
  24.     [song2 release]; // 1  
  25.       
  26.     // print current counts  
  27.     NSLog(@"song 1 retain count: %i", [song1 retainCount] );  
  28.     NSLog(@"song 2 retain count: %i", [song2 retainCount] );  
  29.       
  30.     // release them until they dealloc themselves  
  31.     [song1 release]; // 1  
  32.     [song1 release]; // 0  
  33.     [song2 release]; // 0  
  34.       
  35.     return 0;  
  36. }  
代码分析:
在这个main函数中, 声明了两个Song对象, 当retain调用增加引用计数, 而release调用减少它。 
调用 [obj retainCount] 来取得引用计数的 int 值。 
当retainCount到达 0, 两个对象都会调用 dealloc, 所以可以看到印出了两个 “释放Song对象...” 。 
在Song对象释放的时候, 先要释放它自己的对象类型成员变量title和artist, 然后再调用[super dealloc] 。

自动释放池
内存释放池(Autorelease pool ) 提供了一个对象容器, 
每次对象发送autorelease消息时, 对象的引用计数并不真正变化, 
而是向内存释放池中添加一条记录, 记下对象的这种要求, 
直到当内存释放池发送drain或release消息时, 
当池被销毁前会通知池中的所有对象, 全部发送release消息真正将引用计数减少。 
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  2. … …  
  3. [pool release];// [pool drain];  
这些语句必须要放在下面语句之间, 直到池被释放, 
一个对象要想纳入内存释放池对象, 必须要发送autorelease。 
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #import <Foundation/Foundation.h>  
  2.   
  3. int main (int argc, const charchar * argv[]) {  
  4.       
  5.     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  6.       
  7.     NSArray *weeksNames1 = [NSArray arrayWithObjects:   
  8.                            @"星期一",@"星期二",@"星期三",@"星期四"  
  9.                            ,@"星期五",@"星期六",@"星期日",nil];  
  10.       
  11.     NSArray *weeksNames2 = [[NSArray alloc] initWithObjects:   
  12.                            @"星期一",@"星期二",@"星期三",@"星期四"  
  13.                            ,@"星期五",@"星期六",@"星期日",nil];  
  14.       
  15.     //[weeksNames1 release];  
  16.     //[weeksNames1 autorelease];  
  17.     //[weeksNames2 release];  
  18.      //[weeksNames2 autorelease];  
  19.       
  20.     NSLog(@" retain count: %i" , [weeksNames1 retainCount] );  
  21.     NSLog(@" retain count: %i" , [weeksNames2 retainCount] );  
  22.       
  23.    [pool release];  
  24.     return 0;     
  25. }  


NSArray类是Foundation框架提供的不可变数组类,
Foundation框架中对象的创建有两类方法: 
类方法(+)构造方法和实例方法(-) 构造方法。 

打开NSArray ClassReference文档, 其中创建对象有关的方法如图所示。

从NSArray Class Reference文档可以看出,
以+和类名开头(去掉NS, 小写第1 个字母, array), 就是类级构造方法,
以-和initWith开头的就是实例构造方法。 

类级构造方法不能使用 release, 可以不用 autorelease就可以自动纳入内存释放池管理。 
实例构造方法, 如果发出release消息就马上释放对象, 
如果发出autorelease消息可以自动纳入内存释放池管理, 不会马上释放。 
在iOS开发中由于内存相对少, 因此基本上都采用实例构造方法实例化对象, 
采用发送release消息立刻释放对象内存。

属性中的内存管理参数
有的时候我们声明的对象要跨越对象调用 , 内存管理就会变的更加复杂。 

例如在Song类中有一对成员变量存取的方法,当然可以把它们封装成属性, 通过属性参数来管理内存。 
在Song类中给成员变量设置方法如下: 
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void) setTitle:(NSString *) newTitle {  
  2. title = newTitle;  
  3. }  
  4. - (void) setArtist:(NSString *) newArtist {  
  5. artist = newArtist;  
  6. }  
这段代码事实上有内存泄漏, 当设置一个新的Title时候,title = newTitle只是将指针改变了, 
旧对象并没有释放, 所以我们会这样修改这些方法: 
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void) setTitle:(NSString *) newTitle {  
  2. [newTitle retain];  
  3. [title release];  
  4. title = [[NSString alloc] initWithString: newTitle];  
  5. }  
  6. - (void) setArtist:(NSString *) newArtist {  
  7. [newArtist retain];  
  8. [artist release];  
  9. artist = [[NSString alloc] initWithString: newArtist];  
  10. }  
首先保留新对象, 释放旧对象, 然后使用实例构造方法实例化新的对象。 
参数newTitle不要在方法中释放。 
由于基本数据类型(非对象类型) 不需要释放, 因此下面的写法是没有问题的。 
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void) setDuration:(long int) newDuration {  
  2. duration = newDuration;  
  3. }  
此外, 在构造方法中也必须要注意, 不能直接赋值title =newTitle, 而是要调用自身的设置方法:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //构造函数  
  2. -(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist   
  3.            andDuration:(long int)newDuration {  
  4.     self = [super init];  
  5.     if ( self ) {  
  6.         [self setTitle:newTitle];         
  7.         [self setArtist:newArtist];  
  8.         [self setDuration:newDuration];  
  9.     }  
  10.     return self;      
  11. }  

assign 参数
assign参数代表设置时候直接赋值, 而不是复制或者保留它。 
这种机制非常适合一些基本类型, 比如NSInteger和CGFloat, 
或者就是不想直接拥有的类型, 比如委托。 

assign相当于如下写法。
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void) setTitle:(NSString *) newTitle {  
  2. title = newTitle;  
  3. }  
retain参数
retain参数会在赋值时把新值保留(发送retain消息) 。 
此属性只能用于Objective-C对象类型, 而不能用于基本数据类型或者Core Foundation。 
retain相当于如下写法:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. (void) setTitle:(NSString *) newTitle {  
  2. [newTitle retain];  
  3. [title release];  
  4. title = [[NSString alloc] initWithString: newTitle];  
  5. }  
copy参数
copy在赋值时将新值拷贝一份, 拷贝工作由copy方法执行,
此属性只对那些实行了NSCopying协议的对象类型有效。 
copy相当于如下写法:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void) setTitle:(NSString *) newTitle {  
  2. [newTitle copy];  
  3. [title release];  
  4. title = [[NSString alloc] initWithString: newTitle];  
  5. }  

手工内存管理规则的总结:

1)如果需要保持一个对象不被销毁,可以使用retain。在使用完对象后,需要使用release进行释放。

2)给对象发送release消息并不会必须销毁这个对象,只有当这个对象的引用计数减至0时,对象才会被销毁。然后系统会发送dealloc消息给这个对象用于释放它的内存。

3)对使用了retain或者copy、mutableCopy、alloc或new方法的任何对象,以及具有retain和copy特性的属性进行释放,需要覆盖dealloc方法,使得在对象被释放的时候能够释放这些实例变量。

4)在自动释放池被清空时,也会为自动释放的对象做些事情。系统每次都会在自动释放池被释放时发送release消息给池中的每个对象。如果池中的对象引用计数降为0,系统会发送dealloc消息销毁这个对象。

5)如果在方法中不再需要用到这个对象,但需要将其返回,可以给这个对象发送autorelease消息用以标记这个对象延迟释放。autorelease消息并不会影响到对象的引用计数。

6)当应用终止时,内存中的所有对象都会被释放,不论它们是否在自动释放池中。

7)当开发Cocoa或者iOS应用程序时,随着应用程序的运行,自动释放池会被创建和清空(每次的事件都会发生)。在这种情况下,如果要使自动释放池被清空后自动释放的对象还能够存在,对象需要使用retain方法,只要这些对象的引用计数大于发送autorelease消息的数量,就能够在池清理后生存下来。


自动引用计数(ARC):

强变量,通常,所有对象的指针变量都是强变量。

如果想声明强变量,可以使用__strong Faction *fl;这样的__strong来修饰。

值得注意的是,属性默认不是strong,其默认的特性是unsafe_unretained类似assign。

所以需要声明属性strong时,可以如下:

@property (strong, nonatomic) NSMutableArray *birdNames;

编译器会保证在事件循环中通过对赋值执行保持操作强属性能够存活下来。

带有unsafe_unretained(相当于assign)或weak的属性不会执行这些操作。

弱变量,可以使用__week关键字来声明。弱变量不能阻止引用的对象被销毁,防止产生循环保持。

当引用的对象释放时,弱变量会被自动设置为nil。

需要注意的是,在iOS4和Mac OS V10.6中不支持弱变量。

在这种情况下,你仍然可以为属性使用unsafe_unretained, assing特性.

ARC都会在“底层”发生,所以一般不用关心。



属性特性:

assign:指定setter方法用简单的赋值,这是默认操作。你可以对标量类型(如int)使用这个属性。你可以想象一个float,它不是一个对象,所以它不能retain、copy。

retain:指定retain应该在后面的对象上调用,前一个值发送一条release消息。你可以想象一个NSString实例,它是一个对象,而且你可能想要retain它。

copy:指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。

readonly:将只生成getter方法而不生成setter方法(getter方法没有get前缀)。

readwrite:默认属性,将生成不带额外参数的getter和setter方法(setter方法只有一个参数)。

atomic:对于对象的默认属性,就是setter/getter生成的方法是一个原子操作。如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter的情况,相关于方法头尾加了锁一样。

nonatomic:不保证setter/getter的原子性,多线程情况下数据可能会有问题。

详解

atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。

        atomic

                设置成员变量的@property属性时,默认为atomic,提供多线程安全。

                在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:
                        {lock}
                                if (property != newValue) { 
                                        [property release]; 
                                        property = [newValue retain]; 
                                }
                        {unlock}

        nonatomic

        禁止多线程,变量保护,提高性能。

        atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

        指出访问器不是原子操作,而默认地,访问器是原子操作。这也就是说,在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。


assign
        对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char)等等。
        此标记说明设置器直接进行赋值,这也是默认值。在使用垃圾收集的应用程序中,如果你要一个属性使用assign,且这个类符合NSCopying协             议,你就要明确指出这个标记,而不是简单地使用默认值,否则的话,你将得到一个编译警告。这再次向编译器说明你确实需要赋值,即使它是           可拷贝的。

retain
        对其他NSObject和其子类对参数进行release旧值,再retain新值
        指定retain会在赋值时唤醒传入值的retain消息。此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。(原因很明显,retain会增加对象的引用计数,而基本数据类型或者Core Foundation对象都没有引用计数——译者注)。

        注意: 把对象添加到数组中时,引用计数将增加对象的引用次数+1。

copy
        对NSString 它指出,在赋值时使用传入值的一份拷贝。拷贝工作由copy方法执行,此属性只对那些实行了NSCopying协议的对象类型有效。更深入的讨论,请参考“复制”部分。


copy与retain:

Copy其实是建立了一个相同的对象,而retain不是:
1.比如一个NSString 对象,地址为0×1111 ,内容为@”STR”,Copy 到另外一个NSString 之后,地址为0×2222 ,内容相同。

2.新的对象retain为1 ,旧有对象没有变化retain 到另外一个NSString 之后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1。
总结:retain 是指针拷贝,copy 是内容拷贝。


assign与retain:

1. 接触过C,那么假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块内存的时候会引起程序crash掉。
2. 了解到1中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。
总结:上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。


retain, copy, assign区别

assign、copy、retain
assign
:默认类型,setter方法直接赋值,不进行任何retain操作,不改变引用计数。一般用来处理基本数据类型。
retain:释放旧的对象(release),将旧对象的值赋给新对象,再令新对象引用计数为1。我理解为指针的拷贝,拷贝一份原来的指针,释放原来指针指向的对象的内容,再令指针指向新的对象内容。
copy:与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1.为了减少对上下文的依赖而引入的机 制。我理解为内容的拷贝,向内存申请一块空间,把原来的对象内容赋给它,令其引用计数为1。对copy属性要特别注意:被定义有copy属性的对象必须要 符合NSCopying协议,必须实现- (id)copyWithZone:(NSZone *)zone方法。
也可以直接使用:
    使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)
    使用copy: 对NSString
    使用retain: 对其他NSObject和其子类



1. 
假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a
和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块
内存的时候会引起程序crash掉。

2. 
了解到1中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference 
counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到 
2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,
代表该内存不再被任何指针所引用,系统可以把它直接释放掉。

3. 上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。
 
4. copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。




strong和weak特性

strong与weak
strong
:强引用,也是我们通常说的引用,其存亡直接决定了所指向对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示在列表中,则此对象会被从内存中释放。
weak:弱引用,不决定对象的存亡。即使一个对象被持有无数个弱引用,只要没有强引用指向它,那么还是会被清除。
strongretain功能相似;weakassign相似,只是当对象消失后weak会自动把指针变为nil;

strong 用来修饰强引用的属性;

@property (strong) SomeClass * aObject; 
对应原来的 
@property (retain) SomeClass * aObject; 和 @property (copy) SomeClass * aObject; 


weak 用来修饰弱引用的属性;
@property (weak) SomeClass * aObject; 
对应原来的 
@property (assign) SomeClass * aObject; 

strong关键字与retain关似,用了它,引用计数自动+1,用实例更能说明一切

@property (nonatomic, strong) NSString *string1;   

@property (nonatomic, strong) NSString *string2;  

 

有这样两个属性,

 

 @synthesize string1;   

@synthesize string2;  

 

 

猜一下下面代码将输出什么结果?

 

self.string1 = @"String 1";   

self.string2 = self.string1;   

self.string1 = nil;  

NSLog(@"String 2 = %@", self.string2);  

 

 

结果是:String 2 = String 1

由于string2是strong定义的属性,所以引用计数+1,使得它们所指向的值都是@"String 1", 如果你对retain熟悉的话,这理解并不难。

 

接着我们来看weak关键字:

如果这样声明两个属性:

 

@property (nonatomic, strong) NSString *string1;   

@property (nonatomic, weak) NSString *string2;  


并定义 

@synthesize string1;   

@synthesize string2;  

 

 
   


再来猜一下,下面输出是什么?

 

 self.string1 = @"String 1";   

self.string2 = self.string1;   

self.string1 = nil;  

NSLog(@"String 2 = %@", self.string2);  


结果是:String 2 = null

 

分析一下,由于self.string1与self.string2指向同一地址,且string2没有retain内存地址,而self.string1=nil释放了内存,所以string1为nil。声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。在c/c++开发过程中,为何大牛都说指针的空间释放了后,都要将指针赋为NULL. 在这儿用weak关键字帮我们做了这一步。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值