ARC中的生命周期限定符
- __strong is the default. An object remains “alive” as long as there is a strong pointer to it.
- __weak specifies a reference that does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object.
- __unsafe_unretained specifies a reference that does not keep the referenced object alive and is not set to nil when there are no strong references to the object. If the object it references is deallocated, the pointer is left dangling.
- __autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.
__strong是默认的生命周期限定符,当所修饰的对象被一个强指针引用时,该对象将在内存中保持活跃状态
__weak表明了一种引用关系,这种引用关系并不能保证所修饰的对象存活下来。当没有强引用指向这个对象的时候,弱引用变量将会置为nil(变为空指针)
__unsafe_unretained表明了一种引用关系,这种引用关系同样不能保证所修饰的对象存活下来。但是当没有强引用指向这个对象的时候,引用变量并不会被置为nil。所以当引用指向的对象被销毁时,指针将成为野指针。(首先unretain即不会改变引用计数,unsafe指的是该引用变量有可能成为野指针)
__autoreleasing用来修饰函数声明中的双指针参数(id * 或者 **)
__autoreleasing的使用有下面一个例子帮助理解:
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the error.
// ...
编译器会对变量error
做如下操作
NSError * __strong e;
而对函数performOperationWithError:
接收的是一个对象引用的引用,也就是说函数声明应该是这样的
-(BOOL)performOperationWithError:(NSError **)error;
编译器会有如下操作
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
对例子中的第2行代码的理解是这样的:函数需要接收的参数是__autoreleasing修饰的,而实际接收到的参数是__strong的,所以编译器会进行如下的操作
NSError * __strong error;
NSError * __autoreleasing tmp = error;// 临时变量
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;// 将临时变量的值再回传
为了加深对__autoreleaseing的理解,再举一个例子
屏幕快照 2017-07-14 下午2.58.35.png
出现了野指针调用的问题:这是因为函数形参是__autoreleasing修饰的变量,而巧合的是dict的枚举遍历方法中,有一个内部添加的autoreleasepool,所以代码相当于
- (BOOL)validateDictionary:(NSDictionary *)dict error:(NSError * __autoreleasing *)error {
...
for (NSUInteger idx=0; idx<dict.allKeys.count && !stop; idx++) {
@autoreleasepool {
...
if (error) {
*error = [NSError errorWithDomain:@"" code:0 userInfo:nil];
}
}// 在这个地方error就被提前释放了
}
...
}
所以一个__autoreleasing修饰的NSError对象在离开autoreleasepool时被释放了,这样外部访问error对象时,就会出现EXC_BAD_ACCESS错误。如果要解决这个问题,可以在遍历前创建一个临时的NSError,然后在遍历完成后将临时变量的值再回传给传入的error
- (BOOL)validateDictionary:(NSDictionary *)dict error:(NSError **)error {
__block BOOL isValid = YES;
__block NSError *err;
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
*stop = YES; isValid = NO;
if (err) {
err = [NSError errorWithDomain:@"" code:0 userInfo:nil];
}
}];
*error = err;
return isValid;
}