Advanced Memory Management Programming Guide

关于iOS内存管理

该文章包含了一些ARC出现之前的内存管理方法,看完之后去看 《 Transitioning to ARC Release Notes 》可以更加完整地把知识结合起来。

内存管理可以理解为把内存这种有限的资源分配给一个application里面所有代码和数据的动作。

使用Objective-C的时候,内存管理实际上就是对app内使用的Object的关系的管理。因为iOS的内存管理是基于reference counting的。

目标是app占用的内存中不包含任何不需要的object。

该topic中列举的obj关系图原图:

memory_management_2x

两种管理方法:

  1. MRR(manual retain-release):自行跟踪object的内存使用情况并在恰当时候手动retain和release。retain-release就是reference counting模型的具体实现,由NSObject结合runtime环境实现。

  2. ARC(Automatic Reference Counting):基于MRR。ARC会自动在autorelease pool block内自动插入恰当的函数管理内存。该动作在编译时完成。

  3. GC (garbage collection) : ObjC 2.0 provided an optional conservative, generational garbage collector. 如果enable了garbage collection,那么runtime会将retain/release转成空操作,所有的ObjC对象都可以被回收,而C的对象可以用 __ strong标记符让GC回收;A zero-ing weak subsystem was also provided such that pointers marked as "__weak" are set to zero when the object (or more simply, GC memory) is collected.但是GC在iOS上performance不好,从来都没有enable过,而且在OS X 10.8上也会设置为deprecated,将来会从OSX中移除。**OC2.0有Garbage collection,但是iOS平台不提供。**

两种管理错误:

  1. Free/Overwriting需要使用的内存空间:导致crash/顺坏数据

  2. 没有Free已经不再需要使用的内存空间:将引起内存泄露

Memory Management Policy

在一个reference counting环境下的内存管理,是依靠NSObject protocol中提供的方法和一套方法名命名规范实现的。

内存管理基本法则Basic Rules

核心思想是这个:

The memory management model is based on object ownership

  • 你持有(own)自己创建的object,自己创建 指的是调用方法名开头是alloc \ new \ copy \ mutableCopy的方法,如allocnewObjectmutableCopy
  • 使用retain持有(own)指定object
  • 释放(relinquish)不再需要使用的object
  • 不能释放(relinquish)不是自己持有的object

栗子1:创建object时持有aPerson,使用完之后release掉

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}


栗子2:使用创建autorelease object,让其在autorelease pool内推迟release

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

栗子3:直接使用类方法stringWithFormat:方法创建object不算自己持有(是class object持有),可以直接作为函数返回值

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@",
                                 self.firstName, self.lastName];
    return string;
}

栗子4:错误栗子,使用alloc创建object之后自己持有(retain了一次)却直接返回,由于方法调用方(caller)无法补上这次落下的释放(release),将导致内存漏洞(memory leak)

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
                                         self.firstName, self.lastName];
    return string;
}

You Don’t Own Objects Returned by Reference

NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                        encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
    // Deal with error...
}
// ...
[string release];

上面的这种情况返回[ClassName ** or id *],你将不持有error object。

释放带retain属性的property

对于带有retain属性的property,需要在dealloc方法中释放:


@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end


@implementation Person
// ...
- (void)dealloc
    [_firstName release];
    [_lastName release];
    [super dealloc];
} 

@end

注意事项:

  1. Important:

  • Never invoke another object’s dealloc method directly.
  • You must invoke the superclass’s implementation at the end of your implementation.
  • You should not tie management of system resources to object lifetimes; see Don’t Use dealloc to Manage Scarce Resources (page 17).

When an application terminates, objects may not be sent a dealloc message. Because the process’s memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods.

Core Foundation Uses Similar but Different Rules

Cocoa框架和Core Foundation的内存管理有挺大不同的,如下面例子,你不持有myInstance:

MyClass *myInstance = [MyClass createInstance];

详情见:《Memory Management Programming Guide for Core Foundation

实际操作

使用Accessor Method来简化内存管理

栗子:

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

考虑上面的property count,你需要做的是在对property进行set value之后,确保它在被使用的时候不被释放。

因为对property的访问都是通过accessor method来完成的,把retain和release操作写到里面可以确保不会出错(retian和release匹配)和简化代码。

实现代码:

get方法

- (NSNumber *)count {
    return _count;
}

set方法

注意要释放之前的count object,顺序如下:

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

使用Accessor Method设置Property值

即使在Object Class的实现implement里,也要使用accessor method来访问property。

这里使用了[NSNumber alloc]来创建了一个NSNumber Object,set property之后要记得释放。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

如果使用[NSNumber numberWithInteger:0]这样的类方法创建Number object则不需要自己release。

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

一个不恰当的栗子

这样修改将不产生KVO通知也可能对日后维护产生影响。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
}

不要在init方法中使用Accosser Method

这个之前就知道了

init

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

initWithCount:

- initWithCount:(NSNumber *)startingCount {
    self = [super init];
    if (self) {
        _count = [startingCount copy];
    }
    return self;
}

delloc

- (void)dealloc {
    [_count release];
    [super dealloc];
}

使用弱引用防止Retain Cycles

两个object相互强引用会导致无法回收/释放二者的内存空间。

应该根据object之间的层级关系从上往下使用强引用,从下往上使用弱引用。

弱引用不会retain,引用对象有可能会被回收/释放。

其他情况自行考虑……

下面是原图(文档 --> 页面 --> 段落 模型的正确引用)

retaincycles_x2

注意这些情况下object会被释放

  1. object被从collection中删除
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
  1. parent object被释放
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.

防止这些情况的发生,应该在使用前retian

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];

不要试图使用MRR模型来管理临界资源

这一段好像没有必要记得样子

You should typically not manage scarce resources such as file descriptors, network connections, and buffers or caches in a dealloc method. In particular, you should not design classes so that dealloc will be invoked when you think it will be invoked. Invocation of dealloc might be delayed or sidestepped, either because of a bug or because of application tear-down.

Instead, if you have a class whose instances manage scarce resources, you should design your application such that you know when you no longer need the resources and can then tell the instance to “clean up” at that point. You would typically then release the instance, and dealloc would follow, but you will not suffer additional problems if it does not.

Problems may arise if you try to piggy-back resource management on top of dealloc. For example:

  1. Order dependencies on object graph tear-down.
    The object graph tear-down mechanism is inherently non-ordered. Although you might typically expect—and get—a particular order, you are introducing fragility. If an object is unexpectedly autoreleased rather than released for example, the tear-down order may change, which may lead to unexpected results.
  2. Non-reclamation of scarce resources.
    Memory leaks are bugs that should be fixed, but they are generally not immediately fatal. If scarce resources are not released when you expect them to be released, however, you may run into more serious problems. If your application runs out of file descriptors, for example, the user may not be able to save data.
  3. Cleanup logic being executed on the wrong thread.
    If an object is autoreleased at an unexpected time, it will be deallocated on whatever thread’s autorelease pool block it happens to be in. This can easily be fatal for resources that should only be touched from one thread.

Collections 持有自己包含的元素

add一个object到collection的时候,collection会retian该object。

使用remove方法/collection被释放 的时候会释放release该object。

又是两个差不多的栗子

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}
  NSMutableArray *array = <#Get a mutable array#>;
  NSUInteger i;
  // ...
  for (i = 0; i < 10; i++) {
      NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
      [array addObject:allocedNumber];
      [allocedNumber release];
  }

一些MRR规则

  • 自己创建object时retain+1
  • 调用object的retian方法时retain+1
  • 调用object的release方法时retain-1,如果是调用autolease,则在autorelease pool结束时才retain-1
  • 当object的retain count == 0时,它会被deallocate

object的retianCount方法可以返回当前的retian count值,该值在随着你程序的运行而变动。不要根据这个方法返回的值做release操作。

Autorelease Pool

Autorelease Pool提供一种自动地尽可能推迟地向object发送release消息的机制。

一般来说你不需要自己创建autorelease pool,新建项目时默认就有,是这样用的:

    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }

在autorelease pool结束的时候,所有autorelease命令都会执行retian-1

可以嵌套使用:

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
}
... }

这些情况下你需要自己创建autorelease pool:

  • 你的程序不是基于UI框架的,例如“command-line tool”(但是我现在用Xcode6.3创建这个类型的project也有autorelease pool耶……)
  • 循环里面创建了太多临时的object,占用了内存需要尽快释放
  • 创建并使用额外的线程(在thread入口就应该创建)

使用autorelease pool降低内存使用峰值

栗子

在loop中使用autorelease pool释放临时object

NSArray *urls = <# An array of file URLs #>;
  for (NSURL *url in urls) {
      @autoreleasepool {
          NSError *error;
           NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
          /* Process the string, creating and autoreleasing more objects. */
      }
}

如果loop中使用了autorelease pool,而你想要某个临时创建的object

在autorelease pool里面retain一次,不要让其在嵌套的pool内被释放,然后在loop结束之后再使用autorelease就可以

– (id)findMatchingObject:(id)anObject {
    id match;
    while (match == nil) {
        @autoreleasepool {
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
            if (match != nil) {
                [match retain]; /* Keep match around. */
            } }
}
    return [match autorelease];   /* Let match go and return it. */
}

Autorelease Pool Blocks and Threads

所有在Cocoa应用程序中创建的thread都有自己的autorelease pool,Foundation Only的程序就需要自己创建。

如果你的程序或者创建的线程是长时间运行并且会产生大量的autorelease object,请在适当的地方再嵌套autorelease pool来及时回收。

注意:如果你使用POSIX线程而不是Cocoa框架的线程,一定要确保Cocoa是在multithreading模式(使用NSThread的isMultiThreaded方法检查)。如果不是,可以在使用前创建一个NSThread objet然后马上释放,Cocoa会自动进入multithreading模式。

标签:ios, object-c, mrr, arc, ios内存管理