TwitterImagePipeline/Project/TIP_Project.m (157 lines of code) (raw):
//
// TIP_Project.m
// TwitterImagePipeline
//
// Created on 3/5/15.
// Copyright (c) 2015 Twitter. All rights reserved.
//
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonHMAC.h>
#include <objc/runtime.h>
#import "NSData+TIPAdditions.h"
#import "TIP_Project.h"
#import "TIPError.h"
#import "TIPURLStringCoding.h"
NS_ASSUME_NONNULL_BEGIN
const NSTimeInterval TIPTimeToLiveDefault = 30 * 24 * 60 * 60;
#pragma mark Problem Names
TIPProblem TIPProblemDiskCacheUpdateImageEntryCouldNotGenerateFileName = @"TIPProblemDiskCacheUpdateImageEntryCouldNotGenerateFileName";
TIPProblem TIPProblemImageFailedToScale = @"TIPProblemImageFailedToScale";
TIPProblem TIPProblemImageContainerHasNilImage = @"TIPProblemImageContainerHasNilImage";
TIPProblem TIPProblemImageFetchHasInvalidTargetDimensions = @"TIPProblemImageFetchHasInvalidTargetDimensions";
TIPProblem TIPProblemImageDownloadedHasGPSInfo = @"TIPProblemImageDownloadedHasGPSInfo";
TIPProblem TIPProblemImageDownloadedCouldNotBeDecoded = @"TIPProblemImageDownloadedCouldNotBeDecoded";
TIPProblem TIPProblemImageTooLargeToStoreInDiskCache = @"TIPProblemImageTooLargeToStoreInDiskCache";
TIPProblem TIPProblemImageDownloadedWithUnnecessaryError = @"TIPProblemImageDownloadedWithUnnecessaryError";
#pragma mark Problem User Info Keys
TIPProblemInfoKey TIPProblemInfoKeyImageIdentifier = @"imageIdentifier";
TIPProblemInfoKey TIPProblemInfoKeySafeImageIdentifier = @"safeImageIdentifier";
TIPProblemInfoKey TIPProblemInfoKeyImageURL = @"imageURL";
TIPProblemInfoKey TIPProblemInfoKeyTargetDimensions = @"targetDimensions";
TIPProblemInfoKey TIPProblemInfoKeyTargetContentMode = @"targetContentMode";
TIPProblemInfoKey TIPProblemInfoKeyScaledDimensions = @"scaledDimensions";
TIPProblemInfoKey TIPProblemInfoKeyFetchRequest = @"fetchRequest";
TIPProblemInfoKey TIPProblemInfoKeyImageDimensions = @"dimensions";
TIPProblemInfoKey TIPProblemInfoKeyImageIsAnimated = @"animated";
NSString *TIPVersion()
{
TIPStaticAssert(TIP_PROJECT_VERSION >= 1.0 && TIP_PROJECT_VERSION <= 10.0, INVALID_TIP_VERSION);
#define __TIP_VERSION(version) @"" #version
#define _TIP_VERSION(version) __TIP_VERSION( version )
#define TIP_VERSION() _TIP_VERSION( TIP_PROJECT_VERSION )
return TIP_VERSION();
}
void TIPSwizzle(Class cls, SEL originalSelector, SEL swizzledSelector)
{
const Method originalMethod = class_getInstanceMethod(cls, originalSelector);
const Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
const BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
void TIPClassSwizzle(Class cls, SEL originalSelector, SEL swizzledSelector)
{
cls = object_getClass((id)cls);
const Method originalMethod = class_getClassMethod(cls, originalSelector);
const Method swizzledMethod = class_getClassMethod(cls, swizzledSelector);
const BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
static volatile BOOL sShouldAssertDuringPipelineRegistration = YES;
BOOL TIPShouldAssertDuringPipelineRegistation()
{
return sShouldAssertDuringPipelineRegistration;
}
void TIPSetShouldAssertDuringPipelineRegistation(BOOL shouldAssertDuringPipelineRegistration)
{
sShouldAssertDuringPipelineRegistration = shouldAssertDuringPipelineRegistration;
}
CGSize TIPScaleToFillKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CGFloat scale)
{
const CGSize scaledTargetSize = CGSizeMake(__tg_ceil(targetSize.width * scale), __tg_ceil(targetSize.height * scale));
const CGSize scaledSourceSize = CGSizeMake(__tg_ceil(sourceSize.width * scale), __tg_ceil(sourceSize.height * scale));
const CGFloat rx = scaledTargetSize.width / scaledSourceSize.width;
const CGFloat ry = scaledTargetSize.height / scaledSourceSize.height;
CGSize size;
if (rx > ry) {
// Width will be the scaled target width
const CGFloat targetWidth = scaledTargetSize.width;
// get the height from the width preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.height / scaledSourceSize.width;
const CGFloat aspectHeight = targetWidth * ar;
const CGFloat targetHeight = round(aspectHeight);
size = CGSizeMake(targetWidth / scale, targetHeight / scale);
} else {
// Height will be the scaled target height
const CGFloat targetHeight = scaledTargetSize.height;
// get the width from the height preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.width / scaledSourceSize.height;
const CGFloat aspectWidth = targetHeight * ar;
const CGFloat targetWidth = round(aspectWidth);
size = CGSizeMake(targetWidth / scale, targetHeight / scale);
}
return size;
}
CGSize TIPScaleToFitKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CGFloat scale)
{
const CGSize scaledTargetSize = CGSizeMake(__tg_ceil(targetSize.width * scale), __tg_ceil(targetSize.height * scale));
const CGSize scaledSourceSize = CGSizeMake(__tg_ceil(sourceSize.width * scale), __tg_ceil(sourceSize.height * scale));
const CGFloat rx = scaledTargetSize.width / scaledSourceSize.width;
const CGFloat ry = scaledTargetSize.height / scaledSourceSize.height;
CGSize size;
if (rx < ry) {
// Width will be the scaled target width
const CGFloat targetWidth = scaledTargetSize.width;
// get the height from the width preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.height / scaledSourceSize.width;
const CGFloat aspectHeight = targetWidth * ar;
const CGFloat targetHeight = round(aspectHeight);
size = CGSizeMake(targetWidth / scale, targetHeight / scale);
} else {
// Height will be the scaled target height
const CGFloat targetHeight = scaledTargetSize.height;
// get the width from the height preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.width / scaledSourceSize.height;
const CGFloat aspectWidth = targetHeight * ar;
const CGFloat targetWidth = round(aspectWidth);
size = CGSizeMake(targetWidth / scale, targetHeight / scale);
}
return size;
}
NSString *TIPHash(NSString *string)
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
unsigned char hash[CC_SHA1_DIGEST_LENGTH];
(void)CC_SHA1(data.bytes, (CC_LONG)data.length, hash);
data = [[NSData alloc] initWithBytesNoCopy:hash length:CC_SHA1_DIGEST_LENGTH freeWhenDone:NO];
NSString *hashedString = [data tip_hexStringValue];
TIPAssert(hashedString);
return hashedString;
}
NSString *TIPSafeFromRaw(NSString *raw)
{
NSString *safe = TIPURLEncodeString(raw);
if (safe.length > (NAME_MAX - 4 /* 4 = 3 characters for extension "tmp" + 1 character for '.' */)) {
safe = TIPHash(safe);
}
TIPAssert(safe.length != 0);
return safe;
}
NSString *TIPRawFromSafe(NSString *safe)
{
TIPAssert(safe != 0);
NSString *raw = TIPURLDecodeString(safe, NO);
TIPAssert(raw != 0);
return (NSString * _Nonnull)raw; // TIPAssert() performed 1 line above
}
dispatch_block_t __nullable TIPStartBackgroundTask(NSString * __nullable name)
{
__block NSUInteger taskId = UIBackgroundTaskInvalid;
dispatch_block_t clearTaskBlock = NULL;
Class UIApplicationClass = [UIApplication class];
if (!TIPIsExtension()) {
clearTaskBlock = ^{
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplicationClass sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
};
taskId = [[UIApplicationClass sharedApplication] beginBackgroundTaskWithName:name
expirationHandler:clearTaskBlock];
}
return clearTaskBlock;
}
NS_ASSUME_NONNULL_END