从URLSession传递NSError的正确方法

问题描述

我有网络层类,该类具有带有URL请求的方法。看起来像这样:

- (void)networkRequestWithError:(NSError *__strong *)responseError
                  andCompletion:(void (^)(NSData*))completion
{
    NSURL *url = ...
                        
    NSURLSessionDataTask *dataTask = [NSURLSession.sharedSession
                                      dataTaskWithURL:url
                                      completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
        // *responseError = error; for real errors
        *responseError = [NSError errorWithDomain:@"1"
                                             code:1
                                         userInfo:@{}];        
        completion(data);
    }];
    
    [dataTask resume];
}
@end

我在控制器中创建了此网络层的实例,并且想在完成块中处理错误

    __block NSError *responseError; 
    
    [self.networkService networkRequestWithError:&responseError
                                  withCompletion:^(NSData*) {
                
        if (responseError != nil) {
            NSLog(@"%@",responseError.localizedDescription);
        } else {
            //Some action with data. No matter
        }
    }];

问题::responseError在dataTask完成范围中有一些值(当我初始化它时),但是在我的控制器的完成块中,它始终为零。我不知道为什么。

解决方法

这是一个异步方法。不要通过间接设置响应错误。严格执行dataTaskWithURL的操作:将其作为另一个参数传递到完成块中。

 - (void)networkRequestWithCompletion:(void (^)(NSData*,NSError*))completion
,

要获得有关代码为何无法正常工作的技术说明,必须对__block变量进行移动这一事实,因此在某一点获得指向它的指针并不意味着该指针稍后仍指向变量。在获取__block变量的地址时必须小心。

作为一种优化,块从堆栈开始,而__block变量也从堆栈的特殊结构开始。当某些代码需要存储该块以便在当前作用域之后使用时,它将复制该块,这会导致该块(如果尚未存在)移动到堆中(因为堆栈中的内容可能不会超出当前调用的范围) )。如果将块移至堆中,则该块捕获的所有__block变量也将移至堆中(同样,因为它必须位于堆中才能超过当前调用的时间)。

通常,当您获取或设置__block变量时,编译器会将其转换为幕后的几次指针查找,以访问__block变量的实际位置,因此无论如何透明地工作如果它在堆栈或堆上。但是,如果像&responseError那样使用它的地址来获取指针,则只能按当前变量的形式获取它的地址,但是如果移动该变量,它就不会更新。

因此,您遇到的情况是第二段代码中的__block变量responseError从堆栈中开始,当您执行&responseError时,将获得一个指针到其在堆栈上的位置。将该指针传递到-networkRequestWithError:withCompletion:中,然后将该指针(也称为responseError)捕获到它创建的块中,并传递到-[NSURLSession dataTaskWithURL:completionHandler:]中。

同时,第二个代码段中的__block变量responseError被传递到-networkRequestWithError:withCompletion:的块和该块(称为completion ),然后由您传入-[NSURLSession dataTaskWithURL:completionHandler:]的块捕获。那是一个异步操作,因此他们将块复制,该块复制第一个块并将块移动,并将__block变量responseError复制到堆。

-[NSURLSession dataTaskWithURL:completionHandler:]的完成处理程序被异步调用时,创建原始__block变量responseError的堆栈帧已经结束,并且我们捕获的指针(称为{ responseError中的{1}}是悬空指针,它是指向堆栈上原始-networkRequestWithError:withCompletion:变量__block的指针。分配给该指针指向的事物是未定义的行为,并且实际上可能会覆盖栈上一些不相关的变量。另一方面,堆上的实际responseError变量__block不会更改,仍然保留其最初保留的值(responseError)。

在第二段代码中,如果您在块之前和块中打印出nilresponseError)的地址,您将看到它们是不同的,表明它已被移动

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...