1. 前言
在上篇文章中 ARC系列一:ARC VS MRC 介绍了Objective-C中的引用计数内存管理方法,以及MRC和ARC之间的区别和联系。编译器开启了ARC,能帮我们自动管理对象的生存周期。那么此时需要问自己一些问题:哪些对象的生命周期可以被ARC管理和ARC如何管理对象的生命周期?
2. ARC可以管理的对象
ARC需要管理的对象可包括如下三种,可以把这些类型叫做Retainable object pointers。Retainable object pointers是retainable object pointer type的值。
- Block pointer
- Objective-C object pointers(比如NSString *, id, Class等)
- typedefs marked with
__attribute__((NSObject))(通常不推荐) 其它指针类型不在ARC的管理范围之内,比如int *和CFStringRef。
编译器支持的引用计数系统需要满足如下三个要求才能做到正确的管理对象的生命周期:
- 类型系统必须可靠的识别需要管理的对象,比如int*就不是ARC管理的对象,一个指向int的指针,其取值可能如下:指向数组首地址,指向数组内一个元素,或者指向一个int变量。但是retainable object pointer types的值永远不会是内部的。
int n = 1;
int arr[5] = {0,1,2,3,4};
int *ptr1 = arr;
int *ptr2 = arr + 2;
int *ptr3 = &n;
- 类型系统必须可靠地指示如何管理一个类型的对象,也就是如何增加或减少引用计数。
- 在调用者和被调用者之间,参数和返回值是否传递以及何时传递所有权必须继续约定。
3. Retain count semantics
一个retainable object pointer要么是一个空指针要么指向一个有效的对象。如果不是,那么这就是一个无效指针。开启ARC的情况下,对无效指针执行操作,属于未定义的行为。 ARC管理的对象,能够向其发送如下三个消息,来进行引用计数管理(也就是上面提到的如何增加或减少引用计数)。
- retain 无需参数,且返回指向该对象的指针
- release 无需参数,返回
void - autorelease, 无需参数,且返回指向该对象的指针 对于引用计数操作,需要注意如下:
- 先发送retain,在发送release。并不等同于没有发送上述两个消息,尽管最终的引用计数没变。这一种意识是这些方法可能不会引发异常。
- 如果对某个对象发送release,并且没有发送retain消息且仍使用该对象,这是未定义的行为。
- 向对象发送autorelease消息必须等同于,当一个autoreleasepool进行pop时,对该对象发送release的行为相同。它可能不会抛出异常。
- 如果指针为nil,向对象发送消息,则没有效果,相当于一句空语句。
4. Retainable object pointers作为操作数和参数
当Retainable object pointers仅仅简单的作为表达式的操作数时,并不对其发送retain或release消息。包括如下的情况:
- 作为函数或方法的参数
- 作为函数或方法的返回对象
- 从一个对象加载一个retainable object pointer(non-weak ownership) 但是除了上述三种情况,也有一些例外的情况,下面接着介绍。
4.1 Consumed parameters
当函数或方法的形参被__(attribute(ns_consumed))修饰时,表明调用者期望将传递给该形参的实参引用计数+1。需要注意的是该属性是方法或函数的一部分,并不是参数类型,它只控制参数的传递和接收方式。方法或函数的声明如下:
void foo(__attribute((ns_consumed)) id x);
- (void) foo: (id) __attribute((ns_consumed)) x;
我们可以进行如下测试:定义了两个函数其中,其中一个函数的参数用ns_consumed修饰,而另一个则不用,并且在main函数中分别调用两个函数。
void testConsumed(NSString * __attribute((ns_consumed)) str){
NSLog(@"consumed parameters");
}
void test(NSString *str){
NSLog(@"non-consumed parameters");
}
int main(int argc, const char * argv[]) {
NSString *str = [[NSString alloc] init];
testConsumed(str);
test(str);
return 0;
}
通过查看汇编代码,可以看出在调用testConsumed函数之前,对retainable object pointer执行了一次objc_retain方法。而在调用test函数之前并没有调用objc_retain函数。object_retain的声明如下,value为null或者指向一个有效的对象。它执行retain操作,就像对其发送了retain消息一样。如果value为null,则该函数无任何影响。
id objc_retain(id value);
可以通过查看
testConsumed函数查看其内部执行了NSLog语句,并且在最后执行了objc_storeStrong。
objc_storeStrong的源代码如下,其中object指向一个有效对象的指针,且object指向的该对象已为指针充分对齐。value值为null或指向有效对象的指针。从代码可以看出,objc_storeStrong可以将object指向的对象执行release操作,并且对value执行release操作。若value为nil,则该函数的功能就是将object指向的__strong对象发送release消息,那么在TestConsumed函数中,就是将参数str指向的对象发送release消息,这也和调用testConsumed函数之前的objc_retain对应。
id objc_storeStrong(id *object, id value) {
value = [value retain];
id oldValue = *object;
*object = value;
[oldValue release];
return value;
}
隐式self参数可能被标记为__attribute__((ns_consumes_self)),在init方法族中,已被视为隐式标记为有此属性。
4.2 Retained return values
当函数或方法的返回为retainable object pointer,可能被标记为retuning a retained value。表明调用者希望获得+1 retain count的所有权。通过将ns_returns_retained属性添加到函数或方法的声明中。该属性是方法或函数的类型的一部分,。如下面所示:
id foo(void) __attribute((ns_returns_retained));
- (id) foo __attribute((ns_returns_retained));
来看下面的测试代码:
NSString * testReturnConsumed(**void**) __attribute((ns_returns_retained)){
NSString *str = [[NSString alloc] init];
return str;
}
NSString * test(**void**){
NSString *str = [[NSString alloc] init];
return str;
}
int main(int argc, const char * argv[]) {
NSString *str2 = testReturnConsumed();
NSString *str1 = test();
return 0;
}
main函数的汇编代码如下:
通过查看汇编代码来查看
testReturnConsumed和test函数的不同。
在testReturnConsumed函数中,在其内部对str进行了retain操作,那么在main函数中,str1获取了testReturnConsumed生成的NSString对象的所有权。所以说在被该属性修饰的函数或方法能够将所有权传递至调用者,init,copy,alloc,mutablecopy和new等方法族都有此属性。也可以通过显式标记方法__attribute__((ns_returns_not_retained))来消除该属性的作用。
test函数的汇编代码如下:
注意到在test的汇编代码处,调用了
objc_autoreleaseReturnValue函数,那么在main函数中调用test函数之后,调用了一个名为# objc_retainAutoreleasedReturnValue的函数。
objc_autoreleaseReturnValue的声明如下:
id objc_autoreleaseReturnValue(id value);
id为null,则不执行任何操作。如果不是,且调用者执行了objc_retainAutoreleasedReturnValue方法,那么它尽最大努力将所有权保留至调用者中。否则,则对返回值发送autorelease消息。
objc_retainAutoreleasedReturnValue(id value)的声明如下:
id objc_retainAutoreleasedReturnValue(id value);
id为null,则不执行任何操作。如果不是,且该返回值是作为objc_retainAutoreleasedReturnValue参数,那么尝试去获取objc_autoreleaseReturnValue参数的所有权。如果不是,则会执行objc_retain函数。
可以看出__attribute((ns_returns_retained))属性可以将所有权从被调用者传递至被调用者。而不加此属性的函数或方法,可能会传递所有权,视情况而定。
4.3 Bridged casts
bridged casts能够将对象的的生命周期移除或添加至ARC的控制之下,它们是一种C-Style的转换。主要是三个关键字:
- (__bridge T) op,将op转换成目标类型。如果T是retainable object pointer type,那么op必须是non-retainable object pointer type。反之,如果T是non-retainable object pointer type,则op必须是retainable object pointer type。其它形式的转换是无效的,且所有权并未转移,ARC不会插入retain操作。
- (__bridge_retained T) op,op必须是retainable object pointer type,T必须是non-retainable object pointer type。ARC对值执行retain操作,但编译器可能会有优化,并且该值的接受者负责执行类似release操作。
- (__bridge_transfer T) op, op必须是non-retainable object pointer type,T必须是retainable object pointer type。ARC会在表达式末尾release该值,编译器对此可能有优化。 首先使用(__bridge T) op,在NSString *和 CFStringref之间进行转换。代码如下:
int main(int argc, const char *argv[]){
NSString *nsString = [NSString stringWithString:@"NSString"];
CFStringRef cfString = CFSTR("CFString");
(__bridge NSString *) cfString;
return 0;
}
其汇编代码如下图,可以看出ARC并未执行额外的retain操作。
再来看下(__bridge_retained T) op,代码如下:
int main(int argc, const char *argv[]){
NSString *nsString = [NSString stringWithString:@"NSString"];
CFStringRef cfString = CFSTR("CFString");
(__bridge_retained CFStringRef) nsString;
return 0;
}
其汇编代码如下,可以看出(__bridge_retained T),对操作数执行了一次retain操作。但需要对其执行CFrelease操作,以防止内存泄露。
最后再来看下(__bridge_transfer T) op,代码如下:
int main(int argc, const char *argv[]){
NSString *nsString = [NSString stringWithString:@"NSString"];
CFStringRef cfString = CFSTR("CFString");
(__bridge_transfer NSString *) cfString;
return 0;
}
汇编代码如下,可以看出(__bridge_transfer T) op,将所有权转移至NSString,然后生命周期由ARC控制,汇编代码中会有一个objc_release函数的调用。
**4.4 Restrictions
4.4.1 Conversion of retainable object pointers
通常来说,隐式或显式子将retainable object pointer type转换成任何non-retainable type以及将non-retainable type转换成retainable object pointer type是错误的,比如一个objective-c对象指针不应该转换成void *,但是可以转换成intptr_t,因为此类转换不会转移所有权。在需要的时刻可以使用上面提到的bridge_cast来进行non-retainable type 和 retainable object pointer type之间的转换。
4.4.2 Conversion to retainable object pointer type of expressions with know semantics
由上面可知,不使用bridge cast将non-retainable type转换成retainable object pointer type是错误的,但有三种表达式情况例外。在此之前需要介绍C retainable pointer types。 C retainable pointer type指向void或者一个strcut or class type(指的是CFFoundation Type,ARC并不管理此类型对象的生命周期)。
- known retained 有如下几种种情况,代码如下。
NSString *str1 = @"hello world"; //objecvie-c字符串常量
NSString *str2 = nil; //null pointer
- known unretained