NSError *__autoreleasing* error 中的 __autoreleasing 修饰符

NSError *autoreleasing* error 中的 autoreleasing 修饰符

我们在进行 iOS 的日常开发时,经常会遇到这样的情况:当调用某一个方法时,除了关心方法返回的值时,还需要获取更多的信息。

比较多的情况是,获取一个返回的对象BOOL 值,比较顺利的情况是返回了有效的对象YES,但是难免事情不如人意,可能就返回了 nilNO,这种情况下,我们就会希望知道为什么出现异常。比较多的一种处理方式是,传入一个 NSError 对象的指针,将方法处理的异常通过这个 NSError 对象传出来。例如 NSString 的方法:

stringWithContentsOfURL:encoding:error:

在遇到这样的调用时,我经常会疑惑,这里为什么使用了 __autoreleasing 修饰符?接下来我们就来讲讲这里面的原因。(如果你已经知道了原因,可以直接跳过这篇文章了😂)

内存管理的原则

首先,我们需要明确的是几条内存管理中的原则,这很重要。

  • 自己生成的对象,自己所持有。
  • 非自己生成的对象,自己也能持有。
  • 不需要自己持有的对象是释放。
  • 非自己持有的对象无法释放。

使用 alloc, new, copy, mutableCopy 开头的方法意味着自己生成的对象只有自己持有。其他的方法取得的对象为非自己生成。

__autoreleasing

在使用 MRC 时,我们可以对对象调用 autorelease 方法,将对象注册到最近的 autoreleasepool 中,但是到了 ARC,autorelease 方法连同 retain, release 方法,一起变禁止直接调用了。那我们要怎样来使用 autoreleasepool,将对象注册到其中呢?答案就是 __autoreleasing 修饰符。对象赋值给附有 __autoreleasing 修饰符的变量等价于在 MRC 下调用对象的 autorelease 方法,即对象被注册到 autoreleasepool。

NSError *__autoreleasing*

现在可以了进入正题了。
假设我们有一个方法 - (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;, 该方法接受一个 NSError 对象的指针作为参数,返回一个 BOOL 值。使用时如下所示:

1
2
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];

再来假设,方法的内部实习如下:

1
2
3
4
5
6
7
8
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error
{
// 方法执行发生错误

*error = [NSError errorWithDomain:MyAppDomain code:errorCode userInfo:nil];

return NO;
}

我们在来看看,之前提到的内存管理原则,方法 - (BOOL)performOperationWithError:(NSError *__autoreleasing *)error; 不是以 allocnewcopy 或者 mutableCopy 开头的,所以 error 取得的应该是非自己生成并持有的对象。

赋值对象指针时,所有权修饰符必须一致

赋值对象指针时,所有权修饰符必须一致,也就是说:

1
2
NSError *error = nil; // 默认修饰符是 __strong
NSError *__strong* pError = &error;

同样的:

1
2
3
4
5
NSError __weak *error = nil; 
NSError *__weak* pError = &error;

NSError __unsafe_unretain *error = nil;
NSError *__unsafe_unretain* pError = &error;

你可能注意到了,我们在前面的例子中声明的是 NSError *error = nil; 这里使用的默认修饰符 __strong, 而在方法 - (BOOL)performOperationWithError:(NSError *__autoreleasing *)error; 中需要的是 __autoreleasing,但这样的使用,为什么没有报错呢?实际上,是编译器帮了我们,编译器会自动将源码转化成如下形式:

1
2
3
4
NSError *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;

严格遵守内存管理原则

其实我们也可以将方法中的参数修饰符改为 __strong- (BOOL)performOperationWithError:(NSError *__strong *)error; 对象不注册到 autoreleaspool 也能传递。但是,这违背了内存管理的原则,只有作为alloc, new, copy, mutableCopy 开头的方法,能够自己生成并持有。

为了在使用参数取得对象时,贯彻内存管理的原则,我们要将参数声明为附有 __autorealsing 修饰符。


[1] 参考:《Obejctive-C 高级编程 iOS 与 OS X 多线程和内存管理》
0%