ObjectiveC的内存管理2从MRC到ARC

17次阅读

共计 3461 个字符,预计需要花费 9 分钟才能阅读完成。

罗里吧嗦颠三倒四,单纯的个人笔记。

MRC

引用计数上一篇已经有大概讲过。在 Objective- C 里,每个继承自 NSObject 的对象都会记录自身的引用计数,一番加加减减之后,变成 0 就会释放掉。
MRC 是 Mannul Reference Counting 的缩写,意思也很简单,这番加加减减都靠手动管理的意思。

使用时的基本原则是:管好自己。每个对象,引用别的对象时加了几次计数最终到了不用的时候就要减几次。不能多也不能少。
这样就聚焦了很多。

导致引用计数增加的操作,显式的 retain 不多说,剩下的就是四个关键字:alloc、new(以及 new 开头的方法)、copy、mutableCopy,使用这四个关键字得到的对象,就算你自己加的引用计数,回头要自己减掉。

引用计数减少的操作就是 release 了。

AutoRelease

AutoReleasePool 是个自动释放池,加入其中的对象会延迟到 Pool“结束”时释放。
在 MRC 中,你可以显式创建一个 NSAutoReleasePool,显式地将一个对象加入进去,并显式释放 AutoReleasePool:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
NSObject *obj = [[[NSObject alloc] init] autorelease];
[pool release];//[pool drain];

在 ARC 中,需要通过特殊的语法:

@autoreleasepool {// Code benefitting from a local autorelease pool.}

默认地,每个 Runloop 迭代开始时会创建一个 AutoReleasePool,Runloop 迭代结束时释放。也就是说,当我们没有显式创建 AutoReleasePool 时,autorelease 的对象会在 Runloop 迭代结束时释放。
当我们显式地创建 AutoReleasePool 时,其释放时机是我们决定的(显式调用 release/drain 或 block 结束)。

主动使用 AutoReleasePool 的目的通常是为了控制内存峰值。比如,我有一个大循环,每次循环都会创建比较大的 autorelease 的临时对象。如果不显式释放,这些临时对象会在整个循环结束后才一起释放,期间可能造成内存占用过高。这种情况下就可以在每次循环内声明 autoreleasepool,保证临时对象不会堆积。

ARC

ARC 为我们自动地做了很多,屏蔽了很多细节,理论上来说,我们只需要关注对象间的所有权关系即可。
上层机制虽然简单,涉及到的细节还是有很多的,可喜可贺的是,ARC 是有标准文档的。简直 …

ARC 提供的变量修饰符有以下几个:

  • __strong
  • __weak
  • __unsafe_unretaied
  • __autoreleasing

提供的属性修饰符有:

  • assign 对应的所有权类型是 __unsafe_unretained。
  • copy 对应的所有权类型是 __strong。
  • retain 对应的所有权类型是 __strong。
  • strong 对应的所有权类型是 __strong。
  • unsafe_unretained 对应的所有权类型是__unsafe_unretained。
  • weak 对应的所有权类型是 __weak。

(基本类型默认是 assign,对象类型默认是 strong)

__strong

强引用,不多说了。注意声明变量和属性时若未加说明,默认是强引用。

__weak

弱引用。当对象被释放时,weak 修饰的变量会被置为 nil。
仔细想想,想要实现这个特性,所有的 weak 变量都需要放到一个全局的 map 里,实现成本还是比较高的。

__unsafe_unretained

不做任何额外操作。

__autoreleasing

__autoreleasing 标记的变量等价于调用 autorelease 方法

想到一个小问题:对于函数返回值,ARC 是怎么知道要不要加引用计数呢?
看这几行代码:

- (void)testMethod
{NSObject *obj = [NSObject new];
    NSArray *array = [NSArray array];
    // do something
}

在 ARC 中,obj 和 array 用完之后都会被自动释放,但是细想之下其实有不少细节。
要知道,[NSObject new]返回的对象引用计数是有 + 1 的,而 [NSArray array] 并不是。
这俩玩意儿引用计数差了 1,ARC 是怎么知道谁要多释放一次的?
在 MRC 中,我们知道 new 出来的 obj 需要手动释放,而 array 就不需要,是通过方法的关键词进行判断。
但是方法中的关键词不应该是某种约定吗?ARC 难道也会去看一个方法是否是以 new 开头?
看了文档之后发现 …ARC 还真是这么做的 …

Methods in the alloc, copy, init, mutableCopy, and new families are implicitly marked __attribute__((ns_returns_retained)).

回想起来,在 MRC 时代,这些关键词应该是止步于约定的。而 ARC 或许是为了平滑过渡,把曾经的约定变成了语法规范,emmm,感觉这么搞不是很好啊。

Block 内存管理

在 ARC 之后,内存管理的问题减少了很多,但仍然有一些遗留。其中最重要的一部分就是 Block 相关的内存管理。
参考 Objective- C 中 Block 的循环引用问题

Bridge

Core Foundation 框架 (CoreFoundation.framework) 是一组 C 语言接口,它们为 iOS 应用程序提供基本数据管理和服务功能。
Objective- C 对象和 CF 对象是可以直接转换的:

CFStringRef aCFString = (CFStringRef)aNSString;
NSString *aNSString = (NSString *)aCFString;

然而 ARC 是不支持 CF 对象的内存管理的,这就需要我们关注谁来释放转换后的对象的问题。

在 MRC 中,相对来说比较简单,CFRelease 和 release 方法是等效的,择机使用即可。
这里主要关注 ARC 下的情况。
根据不同需求,有 3 种转换方式

  • __bridge(不改变对象所有权)
  • __bridge_retained 或者 CFBridgingRetain()(解除 ARC 所有权)
  • __bridge_transfer 或者 CFBridgingRelease()(给予 ARC 所有权)

1. __bridge

__bridge 不改变对象所有权。

  1. OC 对象转 CF,仍由 ARC 管理,不需要显式释放
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge CFStringRef)aNSString;
// do something
  1. CF 对象转 OC,仍由 CF 管理,需要显式释放
CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);
NSString *aNSString = (__bridge NSString *)aCFString;
// do something
CFRelease(aCFString);

2. __bridge_retained

所有权给 CF,因此要调用 CFRelease 显式释放

NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;
// do something
CFRelease(aCFString); 

3. __bridge_transfer

所有权给 ARC,因此无需手动管理

CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);
NSString *aNSString = (__bridge_transfer NSString *)aCFString;
// do something

正文完
 0