| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- /*
- File: JAHPQNSURLSessionDemux.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 "JAHPQNSURLSessionDemux.h"
- @interface JAHPQNSURLSessionDemuxTaskInfo : 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 JAHPQNSURLSessionDemuxTaskInfo ()
- @property (atomic, strong, readwrite) id<NSURLSessionDataDelegate> delegate;
- @property (atomic, strong, readwrite) NSThread * thread;
- @end
- @implementation JAHPQNSURLSessionDemuxTaskInfo
- - (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 JAHPQNSURLSessionDemux () <NSURLSessionDataDelegate>
- @property (atomic, strong, readonly ) NSMutableDictionary * taskInfoByTaskID; // keys NSURLSessionTask taskIdentifier, values are SessionManager
- @property (atomic, strong, readonly ) NSOperationQueue * sessionDelegateQueue;
- @end
- @implementation JAHPQNSURLSessionDemux
- - (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:@"JAHPQNSURLSessionDemux"];
-
- self->_session = [NSURLSession sessionWithConfiguration:self->_configuration delegate:self delegateQueue:self->_sessionDelegateQueue];
- self->_session.sessionDescription = @"JAHPQNSURLSessionDemux";
- }
- return self;
- }
- - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes
- {
- NSURLSessionDataTask * task;
- JAHPQNSURLSessionDemuxTaskInfo * 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 = [[JAHPQNSURLSessionDemuxTaskInfo alloc] initWithTask:task delegate:delegate modes:modes];
-
- @synchronized (self) {
- self.taskInfoByTaskID[@(task.taskIdentifier)] = taskInfo;
- }
-
- return task;
- }
- - (JAHPQNSURLSessionDemuxTaskInfo *)taskInfoForTask:(NSURLSessionTask *)task
- {
- // rdar://21484589
- // This is called from each NSURLSessionTaskDelegate
- // method implemented by JAHPQNSURLSessionDemux,
- // which are called from the NSURLSession delegateQueue,
- // which is a different thread than self.clientThread.
- // It is possible that -stopLoading was called on self.clientThread
- // just before this method if so, we cannot make the
- // assertion (task != nil).
- // rdar://24042545
- // URLSession:task:didCompleteWithError: should always
- // be the last NSURLSessionTaskDelegate method called.
- // Although, it is possible that this is not always
- // the case. Therefore we cannot make the assertion
- // (self.taskInfoByTaskID[@(task.taskIdentifier)] != nil)
- // because [self.taskInfoByTaskID removeObjectForKey:@(taskInfo.task.taskIdentifier)]
- // is called in URLSession:task:didCompleteWithError:.
- JAHPQNSURLSessionDemuxTaskInfo * result;
- @synchronized (self) {
- result = self.taskInfoByTaskID[@(task.taskIdentifier)];
- }
- return result;
- }
- - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:task];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:task];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:task];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:task];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * 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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:dataTask];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:dataTask];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:dataTask];
- if (taskInfo && [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
- {
- JAHPQNSURLSessionDemuxTaskInfo * taskInfo;
-
- taskInfo = [self taskInfoForTask:dataTask];
- if (taskInfo && [taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
- [taskInfo performBlock:^{
- [taskInfo.delegate URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
- }];
- } else {
- completionHandler(proposedResponse);
- }
- }
- @end
|