Source/NSURLCache+TNLAdditions.m (164 lines of code) (raw):

// // NSURLCache+TNLAdditions.m // TwitterNetworkLayer // // Created on 8/12/14. // Copyright © 2020 Twitter. All rights reserved. // #include <objc/message.h> #import "NSURLCache+TNLAdditions.h" #import "TNLRequestConfiguration_Project.h" NS_ASSUME_NONNULL_BEGIN @interface TNLImpotentURLCache : NSURLCache @end @interface TNLSharedURLCacheProxy : NSProxy @end // Demux interface for one NSURLSession to support multiple NSURLCache instances. // Unfortunately, NSURLSession accesses the underlying `CFURLCache` of the provide // NSURLCache which circumvents the Objective-C interface some undetermined reason. // This doesn't impact cache entry retrieval or storage, so things behave as expected - // NSURLSession just might be establishing assumptions that don't hold when a proxy is used. @interface TNLURLCacheDemuxProxy : TNLSharedURLCacheProxy @end NSURLCache *TNLGetURLCacheDemuxProxy() { static TNLURLCacheDemuxProxy *sProxy; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sProxy = [TNLURLCacheDemuxProxy alloc]; }); return (id)sProxy; } @implementation NSURLCache (TNLAdditions) + (NSURLCache *)tnl_impotentURLCache { static TNLImpotentURLCache *sCache; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sCache = [[TNLImpotentURLCache alloc] init]; }); return sCache; } + (NSURLCache *)tnl_sharedURLCacheProxy { static TNLSharedURLCacheProxy *sProxy; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sProxy = [TNLSharedURLCacheProxy alloc]; }); return (id)sProxy; } @end @implementation TNLSharedURLCacheProxy - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [[NSURLCache sharedURLCache] methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:[NSURLCache sharedURLCache]]; } // `NSURLCache` objects have an underlying `CFURLCache` which is accessed via // accessor (same with its cf type id). // // Using `methodSignatureForSelector:` will work, but is handled // via an exception handler when the _CFURLCache accessor is not strictly a 1:1 match to the a // selector signature. // // This is fine normally, however when debugging with exception breakpoints this can // be frustrating. // // So, to avoid that problem (and the exception overhead), we will insert `_CFURLCache` method // in our cache proxy and so we can call directly to the shared NSURLCache. - (CFTypeRef)_CFURLCache { CFTypeRef (*_CFURLCacheMethodFun)(id, SEL) = (CFTypeRef (*)(id, SEL))objc_msgSend; const CFTypeRef v = _CFURLCacheMethodFun([NSURLCache sharedURLCache], _cmd); return v; } @end @implementation TNLURLCacheDemuxProxy // Legacy - (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(request); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { return [cache cachedResponseForRequest:request]; } return nil; } - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(request); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { [cache storeCachedResponse:cachedResponse forRequest:request]; } } - (void)removeCachedResponseForRequest:(NSURLRequest *)request { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(request); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { [cache removeCachedResponseForRequest:request]; } } // Modern - API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0)) - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forDataTask:(NSURLSessionDataTask *)dataTask { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(dataTask.originalRequest); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { [cache storeCachedResponse:cachedResponse forDataTask:dataTask]; } } - (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^) (NSCachedURLResponse * _Nullable cachedResponse))completionHandler { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(dataTask.originalRequest); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { [cache getCachedResponseForDataTask:dataTask completionHandler:completionHandler]; } else { completionHandler(nil); } } - (void)removeCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask { TNLRequestConfiguration *config = TNLRequestConfigurationGetAssociatedWithRequest(dataTask.originalRequest); NSURLCache *cache = TNLUnwrappedURLCache(config.URLCache); if (cache) { [cache removeCachedResponseForDataTask:dataTask]; } } @end @implementation TNLImpotentURLCache - (id)init { // Don't call super! // This object is impotent. // Calling super would create an expensive NSURLCache. return self; } - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity directoryURL:(nullable NSURL *)url { return [self init]; } #if !TARGET_OS_MACCATALYST - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(nullable NSString *)path { return [self init]; } #endif - (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { return nil; } - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request { } - (void)removeCachedResponseForRequest:(NSURLRequest *)request { } - (void)removeAllCachedResponses { } - (NSUInteger)memoryCapacity { return 0; } - (NSUInteger)diskCapacity { return 0; } - (void)setMemoryCapacity:(NSUInteger)memoryCapacity { } - (void)setDiskCapacity:(NSUInteger)diskCapacity { } - (NSUInteger)currentMemoryUsage { return 0; } - (NSUInteger)currentDiskUsage { return 0; } @end NS_ASSUME_NONNULL_END