I handle some old code, it runs well, but now crash only on ios 14
here is the demo
static NSData *DownloadWithRange(NSURL *URL, NSError *__autoreleasing *error) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.timeoutInterval = 10.0;
__block NSData *data = nil;
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
NSURLSession *URLSession = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [URLSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable taskData, NSURLResponse * _Nullable response, NSError * _Nullable taskError) {
data = taskData;
if (error)
*error = taskError;
dispatch_semaphore_signal(sema);
}];
[task resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return data;
}
- (IBAction)crashButton:(id)sender {
NSURL *url = [NSURL URLWithString:@"http://error"];
NSError * error = nil;
NSData *compressedData = DownloadWithRange(url, &error);
NSLog(@"error is %@",error);
}
before DownloadWithRange returned, the taskError memory(NSURLError) has released
on ios 13, it don't crash
it's really weird
The zombie diagnostics are letting you know that the autorelease object is getting deallocated by the time the data is returned. You should not be instantiating an autorelease object in one thread and trying to have a pool on a separate thread manage that. As the docs say:
While the problem might be manifesting itself differently in iOS 14, I do not believe that this pattern was ever acceptable/prudent.
If you're going to use this pattern (which I wouldn't advise; see below), you can solve this problem by copying the error object on the calling thread before returning:
FWIW, this technique of using semaphore to make asynchronous method behave synchronously is generally considered an anti-pattern. And you definitely should never use this pattern from the main thread.
I would suggest adopting asynchronous patterns:
And
Note, in addition to adopting asynchronous completion block pattern, a few other observations:
If you’re going to create a new
NSURLSession
for each request, make sure to invalidate it or else you will leak memory.I’m returning the
NSURLSessionTask
, which some callers may want in case they might want to cancel the request (e.g. if the view in question is dismissed or a new request must be generated). But as shown above, you don’t need to use thisNSURLSessionTask
reference if you don’t want.I'm dispatching the completion handler back to the main queue. That is not strictly necessary, but it is often a useful convenience.