原文链接:https://www.debugger.wiki/article/html/1570775013637318
methodWillSetError会去异步设置error的值,然后另外一个地方在error设置后去访问error的值。
实际上现在新版的Xcode已经会对
*error = [NSError errorWithDomain:@"domain" code:1 userInfo:nil];
进行警告
Block captures an autoreleasing out-parameter, which may result in use-after-free bugs
那么这个警告是什么意思呢?如何解决这个问题?
实际上这个方法
- (void)methodWillSetError:(NSError **)error group:(dispatch_group_t)group
的error,虽然没有明确指定内存的修饰符(strong, weak, autoreleasing,但是如果你直接定义NSError **error的临时变量,在arc下xcode会编译失败,要求你明确指定内存关系)但是编译器会默认转成NSError * __autoreleasing*,而在block中捕获一个__autoreleasing的out-parameter是很容易造成错误的。
解决方法:
1、形参 NSError**error 显式指定 __strong 修饰, __strong修饰后,xcode警告已经消失。
2、block内部引用的对象,下边示例代码可以证明,即使使用__block修饰对象,block获取到的值也是block代码被加载时对象的值,之后对象值再改变,__block对象的值也不会跟着变的。
为什么会变和不变: 栈内存归系统管,局部变量指针在栈中,方法结束被释放很正常,指针防堆中,就不会被释放了,block被程序加载的时刻,block对象的指针地址被替换为一个堆地址,所以它在方法结束不会被系统释放。
NSInteger i = 1;
__block NSInteger blockI = i;
printf("\n改变前的i:%ld",(long)i);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
printf("\n改变后的i:%ld",(long)blockI);
});
});
i = 2;
(这段代码有些问题,晚些修复,不影响下边的方法)
methodWillSetError 方法传入的error是双层指针,对*error赋值改变的是error对应的内存地址,局部变量在当前方法结束时被释放,所以已经没有指向error对象地址的指针了,那赋值完成后局部变量error的内存地址会立即被覆盖吗?如果不会,是否可以通知读取error的内存地址直接获取error的值?
没有指针,那就不用指针,所以实现流程是:
对象 => 内存地址 => 对象
核心代码:
NSError *error = [NSError errorWithDomain:@"com.test.domain" code:100 userInfo:nil];
NSInteger *path;
NSInteger path2 = (NSInteger)&error;
path = (NSInteger *)path2;
NSError *b ;
memcpy((void*)&b, path, sizeof(NSError *));
NSLog(@"%@",b);
考题实现的完整代码:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface TestObj : NSObject
@end
@implementation TestObj
- (void)methodWillSetError:(__strong NSError **)error group:(dispatch_group_t)group {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
*error = [NSError errorWithDomain:@"domain2" code:1 userInfo:@{@"test2":@(303)}];
NSLog(@"%@", *error);
dispatch_group_leave(group);
});
}
@end
void testBlockAndAutoReleasePool() {
NSLog(@"Hello, World!");
NSError *error = nil;
NSLog(@"%p", &error);
TestObj *testObj = [TestObj new];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[testObj methodWillSetError:&error group:group];
NSInteger *path;
NSInteger path2 = (NSInteger)&error;
path = (NSInteger *)path2;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"%@", error);
NSError *b ;
memcpy((void*)&b, path, sizeof(NSError *));
NSLog(@"%@",b);
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
testBlockAndAutoReleasePool();
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
return 0;
}