Skip to content

Instantly share code, notes, and snippets.

@NSProgrammer
Last active August 5, 2020 22:18
Show Gist options
  • Save NSProgrammer/6e4c93ca9b9518178c9cbc7d950efd9c to your computer and use it in GitHub Desktop.
Save NSProgrammer/6e4c93ca9b9518178c9cbc7d950efd9c to your computer and use it in GitHub Desktop.
Integrating Twitter Network Layer (TNL) with Twitter Image Pipeline (TIP)
//
// 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment