2016-08-01  15:36:30

  1. OC中的内存管理:
  • Objective--C增加了垃圾回收机制,作为初学者,需要清楚内存的管理,什么时候该申请内存,什么时候该释放内存,养成良好的编程习惯,开发无内存泄漏的应用程序。对于自己开发的应用程序,自己管理内存,当程序需要空间,则手动分配空间,当空间或其他资源不再需要时,手动回收。
  • OC中的alloc+init为开辟空间,OC中的release是释放空间的一个标示。

  2.自动释放池

  • 自动释放池是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池统一回收释放。
  • 每当一个对象接收到autorelease消息时,对象就会被放到自动释放池中,当自动释放池被释放时,(自动释放池中的)对象就会接收到一次release消息。
  • 当一个对象接收autorelease消息时,系统会把对象放到最近的自动释放池中,当需要清空自动释放池时,你可以向自动释放池发送一个release消息。

1 NSautoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 2 [pool addObject:tt]; 3 [pool addObject:ttt]; 4 [pool addObject:c]; 5 [pool drain];

  • 通常情况下,一个Cocoa中的命令行工具应用程序main函数中包含一下代码:
int main()
{
      @autoreleasepool
    {
          NSLog(@"Hello,World");  
    }   
      return 0;  
}
  • 简单来说,MRC下,当向一个对象发送autorelease消息时,对象不会被立即释放,而是会被稍后释放,当然,稍后并不是指任意时间。

 

#import <Foundation/Foundation.h>

@interface XYPoint : NSObject
{
    int x;
    int y;
}

@property (nonatomic) int x, y;

- (id)initWithX:(int)_x andY:(int)_y;

@end
#import "XYPoint.h"

@implementation XYPoint

@synthesize x, y;

- (id)initWithX:(int)_x andY:(int)_y
{
    if (self = [super init])
    {
        x = _x;
        y = _y;
    }
    return self;
}

@end
#import <Foundation/Foundation.h>
#import "XYPoint.h"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        XYPoint *p1 = [[XYPoint alloc] initWithX:1 andY:1];
        NSLog(@"%d, %d", p1.x, p1.y);
        [p1 autorelease];
        
        sleep(10);
        p1.x = 10;
        p1.y = 10;
        NSLog(@"%d, %d", p1.x, p1.y);

    }
    return 0;
}

 

  • 如果向p1发送autorelease消息,则所有的结果正常输出,p1不会被立即释放。而是当自动释放池释放时,p1才会被销毁。
  • 使用autorelease需要注意:
  • 首先发送过多的autorelease消息,就和你发送太多的release一样,当清空自动释放池时,可能会引发内存故障。
  • 其次虽然release消息可以被替换 为autorelease,但是出于对系统性能的考虑,可以使用release的地方尽量不使用autorelease,因为自动释放池所做的工作要比直接使用release多很多。
  • 最后,自动释放池的“延迟释放机制”可能会导致无用的内存消耗。
  • 在创建对象时,如果你没有使用alloc,则不需要使用release或者autorelease。但是如果你显示使用了alloc,则你不要忘记使用release或者autorelease。
  • 当自动释放池释放时,自动释放池中的对象可能会被释放。这事因为,当自动释放池释放时,系统会给释放池中的每一个对象发送一个release消息,使得对象的的引用计数器的值减1,如果对象引用计数器的值减为0了,则系统会向对象发送dealloc消息彻底销毁对象。

3.引用计数器

  • 当涉及OC的根类NSObject或其派生类时,我们往往会使用alloc方法申请内存,通过向对象发送release消息回收内存。但是,事情不总是那么简单。正在运行的应用程序可能在多个地方会引用你创建的对象;比如,一个对象可以被存储在一个数组中或者在其他地方被一个实例变量所引用。这种情况下,除非你确定每一个引用该对象的使用者,都不再使用该对象了。否则,你就有可能没办法去释放对象所占的内存,如果贸然回收空间还可能产生内存泄漏或者二次删除的问题。此时,就需要我们对内存进行管理。
  • 内存管理在OC中,也是很重要的一部分内容,在C中,内存申请一次就释放一次,虽然多个指针可以指向同一块空间,但是释放时只能通过气筒一个指针回收这块空间。但是,在OC中,增加了“引用计数器机制”来记录该对象被引用的此数,对象被引用几次,就需要释放几次。
  • 通常,创建一个新对象,对象引用计数器的值会被置为1,若在其他代码中引用该对象,则可以向对象发送retain消息,retain可以使对象的引用计数器的值加1,如果,这段代码中不在需要这个对象,可以给对象发送一个ralease消息,是对象引用计数器的值减1.
  • Foundation框架中也有增减对象引用计数器值得操作,例如,把对象加入数组或者移出数组都可以对对象的引用计数器产生影响。
  • AClass *anObject = [[AClass alloc] init];//reference count = 1 after alloc
  • [anObject retain]; //reference count += 1 after retain //2
  • [anObject retain];//reference count -= 1 after retain //1
  • [anObject retain];//reference count == 0 then dealloc//0

实际上,有三种方法可以增减对象引用计数器的值。这也就意味着你释放一个对象时,应该注意一下几种限制:

  • 显示使用alloc创建一个对象
  • 显示使用copy[WithZone:]或者mutableCopy[WithZone:]拷贝对象
  • 显示使用retain

使用之前的XYPoint类进行测试:

 1 #import <Foundation/Foundation.h>
 2 #import "XYPoint.h"
 3 int main(int argc, const char * argv[])
 4 {
 5     @autoreleasepool
 6     {
 7         XYPoint *p1 = [[XYPoint alloc] initWithX:1 andY:1];//使用alloc,对象引用计数起得值由0增为1
 8         NSLog(@"%d, %d", p1.x, p1.y);
 9         NSLog(@"%ld", [p1 retainCount]);//1
10         [p1 retain];//使用retain使对象引用计数器的值加1
11         NSLog(@"%ld", [p1 retainCount]);//2
12         
13         [p1 release];
14         [p1 release];
15        
16     }
17     return 0;
18 }
  • 一个对象可以接收任意个retain或者release消息,只要你能保证对象的引用计数器的值大于0。一旦对象引用计数器的值减为0,则编译器会给对象发送一个dealloc消息彻底销毁对象(即 dealloc 方法会被自动执行)。此时如果继续向对象发送release消息,则可能会导致内存错误使程序崩溃。
  • 我们可以通过向对象发送retainCount消息获得对象引用计数器的值。这个方法一般很少用,主要是帮助我们更好的理解当一个对象alloc,retain,以及release时,引用计数器是如何变化的。

4.内存分配,初始化

(1)而在OC中,分配空间和初始化是两个不同的方法。分配空间是通过类方法alloc处理,其中一会对所有的实例变量初始化,但是除了从NSObject继承的指针isa(is-a)之外的实例变量均被置为0(在运行时,isa的值可以标识新创建对象的类型)

(2)但是对实例变量而言,它们的初始值应该取决于构造函数中的参数,在Obj中,相关的初始化代码会被放在一种方法中,它们的名字通常以init开头。

(3)在OC中,创建对象严格分成了两步,空间分配和初始化。

  • alloc消息发送给类对象,init消息则发送给alloc出来的新对象。并且初始化时不可选的。alloc之后必须要跟着init。
  • 在OC中,初始化也是用方法来实现,并且,初始化方法一般会以init开头,而不是强制使用init开头,但强烈推荐初始化方法命名时遵循如下规则:初始化方法的方法名须以init开头。

(4)以下是对正确实现初始化方法的约束或者规范:

  • 名字以init开头
  • 方法需返回一个对象,便于之后的使用。
  • 方法体内,执行父类的初始化方法。
  • 方法体内要检查父类init方法的返回值。
  • 方法体内要正确的处理初始化中的错误,不管是本类的还是继承而来的,都要考虑到。

(5)对一个消息来讲有两个特殊的标识self和super, self是指当前的对象,super是指父类。在OC中,没有this关键字,进而取而代之的是self。其实self不是真正的关键字,每个方法中就会有一个隐藏的参数self,它的值是当前的对象。

(6)MRC下,程序中(alloc, copy, retain)必需要与release成对出现。

(7)处理初始化方法中的错误。

(8)当使用初始化方法初始化一个对象时,有一下三处潜在危险可能导致程序错误。

  • 方法的参数。在执行[super init]之前,如果方法的参数无效,那么初始化必须停止。
  • 执行父类的init方法。在执行父类的init方法时,有可能初始化不成功,此时我们应当放弃当前的初始化操作。
  • 类中特有实例变量的初始化。执行完父类的init,完成对继承自父类成员的初始化后,接着为特有的成员进行初始化,但是一旦资源分配失败,则初始化的方法需要终止。

5、内存回收

  1. 在OC中,实例方法dealloc用于释放对象中的实例变量指向的堆空间。
  2. OC的dealloc方法调用的时机,当对象的引用计数器的值为0时,dealloc方法会被自动执行以销毁对象。
  3. 来学习一种比较好的方法释放上述例子中实例变量origin所占的空间,之间origin所占的空间是在main函数中通过[[c origin] release]释放。 setOrigin:方法中会使origin引用计数器值减一,所以我们需要在Circle类中重写dealloc方法。
  4. 重写dealloc方法时,必须保证不仅要释放自己实例变量所占的空间,而且也要释放继承的变量所占的空间,为此,你可以通过向super发送dealloc来实现这个操作。
  5. dealloc函数的一个实现例子
1 - (void)dealloc
2 {
3     //调用set函数,先release,再置空。
4     self.origin = nil;  //引用计数值减1
5     [super dealloc]; //释放从父类继承下来的实例变量
6 }