// // Example // man // // Created by man 11/11/2018. // Copyright © 2020 man. All rights reserved. // #import "_QNSURLSessionDemux.h" @interface _QNSURLSessionDemuxTaskInfo : NSObject - (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id)delegate modes:(NSArray *)modes; @property (atomic, strong, readonly ) NSURLSessionDataTask * task; @property (atomic, strong, readonly ) id delegate; @property (atomic, strong, readonly ) NSThread * thread; @property (atomic, copy, readonly ) NSArray * modes; - (void)performBlock:(dispatch_block_t)block; - (void)invalidate; @end @interface _QNSURLSessionDemuxTaskInfo () @property (atomic, strong, readwrite) id delegate; @property (atomic, strong, readwrite) NSThread * thread; @end @implementation _QNSURLSessionDemuxTaskInfo - (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id)delegate modes:(NSArray *)modes { //assert(task != nil); //assert(delegate != nil); //assert(modes != nil); self = [super init]; if (self != nil) { self->_task = task; self->_delegate = delegate; self->_thread = [NSThread currentThread]; self->_modes = [modes copy]; } return self; } - (void)performBlock:(dispatch_block_t)block { //assert(self.delegate != nil); //assert(self.thread != nil); [self performSelector:@selector(performBlockOnClientThread:) onThread:self.thread withObject:[block copy] waitUntilDone:NO modes:self.modes]; } - (void)performBlockOnClientThread:(dispatch_block_t)block { //assert([NSThread currentThread] == self.thread); block(); } - (void)invalidate { self.delegate = nil; self.thread = nil; } @end @interface _QNSURLSessionDemux () @property (atomic, strong, readonly ) NSMutableDictionary * taskInfoByTaskID; // keys NSURLSessionTask taskIdentifier, values are SessionManager @property (atomic, strong, readonly ) NSOperationQueue * sessionDelegateQueue; @end @implementation _QNSURLSessionDemux - (instancetype)init { return [self initWithConfiguration:nil]; } - (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration { // configuration may be nil self = [super init]; if (self != nil) { if (configuration == nil) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self->_configuration = [configuration copy]; self->_taskInfoByTaskID = [[NSMutableDictionary alloc] init]; self->_sessionDelegateQueue = [[NSOperationQueue alloc] init]; [self->_sessionDelegateQueue setMaxConcurrentOperationCount:1]; [self->_sessionDelegateQueue setName:@"_QNSURLSessionDemux"]; self->_session = [NSURLSession sessionWithConfiguration:self->_configuration delegate:self delegateQueue:self->_sessionDelegateQueue]; self->_session.sessionDescription = @"_QNSURLSessionDemux"; } return self; } - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id)delegate modes:(NSArray *)modes { NSURLSessionDataTask * task; _QNSURLSessionDemuxTaskInfo * taskInfo; //assert(request != nil); //assert(delegate != nil); // modes may be nil if ([modes count] == 0) { modes = @[ NSDefaultRunLoopMode ]; } task = [self.session dataTaskWithRequest:request]; //assert(task != nil); taskInfo = [[_QNSURLSessionDemuxTaskInfo alloc] initWithTask:task delegate:delegate modes:modes]; @synchronized (self) { self.taskInfoByTaskID[@(task.taskIdentifier)] = taskInfo; } return task; } - (_QNSURLSessionDemuxTaskInfo *)taskInfoForTask:(NSURLSessionTask *)task { _QNSURLSessionDemuxTaskInfo * result; //assert(task != nil); @synchronized (self) { result = self.taskInfoByTaskID[@(task.taskIdentifier)]; //assert(result != nil); } return result; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:task]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler]; }]; } else { completionHandler(newRequest); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:task]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; }]; } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:task]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:needNewBodyStream:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session task:task needNewBodyStream:completionHandler]; }]; } else { completionHandler(nil); } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:task]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend]; }]; } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:task]; // This is our last delegate callback so we remove our task info record. @synchronized (self) { [self.taskInfoByTaskID removeObjectForKey:@(taskInfo.task.taskIdentifier)]; } // Call the delegate if required. In that case we invalidate the task info on the client thread // after calling the delegate, otherwise the client thread side of the -performBlock: code can // find itself with an invalidated task info. if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session task:task didCompleteWithError:error]; [taskInfo invalidate]; }]; } else { [taskInfo invalidate]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:dataTask]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; }]; } else { completionHandler(NSURLSessionResponseAllow); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:dataTask]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didBecomeDownloadTask:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask]; }]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:dataTask]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveData:data]; }]; } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { _QNSURLSessionDemuxTaskInfo * taskInfo; taskInfo = [self taskInfoForTask:dataTask]; if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) { [taskInfo performBlock:^{ [taskInfo.delegate URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; }]; } else { completionHandler(proposedResponse); } } @end