Objective-C:内存管理

Page content

基于引用计数的内存管理

内存管理方式

引用计数

iOS中使用retainCount来管理Objc对象的内存,通过retain来增加引用计数,release减少引用计数,retainCount检查引用计数,retainCount为0,该对象将被释放。

垃圾回收

在MacOS的开发中,有垃圾回收的内存管理机制,不过在MacOS10.10左右,该机制被废弃,改用引用计数。

autorelease

  1. 发送autorelease消息,表示将对象加入到autoreleasePool中;
  2. 除了alloc、new、copy、mutableCopy等关键字的类构建方法(便利构造方法),会对对象自动发送autorelease消息,将其加入autoreleasePool中;
  3. 被加入autoreleasePool中的对象,将在autoreleasePool销毁时,被发送release消息。

autoreleasePool

NSAutoreleasePool

NSAutoreleasePool是MRC时代的旧的API,只能在MRC中使用,其用法如下:

~~~objective-c
NSAutorelaesePool * pool = [NSAutorelaesePool new];
    // code here
[pool release];
~~~

NSAutoreleasePool 在循环中要注意不能被break,否则对象无法释放。

@autoreleasepool{}

@autoreleasepool{} 是新的API,既可以在MRC中使用,也可以在ARC中使用,而且不必担心break,性能也比NSAutoreleasePool高:

~~~objective-c
@autoreleasepool {
    // code here
}
~~~

autoreleasePool的创建和释放时机:

runloop_04

autoreleasePool的使用时机:

  1. 程序不基于UIFramework(命令行项目);
  2. 循环内创建大量临时对象或者长期执行的任务中有大量临时对象;
  3. 新线程开始执行时。

setter与dealloc

setter

MRC一个错误的setter示范:

~~~objective-c
- (void)setValue:(id)value  {   
    [_value release];
    _value = [value retain];
}
~~~

当_value 和 value是同一个对象是,这种写法会导致value提前释放。 安全的setter写法:

~~~objective-c
- (void)setValue:(id)value  {
    id temp = _value
    [value retain];
    _value = value;
    [temp release];
}
// 或者
- (void)setValue:(id)value  { 
    if (_value != value) {
        [_value release];
        _value = [value retain];
    }
}
~~~

dealloc

~~~objective-c
@interface MyBuffer:NSObject {
    NSString * fileName;
    NSView * fileMonitorView;
    char * buffer;
    FILE * fp;
}
~~~

MRC 中 除了调用父类的dealloc方法之外,不允许显示的调用dealloc:

~~~objective-c
- (void)dealloc {
    [fileName release];
    [fileMonitorView release];
    if (buffer != NULL) {
        free(buffer);
    }
    if (fp != NULL) {
        fclose(fp);
    }
    [super dealloc];
}
~~~

ARC 中 不允许调用父类的dealloc:

~~~objective-c
- (void)dealloc {
    if (buffer != NULL) {
        free(buffer);
    }
    if (fp != NULL) {
        fclose(fp);
    }
}
~~~

weak, unsafe_unretained, __autoreleasing

__weak

  1. __weak 是ARC为了解决retainCycle而引入的;
  2. 弱引用是通过存储一个对象的指针创建的,且不保留对象;
  3. nonowning relationship。自动nil化,weak修饰的对象被释放,weak变量自动置nil,不会变成野指针。

使用时机:

  1. 已经对该对象有一个强引用,没必要再强引用的时候;
  2. 解决循环引用的时候;

__unsafe_unretained

非nil化的弱指针,更强调所有权不变更。

~~~objective-c
Person * p1 = [[Person alloc] intiWithName:@"alicy"];
__unsafe_unretained Person * temP = p1;
Person * p2 = temP;
temP = nil; // 所有权不变更
~~~

类似Swift中的unOwned: 假定银行系统,一张信用卡(CreditCard)对应一个客户(Customer),但一个客户不一定有一张信用卡: Customer -> unsafe_unretained -> CreditCard; CreditCard -> strong -> Customer; 当该客户将信用卡从此银行注销时:

~~~objective-c
// CreditCard 的 dealloc:
- (void)dealloc {
    [self.customer setCreditCard:nil];
}
~~~

__autoreleasing

__autoreleasing的根本目的是获取一个延迟释放的对象。 比如NSString的初始化方法中:

~~~objective-c
- (nullable instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;

~~~

ARC的编译器会自动为函数的二重指针变量加上一个__autoreleasing修饰符:

~~~objective-c
- (nullable instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(__autoreleasing NSError **)error;
~~~

相应的,我们使用这个函数的时候:

~~~objective-c
NSError * error = nil;
NSString * str = [[NSString alloc] initWithContentsOfFile:@"file" encoding:NSUTF8StringEncoding error:&error];
~~~

ARC的编译器会转为:

~~~objective-c
NSError __strong * error = nil;
NSError __autoreleasing * tempError = error;
NSString * str = [[NSString alloc] initWithContentsOfFile:@"file" encoding:NSUTF8StringEncoding error:&tempError];
error = tempError;
~~~

当我们传递一个为初始化的对象的引用到一个方法中,并在此方法中实例化该对象,且希望返回时这个对象被自动加入到autoreleasePool的时候,我们就可以使用__autoreleasing的关键字。