一道题考你对__autoreleasing和__block的理解

原文链接: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 
#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;
}

你可能感兴趣的:(一道题考你对__autoreleasing和__block的理解)