CustomHTTPProtocol/Core Code/QNSURLSessionDemux.m
/* |
File: QNSURLSessionDemux.m |
Abstract: A general class to demux NSURLSession delegate callbacks. |
Version: 1.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
*/ |
#import "QNSURLSessionDemux.h" |
@interface QNSURLSessionDemuxTaskInfo : NSObject |
- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes; |
@property (atomic, strong, readonly ) NSURLSessionDataTask * task; |
@property (atomic, strong, readonly ) id<NSURLSessionDataDelegate> 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<NSURLSessionDataDelegate> delegate; |
@property (atomic, strong, readwrite) NSThread * thread; |
@end |
@implementation QNSURLSessionDemuxTaskInfo |
- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)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 () <NSURLSessionDataDelegate> |
@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<NSURLSessionDataDelegate>)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 |
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-08-20