@@ -337,13 +337,13 @@ - (void)_close
337337 _metadata.blockedMs = _blockedElapsed * 1000 ;
338338
339339 // Manually make the willCacheResponse callback when needed
340- [self _storeCacheResponse ];
340+ [self _storeCacheResponseCompletion: ^{
341+ [_client URLProtocolDidFinishLoading: _protocol];
341342
342- [_client URLProtocolDidFinishLoading: _protocol];
343-
344- if (_delegate && [_delegate respondsToSelector: @selector (streamClosed: )]) {
345- [_delegate streamClosed: self ];
346- }
343+ if (_delegate && [_delegate respondsToSelector: @selector (streamClosed: )]) {
344+ [_delegate streamClosed: self ];
345+ }
346+ }];
347347}
348348
349349- (bool )closed
@@ -597,19 +597,18 @@ - (void)didReceiveResponse
597597 didReceiveResponse: _response
598598 cacheStoragePolicy: _cachePolicy];
599599
600- // Prepare for manual caching, if needed
601- NSURLCache *cache = _protocol.associatedSession .configuration .URLCache ;
602- if (_cachePolicy != NSURLCacheStorageNotAllowed && // cacheable?
603- [self _shouldUseManualCaching ] && // hack needed and NSURLSession used?
604- cache != nil && // cache configured (NSURLSession-specific)?
605- _local) { // not a push request?
606-
607- // The NSURLCache has a heuristic to limit the max size of items based on the capacity of the
608- // cache. This is our attempt to mimic that behavior and prevent unlimited buffering of large
609- // responses. These numbers were found by manually experimenting and are only approximate.
610- // See SPDYNSURLCachingTest testResponseNearItemCacheSize_DoesUseCache.
611- _cacheDataBuffer = [[NSMutableData alloc ] init ];
612- _cacheMaxItemSize = MAX (cache.memoryCapacity * 0.05 , cache.diskCapacity * 0.01 );
600+ // Prepare for manual caching, if allowed and needed
601+ if (_cachePolicy != NSURLCacheStorageNotAllowed && [self _shouldUseManualCaching ]) {
602+ // Cache configured and not a push stream?
603+ NSURLCache *cache = _protocol.associatedSession .configuration .URLCache ;
604+ if (cache != nil && _local) {
605+ // The NSURLCache has a heuristic to limit the max size of items based on the capacity of the
606+ // cache. This is our attempt to mimic that behavior and prevent unlimited buffering of large
607+ // responses. These numbers were found by manually experimenting and are only approximate.
608+ // See SPDYNSURLCachingTest testResponseNearItemCacheSize_DoesUseCache.
609+ _cacheDataBuffer = [[NSMutableData alloc ] init ];
610+ _cacheMaxItemSize = MAX (cache.memoryCapacity * 0.05 , cache.diskCapacity * 0.05 );
611+ }
613612 }
614613}
615614
@@ -741,7 +740,7 @@ - (void)_didLoadDataChunk:(NSData *)data
741740
742741 if (_cacheDataBuffer) {
743742 NSUInteger bufferSize = _cacheDataBuffer.length + data.length ;
744- if (bufferSize < _cacheMaxItemSize) {
743+ if (bufferSize <= _cacheMaxItemSize) {
745744 [_cacheDataBuffer appendData: data];
746745 } else {
747746 // Throw away anything already buffered, it's going to be too big
@@ -755,19 +754,33 @@ - (void)_didLoadDataChunk:(NSData *)data
755754// ourselves, is turned on.
756755- (bool )_shouldUseManualCaching
757756{
758- NSInteger osVersion = 0 ;
759- NSProcessInfo *processInfo = [NSProcessInfo processInfo ];
760- if ([processInfo respondsToSelector: @selector (operatingSystemVersion )]) {
761- osVersion = [processInfo operatingSystemVersion ].majorVersion ;
762- }
757+ static BOOL osVersionRequiresManualStoreToCache;
758+ static dispatch_once_t once;
759+ dispatch_once (&once, ^{
760+ NSProcessInfo *processInfo = [NSProcessInfo processInfo ];
761+ if ([processInfo respondsToSelector: @selector (operatingSystemVersion )]) {
762+ NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion ];
763+ #if TARGET_OS_MAC
764+ // 10.10 and earlier
765+ osVersionRequiresManualStoreToCache = osVersion.majorVersion < 10 || (osVersion.majorVersion == 10 && osVersion.minorVersion <= 10 );
766+ #else
767+ // iOS 8 and earlier
768+ osVersionRequiresManualStoreToCache = osVersion.majorVersion <= 8 ;
769+ #endif
770+ } else {
771+ osVersionRequiresManualStoreToCache = YES ;
772+ }
773+ });
763774
764- // iOS 8 and earlier and using NSURLSession
765- return (osVersion <= 8 && _protocol.associatedSession != nil && _protocol.associatedSessionTask != nil );
775+ return (osVersionRequiresManualStoreToCache && _protocol.associatedSession != nil && _protocol.associatedSessionTask != nil );
766776}
767777
768- - (void )_storeCacheResponse
778+ - (void )_storeCacheResponseCompletion : ( dispatch_block_t ) completion
769779{
770780 if (_cacheDataBuffer == nil ) {
781+ if (completion) {
782+ completion ();
783+ }
771784 return ;
772785 }
773786
@@ -776,32 +789,51 @@ - (void)_storeCacheResponse
776789 data: _cacheDataBuffer
777790 userInfo: nil
778791 storagePolicy: _cachePolicy];
779- NSURLCache *cache = _protocol.associatedSession . configuration . URLCache ;
792+ NSURLSession *session = _protocol.associatedSession ;
780793 NSURLSessionDataTask *dataTask = (NSURLSessionDataTask *)_protocol.associatedSessionTask ;
794+ NSURLCache *cache = session.configuration .URLCache ;
795+
796+ // Already validated these items are not nil, just being safe
797+ NSParameterAssert (session);
798+ NSParameterAssert (cache);
799+ NSParameterAssert (dataTask);
800+
801+ void (^finalCompletion)(NSCachedURLResponse *) = ^(NSCachedURLResponse *finalCachedResponse){
802+ if (finalCachedResponse) {
803+ if ([cache respondsToSelector: @selector (storeCachedResponse:forDataTask: )]) {
804+ [cache storeCachedResponse: finalCachedResponse forDataTask: dataTask];
805+ } else {
806+ [cache storeCachedResponse: finalCachedResponse forRequest: _request];
807+ }
808+ }
809+
810+ if (completion) {
811+ completion ();
812+ }
813+ };
781814
782815 // Make "official" willCacheResponse callback to app, bypassing the NSURL loading system.
783- id <NSURLSessionDataDelegate > delegate = (id )_protocol. associatedSession .delegate ;
816+ id <NSURLSessionDataDelegate > delegate = (id )session .delegate ;
784817 if ([delegate respondsToSelector: @selector (URLSession:dataTask:willCacheResponse:completionHandler: )]) {
785- NSOperationQueue *queue = _protocol.associatedSession .delegateQueue ;
818+ CFRunLoopRef clientRunLoop = CFRunLoopGetCurrent ();
819+ CFRetain (clientRunLoop);
820+
821+ NSOperationQueue *queue = session.delegateQueue ;
786822 [(queue) ?: [NSOperationQueue mainQueue ] addOperationWithBlock: ^{
787- [delegate URLSession: _protocol.associatedSession dataTask: dataTask willCacheResponse: cachedResponse completionHandler: ^(NSCachedURLResponse * cachedResponse) {
788- // This block may execute asynchronously at any time. No need to come back to the SPDY/NSURL thread
789- if (cachedResponse) {
790- if ([cache respondsToSelector: @selector ( storeCachedResponse:forDataTask: )]) {
791- [cache storeCachedResponse: cachedResponse forDataTask: dataTask] ;
792- } else {
793- [cache storeCachedResponse: cachedResponse forRequest: _request] ;
794- }
795- }
823+ [delegate URLSession: session dataTask: dataTask willCacheResponse: cachedResponse completionHandler: ^(NSCachedURLResponse * cachedResponse) {
824+
825+ // Hop back to the NSURL thread and finish up
826+ CFRunLoopPerformBlock (clientRunLoop, kCFRunLoopDefaultMode , ^ {
827+ finalCompletion ( cachedResponse) ;
828+ });
829+ CFRunLoopWakeUp (clientRunLoop) ;
830+ CFRelease (clientRunLoop);
831+
796832 }];
797833 }];
798834 } else {
799835 // willCacheResponse delegate not implemented. Default behavior is to cache.
800- if ([cache respondsToSelector: @selector (storeCachedResponse:forDataTask: )]) {
801- [cache storeCachedResponse: cachedResponse forDataTask: dataTask];
802- } else {
803- [cache storeCachedResponse: cachedResponse forRequest: _request];
804- }
836+ finalCompletion (cachedResponse);
805837 }
806838}
807839
0 commit comments