This gist is the glue code for the pluggable downloader code to use Twitter Network Layer for iOS for networking in with Twitter Image Pipeline for iOS
TIP supports plugging in any network layer, so feel free to hack on this gist to your own needs.
This gist is the glue code for the pluggable downloader code to use Twitter Network Layer for iOS for networking in with Twitter Image Pipeline for iOS
TIP supports plugging in any network layer, so feel free to hack on this gist to your own needs.
// | |
// TIPXImageFetchDownload.h | |
// Extras for TIP w/ TNL | |
// | |
@import TwitterImagePipeline; | |
@class TNLRequestOperationQueue; | |
// Image Fetch Download backed with TNL | |
@interface TIPXImageFetchDownload : NSObject <TIPImageFetchDownload> | |
+ (TNLRequestOperationQueue *)imageRequestOperationQueue; | |
@end | |
// Download provider that cannot be stubbed - use in Production | |
@interface TIPXImageFetchDownloadProvider : NSObject <TIPImageFetchDownloadProvider> | |
@end | |
// Download provider that can be stubbed - use for Testing | |
@interface TIPXImageFetchDownloadProviderWithStubbing : NSObject <TIPImageFetchDownloadProviderWithStubbingSupport> | |
@property (nonatomic, readwrite) BOOL downloadStubbingEnabled; | |
@end |
// | |
// TIPXImageFetchDownload.m | |
// Extras for TIP w/ TNL | |
// | |
#import "TIPXImageFetchDownload.h" | |
@import TwitterNetworkLayer; | |
@interface TIPXImageFetchDownload (Config) | |
+ (TNLRequestConfiguration *)requestConfiguration:(BOOL)usePseudoProtocol; | |
@end | |
@interface TIPXImageFetchDownload () <TNLRequestDelegate> | |
@property (nonatomic) TNLRequestOperation* operation; | |
@end | |
@implementation TIPXImageFetchDownload | |
{ | |
NSOperationQueuePriority _priority; | |
dispatch_queue_t _queue; | |
struct { | |
BOOL stub:1; | |
BOOL cancelled:1; | |
} _flags; | |
} | |
@synthesize context = _context; | |
- (instancetype)initWithContext:(id<TIPImageFetchDownloadContext>)context | |
stubbingEnabled:(BOOL)stub | |
{ | |
if (self = [super init]) { | |
_context = context; | |
_queue = context.downloadQueue; | |
_flags.stub = !!stub; | |
} | |
return self; | |
} | |
- (void)start | |
{ | |
if (_operation || _flags.cancelled) { | |
return; | |
} | |
id<TIPImageFetchDownloadContext> context = self.context; | |
TNLRequestConfiguration *config = [[self class] requestConfiguration:_flags.stub]; | |
_operation = [TNLRequestOperation operationWithRequest:context.originalRequest | |
configuration:config | |
delegate:self]; | |
_operation.priority = TNLConvertQueuePriorityToTNLPriority(_priority); | |
TNLRequestOperationQueue *queue = [[self class] imageRequestOperationQueue]; | |
NSAssert(_operation != nil); | |
NSAssert(queue != nil); | |
[queue enqueueRequestOperation:_operation]; | |
} | |
- (void)cancelWithDescription:(NSString *)cancelDescription | |
{ | |
if (_flags.cancelled) { | |
return; | |
} | |
_flags.cancelled = 1; | |
if (_operation) { | |
[_operation cancelWithSource:cancelDescription]; | |
} else { | |
// Early cancellation | |
id<TIPImageFetchDownloadClient> client = _context.client; | |
if (client) { | |
dispatch_async(_queue, ^{ | |
NSError *cancelError = TNLErrorFromCancelSource(cancelDescription, nil); | |
[client imageFetchDownload:self | |
didCompleteWithError:cancelError]; | |
}); | |
} | |
} | |
} | |
#pragma mark Properties | |
- (void)discardContext | |
{ | |
_context = nil; | |
} | |
- (void)setPriority:(NSOperationQueuePriority)priority | |
{ | |
_priority = priority; | |
_operation.priority = TNLConvertQueuePriorityToTNLPriority(priority); | |
} | |
- (NSOperationQueuePriority)priority | |
{ | |
return _priority; | |
} | |
- (id)downloadMetrics | |
{ | |
return _operation.response.metrics; | |
} | |
- (NSURLRequest *)finalURLRequest | |
{ | |
return _operation.response.info.finalURLRequest; | |
} | |
#pragma mark Delegate | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
hydrateRequest:(id<TNLRequest>)request | |
completion:(TNLRequestHydrateCompletionBlock)complete | |
{ | |
NSAssert([request isKindOfClass:[NSURLRequest class]]); | |
id<TIPImageFetchDownloadContext> context = self.context; | |
[context.client imageFetchDownload:self | |
hydrateRequest:(NSURLRequest *)request | |
completion:^(NSError *error) { | |
complete(context.hydratedRequest, error); | |
}]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
authorizeURLRequest:(NSURLRequest *)URLRequest | |
completion:(TNLAuthorizeCompletionBlock)completion | |
{ | |
id<TIPImageFetchDownloadContext> context = self.context; | |
[context.client imageFetchDownload:self | |
authorizeRequest:URLRequest | |
completion:^(NSError *error) { | |
completion(context.authorization, error); | |
}]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
didTransitionFromState:(TNLRequestOperationState)oldState | |
toState:(TNLRequestOperationState)newState | |
{ | |
if (newState == TNLRequestOperationStatePreparingRequest && op.attemptCount == 0) { | |
[self.context.client imageFetchDownloadDidStart:self]; | |
} | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
didReceiveURLResponse:(NSHTTPURLResponse *)response | |
{ | |
[self.context.client imageFetchDownload:self didReceiveURLResponse:response]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
didReceiveData:(NSData *)data | |
{ | |
[self.context.client imageFetchDownload:self didReceiveData:data]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
willStartRetryFromResponse:(nonnull TNLResponse *)responseBeforeRetry | |
policyProvider:(nonnull id<TNLRequestRetryPolicyProvider>)policyProvider | |
afterDelay:(NSTimeInterval)delay | |
{ | |
[self.context.client imageFetchDownloadWillRetry:self]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
didStartRetryFromResponse:(TNLResponse *)responseBeforeRetry | |
policyProvider:(id<TNLRequestRetryPolicyProvider>)policyProvider | |
{ | |
// start for any retry attempts | |
[self.context.client imageFetchDownloadDidStart:self]; | |
} | |
- (void)tnl_requestOperation:(TNLRequestOperation *)op | |
didCompleteWithResponse:(TNLResponse *)response | |
{ | |
[self.context.client imageFetchDownload:self | |
didCompleteWithError:response.operationError]; | |
} | |
- (dispatch_queue_t)tnl_completionQueueForRequestOperation:(TNLRequestOperation *)op | |
{ | |
return _queue; | |
} | |
- (dispatch_queue_t)tnl_delegateQueueForRequestOperation:(TNLRequestOperation *)op | |
{ | |
return _queue; | |
} | |
#pragma mark Private | |
+ (TNLRequestConfiguration *)requestConfiguration:(BOOL)usePseudoProtocol | |
{ | |
static TNLRequestConfiguration *sConfig; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
TNLMutableRequestConfiguration *mConfig = [TNLMutableRequestConfiguration configurationWithExpectedAnatomy:TNLRequestAnatomySmallRequestVeryLargeResponse]; | |
mConfig.URLCache = nil; | |
mConfig.cookieStorage = nil; | |
mConfig.cookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever; | |
mConfig.shouldSetCookies = NO; | |
mConfig.responseDataConsumptionMode = TNLResponseDataConsumptionModeChunkToDelegateCallback; | |
mConfig.retryPolicyProvider = nil; // plug in a retry policy here to make things even better | |
TNLRequestProtocolOptions protocolOptions = TNLRequestProtocolOptionsDefault; | |
protocolOptions &= ~TNLRequestProtocolOptionPseudo; | |
mConfig.protocolOptions = protocolOptions; | |
sConfig = [mConfig copy]; | |
}); | |
if (usePseudoProtocol) { | |
TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; | |
TNLRequestProtocolOptions protocolOptions = mConfig.protocolOptions; | |
protocolOptions |= TNLRequestProtocolOptionPseudo; | |
mConfig.protocolOptions = protocolOptions; | |
return mConfig; | |
} | |
return sConfig; | |
} | |
+ (TNLRequestOperationQueue *)imageRequestOperationQueue | |
{ | |
static TNLRequestOperationQueue *sQueue; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
sQueue = [[TNLRequestOperationQueue alloc] initWithIdentifier:@"com.twitter.image.pipeline.tnl.queue"]; | |
}); | |
return sQueue; | |
} | |
@end | |
@implementation TIPXImageFetchDownloadProvider | |
- (id<TIPImageFetchDownload>)imageFetchDownloadWithContext:(id<TIPImageFetchDownloadContext>)context | |
{ | |
return [[TIPXImageFetchDownload alloc] initWithContext:context | |
stubbingEnabled:NO]; | |
} | |
@end | |
@implementation TIPXImageFetchDownloadProviderWithStubbing | |
- (id<TIPImageFetchDownload>)imageFetchDownloadWithContext:(id<TIPImageFetchDownloadContext>)context | |
{ | |
return [[TIPXImageFetchDownload alloc] initWithContext:context | |
stubbingEnabled:self.downloadStubbingEnabled]; | |
} | |
- (void)addDownloadStubForRequestURL:(NSURL *)requestURL | |
responseData:(NSData *)responseData | |
responseMIMEType:(NSString *)MIMEType | |
shouldSupportResuming:(BOOL)shouldSupportResume | |
suggestedBitrate:(uint64_t)suggestedBitrate | |
{ | |
NSHTTPURLResponse *response = [NSHTTPURLResponse tip_responseWithRequestURL:requestURL | |
dataLength:responseData.length | |
responseMIMEType:MIMEType]; | |
TNLPseudoURLResponseConfig *config = [[TNLPseudoURLResponseConfig alloc] init]; | |
config.bps = suggestedBitrate; | |
config.canProvideRange = shouldSupportResume; | |
[TNLPseudoURLProtocol registerURLResponse:response | |
body:responseData | |
config:config | |
withEndpoint:requestURL]; | |
} | |
- (void)removeDownloadStubForRequestURL:(NSURL *)requestURL | |
{ | |
[TNLPseudoURLProtocol unregisterEndpoint:requestURL]; | |
} | |
- (void)removeAllDownloadStubs | |
{ | |
[TNLPseudoURLProtocol unregisterAllEndpoints]; | |
} | |
@end |