Thread with runloop exit accient

@interface XXXXOperation : NSOperation

@property (nonatomic, strong) NSPort *port;

@end


@implementation XXXXOperation


- (void)main

{

@autoreleasepool {

if (self.pluginInfo.pluginId > 0 && self.pluginInfo.downloadUrl.absoluteString.length > 0)

{

self.currentThread = [NSThread currentThread];

NSString *pluginId = self.pluginInfo.pluginId.length > 0 ? self.pluginInfo.pluginId : @"";

NSNumber *pluginVersion = @(self.pluginInfo.pluginVersion);

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];

configuration.timeoutIntervalForRequest = 20;

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

manager.completionQueue = [RNPluginDownloadHelper getCompletionQueue];


NSMutableSet *acceptableContentTypes = [[NSMutableSet alloc] initWithSet:[(AFHTTPResponseSerializer *)manager.responseSerializer acceptableContentTypes]];

[acceptableContentTypes addObject:@"application/zip"];


NSMutableIndexSet *acceptableStatusCodes = [[NSMutableIndexSet alloc] initWithIndexSet:[(AFHTTPResponseSerializer *)manager.responseSerializer acceptableStatusCodes]];

[acceptableStatusCodes addIndexesInRange:NSMakeRange(300, 200)];


AFHTTPResponseSerializer *httpRespSerializer = [AFHTTPResponseSerializer serializer];

httpRespSerializer.acceptableContentTypes = acceptableContentTypes;

httpRespSerializer.acceptableStatusCodes = acceptableStatusCodes;

manager.responseSerializer = httpRespSerializer;

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.pluginInfo.downloadUrl];

request.timeoutInterval = 20;

self.currentLength = [self fileLengthForPath:[self tempFilePath]];

NSString *range = [NSString stringWithFormat:@"bytes=%llu-", self.currentLength];

[request setValue:range forHTTPHeaderField:@"Range"];

self.tryToUnzipWhen416Occour = NO;


__weak typeof(self) wself = self;

NSURLSessionDataTask *downloadTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {


[wself.fileHandle closeFile];

wself.fileHandle = nil;

NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;

if (httpResp.statusCode == 206 && error == nil)

{

NSURL *fileURL = [NSURL fileURLWithPath:[wself tempFilePath]];

[wself performSelector:@selector(handlePackageZipFileWithFilePath:) onThread:[wself currentThread] withObject:fileURL waitUntilDone:NO];

}

else if (wself.tryToUnzipWhen416Occour && httpResp.statusCode == 416 && error.code == NSURLErrorCancelled )

{

NSURL *fileURL = [NSURL fileURLWithPath:[wself tempFilePath]];

[wself performSelector:@selector(handlePackageZipFileWithFilePath:) onThread:[wself currentThread] withObject:fileURL waitUntilDone:NO];

}

else

{

[wself performSelector:@selector(finishThread) onThread:[wself currentThread] withObject:nil waitUntilDone:NO];

}

}];

[manager setDataTaskDidReceiveResponseBlock:^NSURLSessionResponseDisposition(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response) {

NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;

if (httpResp.statusCode == 206)

{

wself.fileLength = response.expectedContentLength + wself.currentLength;

NSString *filePath = [wself tempFilePath];

NSFileManager *fileManager = [NSFileManager defaultManager];

if (![fileManager fileExistsAtPath:filePath])

{

[fileManager createFileAtPath:filePath contents:nil attributes:nil];

}

wself.fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];

return NSURLSessionResponseAllow;

}

else if (httpResp.statusCode == 416)

{

NSString *contentRange = [[httpResp allHeaderFields] objectForKey:@"content-range"];

if ([contentRange hasPrefix:@"bytes"])

{

NSRange slashRange = [contentRange rangeOfString:@"/"];

if (slashRange.location != NSNotFound)

{

NSString *strLength = [contentRange substringFromIndex:(slashRange.location + 1)];

long long length = strLength.longLongValue;

if (length == wself.currentLength)

{

wself.fileLength = length;


wself.tryToUnzipWhen416Occour = YES;

return NSURLSessionResponseCancel;

}

}

}

}

return NSURLSessionResponseAllow;

}];

[manager setDataTaskDidReceiveDataBlock:^(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) {


[wself.fileHandle seekToEndOfFile];

[wself.fileHandle writeData:data];


wself.currentLength += data.length;

}];

[downloadTask resume];

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

self.port = [NSMachPort port];

[runloop addPort:self.port forMode:NSDefaultRunLoopMode];

[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

}

}

}


- (NSString *)tempFilePath

{

NSString *fileName = [NSString stringWithFormat:@"%@_%@_%@", self.pluginInfo.pluginId, @(self.pluginInfo.pluginVersion), self.pluginInfo.pluginFileMD5];

NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];


return filePath;

}


XXXXOperation is subclass of NSOperation, in the main function, I implement a file downloader with AFNetwork, sometimes it crash at the the completion block(the blod line code), the crash reason is "- [NSURL initFileURLWithPath:] nil string parameter":

if (httpResp.statusCode == 206 && error == nil)

{

NSURL *fileURL = [NSURL fileURLWithPath:[wself tempFilePath]];

[wself performSelector:@selector(handlePackageZipFileWithFilePath:) onThread:[wself currentThread] withObject:fileURL waitUntilDone:NO];

}

I try to re-produce the exception, but not success!

The reason is the thread exit when the completion excuting, but I don't know why?


Somebody can explain it? Thanks!

Replies

-runMode:beforeDate:
is documented to run the run loop once and then return. As it’s hard to know in advance all possible run loop sources and when they might fire, you can’t just call it and assume that your source has fired. Rather, you should put that call in a loop and, each time around the loop, check whether things are actually done.

Having said that, I have serious concerns about this whole approach. You’re turning an asynchronous task into a synchronous one (what I call synthetic synchronous) and that’s rarely a good idea and definitely not a good idea when it comes to networking. Each

NSOperation
you have running will tie up a thread (which is a relatively expensive object) that’s just twiddling its thumbs waiting for the underlying asynchronous networking task to complete.

A better approach here is to use an asynchronous (previously known as concurrent) operation. This allows you to keep you

NSOperation
structure but avoid having the operation queue’s thread stuck in
-main
. You can see an example of this in the LinkedImageFetcher sample code.

Note This code really needs to be updated to remove run loops from its core and instead be based on some abstract serialisation mechanism. That’d allow subclasses to use different serialisation mechanisms as needed. I’ve played around with this idea myself and it works well, but I don’t have a production-ready version of that code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"