Objective-C的内存管理(2)——从MRC到ARC

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

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
    原文作者:二师兄
    原文地址: https://segmentfault.com/a/1190000019296924
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞