17-内存管理

最后更新于:2022-04-01 00:17:32

内存管理关心的是清理(回收)不用的内存,以便内存能够再次利用。 提供给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中自己释放它们 ~~~ #import       @interface Song : NSObject {       NSString *title;       NSString *artist;       long int duration;   }      //操作方法   - (void)start;     - (void)stop;      - (void)seek:(long int)time;      //访问成员变量方法   @property NSString *title;   @property NSString *artist;   @property(readwrite) long int duration;      //构造函数   -(Song*) initWithTitle: (NSString *) title andArtist: (NSString *) artist andDuration:( long int )duration ;         @end   ~~~ 实现如下: ~~~ #import "Song.h"      @implementation Song      @synthesize title;   @synthesize artist;   @synthesize duration;      //构造函数   -(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist andDuration:(long int)newDuration {       self = [super init];       if ( self ) {           self.title = newTitle;           self.artist = newArtist;           self.duration = newDuration;       }       return self;          }      - (void)start {       //开始播放   }      - (void)stop {       //停止播放   }      - (void)seek:(long int)time {       //跳过时间   }      -(void) dealloc {       NSLog(@"释放Song对象...");       [title release];       [artist release];       [super dealloc];   }      @end   ~~~ 调用的main函数 ~~~ #import    #import "Song.h"      int main (int argc, const charchar * argv[]) {          Song *song1 = [[Song alloc] initWithTitle:@"Big Big World" andArtist:@"奥斯卡.艾美莉亚" andDuration:180];       Song *song2 = [[Song alloc] initWithTitle:@"It's ok" andArtist:@"atomic kitten" andDuration:280];              // print current counts       NSLog(@"song 1 retain count: %i", [song1 retainCount] );       NSLog(@"song 2 retain count: %i", [song2 retainCount] );              // increment them       [song1 retain]; // 2       [song1 retain]; // 3       [song2 retain]; // 2              // print current counts       NSLog(@"song 1 retain count: %i", [song1 retainCount] );       NSLog(@"song 2 retain count: %i", [song2 retainCount] );              // decrement       [song1 release]; // 2       [song2 release]; // 1              // print current counts       NSLog(@"song 1 retain count: %i", [song1 retainCount] );       NSLog(@"song 2 retain count: %i", [song2 retainCount] );              // release them until they dealloc themselves       [song1 release]; // 1       [song1 release]; // 0       [song2 release]; // 0              return 0;   }   ~~~ 代码分析: 在这个main函数中, 声明了两个Song对象, 当retain调用增加引用计数, 而release调用减少它。  调用 [obj retainCount] 来取得引用计数的 int 值。  当retainCount到达 0, 两个对象都会调用 dealloc, 所以可以看到印出了两个 “释放Song对象...” 。  在Song对象释放的时候, 先要释放它自己的对象类型成员变量title和artist, 然后再调用[super dealloc] 。 自动释放池 内存释放池(Autorelease pool ) 提供了一个对象容器,  每次对象发送autorelease消息时, 对象的引用计数并不真正变化,  而是向内存释放池中添加一条记录, 记下对象的这种要求,  直到当内存释放池发送drain或release消息时,  当池被销毁前会通知池中的所有对象, 全部发送release消息真正将引用计数减少。  ~~~ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];   … …   [pool release];// [pool drain];   ~~~ 这些语句必须要放在下面语句之间, 直到池被释放,  一个对象要想纳入内存释放池对象, 必须要发送autorelease。  ~~~ #import       int main (int argc, const charchar * argv[]) {              NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];              NSArray *weeksNames1 = [NSArray arrayWithObjects:                               @"星期一",@"星期二",@"星期三",@"星期四"                              ,@"星期五",@"星期六",@"星期日",nil];              NSArray *weeksNames2 = [[NSArray alloc] initWithObjects:                               @"星期一",@"星期二",@"星期三",@"星期四"                              ,@"星期五",@"星期六",@"星期日",nil];              //[weeksNames1 release];       //[weeksNames1 autorelease];       //[weeksNames2 release];        //[weeksNames2 autorelease];              NSLog(@" retain count: %i" , [weeksNames1 retainCount] );       NSLog(@" retain count: %i" , [weeksNames2 retainCount] );             [pool release];       return 0;      }   ~~~ NSArray类是Foundation框架提供的不可变数组类, Foundation框架中对象的创建有两类方法:  类方法(+)构造方法和实例方法(-) 构造方法。  打开NSArray ClassReference文档, 其中创建对象有关的方法如图所示。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f102112.jpg) 从NSArray Class Reference文档可以看出, 以+和类名开头(去掉NS, 小写第1 个字母, array),就是类级构造方法, 以-和initWith开头的就是实例构造方法。  类级构造方法不能使用 release, 可以不用 autorelease就可以自动纳入内存释放池管理。  实例构造方法, 如果发出release消息就马上释放对象,  如果发出autorelease消息可以自动纳入内存释放池管理, 不会马上释放。  在iOS开发中由于内存相对少, 因此基本上都采用实例构造方法实例化对象,  采用发送release消息立刻释放对象内存。 属性中的内存管理参数 有的时候我们声明的对象要跨越对象调用 , 内存管理就会变的更加复杂。  例如在Song类中有一对成员变量存取的方法,当然可以把它们封装成属性, 通过属性参数来管理内存。  在Song类中给成员变量设置方法如下:  ~~~ - (void) setTitle:(NSString *) newTitle {   title = newTitle;   }   - (void) setArtist:(NSString *) newArtist {   artist = newArtist;   }   ~~~ 这段代码事实上有内存泄漏, 当设置一个新的Title时候,title = newTitle只是将指针改变了,  旧对象并没有释放, 所以我们会这样修改这些方法:  ~~~ - (void) setTitle:(NSString *) newTitle {   [newTitle retain];   [title release];   title = [[NSString alloc] initWithString: newTitle];   }   - (void) setArtist:(NSString *) newArtist {   [newArtist retain];   [artist release];   artist = [[NSString alloc] initWithString: newArtist];   }   ~~~ 首先保留新对象, 释放旧对象, 然后使用实例构造方法实例化新的对象。  参数newTitle不要在方法中释放。  由于基本数据类型(非对象类型) 不需要释放, 因此下面的写法是没有问题的。  ~~~ - (void) setDuration:(long int) newDuration {   duration = newDuration;   }   ~~~ 此外, 在构造方法中也必须要注意, 不能直接赋值title =newTitle, 而是要调用自身的设置方法: ~~~ //构造函数   -(Song*) initWithTitle: (NSString *) newTitle andArtist: (NSString *) newArtist               andDuration:(long int)newDuration {       self = [super init];       if ( self ) {           [self setTitle:newTitle];                  [self setArtist:newArtist];           [self setDuration:newDuration];       }       return self;       }   ~~~ assign 参数 assign参数代表设置时候直接赋值, 而不是复制或者保留它。  这种机制非常适合一些基本类型, 比如NSInteger和CGFloat,  或者就是不想直接拥有的类型, 比如委托。  assign相当于如下写法。 ~~~ - (void) setTitle:(NSString *) newTitle {   title = newTitle;   }   ~~~ retain参数 retain参数会在赋值时把新值保留(发送retain消息) 。  此属性只能用于Objective-C对象类型, 而不能用于基本数据类型或者Core Foundation。  retain相当于如下写法: ~~~ (void) setTitle:(NSString *) newTitle {   [newTitle retain];   [title release];   title = [[NSString alloc] initWithString: newTitle];   }   ~~~ copy参数 copy在赋值时将新值拷贝一份, 拷贝工作由copy方法执行, 此属性只对那些实行了NSCopying协议的对象类型有效。  copy相当于如下写法: ~~~ - (void) setTitle:(NSString *) newTitle {   [newTitle copy];   [title release];   title = [[NSString alloc] initWithString: newTitle];   }   ~~~ 手工内存管理规则的总结: 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都会在“底层”发生,所以一般不用关心。
';