问题描述
- (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
)。
在第二段代码中,如果您在块之前和块中打印出nil
(responseError
)的地址,您将看到它们是不同的,表明它已被移动