从 URLSession 传递 NSError 的正确方法
Correct way to pass NSError from URLSession
我有网络层 class,它有 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 完成范围内有一些值(当我初始化它时),但在我的控制器完成块中它总是 nil。我不知道为什么。
这是一个异步方法。不要间接设置响应错误。完全按照 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
的堆栈帧已经结束,我们捕获的指针 ( called responseError
in -networkRequestWithError:withCompletion:
,它是指向堆栈上原始 __block
变量 responseError
的指针)是一个悬挂指针。分配给该指针指向的东西是未定义的行为,实际上可能会覆盖堆栈上一些不相关的变量。另一方面,堆上的实际 __block
变量 responseError
并没有改变,仍然保持它原来持有的值 (nil
).
在你的第二段代码中,如果你在块之前和块内打印出responseError
(&responseError
)的地址,你会看到它们是不同的,证明它被感动了。
我有网络层 class,它有 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 完成范围内有一些值(当我初始化它时),但在我的控制器完成块中它总是 nil。我不知道为什么。
这是一个异步方法。不要间接设置响应错误。完全按照 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
的堆栈帧已经结束,我们捕获的指针 ( called responseError
in -networkRequestWithError:withCompletion:
,它是指向堆栈上原始 __block
变量 responseError
的指针)是一个悬挂指针。分配给该指针指向的东西是未定义的行为,实际上可能会覆盖堆栈上一些不相关的变量。另一方面,堆上的实际 __block
变量 responseError
并没有改变,仍然保持它原来持有的值 (nil
).
在你的第二段代码中,如果你在块之前和块内打印出responseError
(&responseError
)的地址,你会看到它们是不同的,证明它被感动了。