PINRemoteImageManager.m 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671
  1. //
  2. // PINRemoteImageManager.m
  3. // Pods
  4. //
  5. // Created by Garrett Moon on 8/17/14.
  6. //
  7. //
  8. #import "PINRemoteImageManager.h"
  9. #import <CommonCrypto/CommonDigest.h>
  10. #import "PINAlternateRepresentationProvider.h"
  11. #import "PINRemoteImage.h"
  12. #import "PINRemoteLock.h"
  13. #import "PINProgressiveImage.h"
  14. #import "PINRemoteImageCallbacks.h"
  15. #import "PINRemoteImageTask.h"
  16. #import "PINRemoteImageProcessorTask.h"
  17. #import "PINRemoteImageDownloadTask.h"
  18. #import "PINDataTaskOperation.h"
  19. #import "PINURLSessionManager.h"
  20. #import "PINRemoteImageMemoryContainer.h"
  21. #import "PINRemoteImageCaching.h"
  22. #import "NSData+ImageDetectors.h"
  23. #import "PINImage+DecodedImage.h"
  24. #import "PINImage+ScaledImage.h"
  25. #if USE_PINCACHE
  26. #import "PINCache+PINRemoteImageCaching.h"
  27. #else
  28. #import "PINRemoteImageBasicCache.h"
  29. #endif
  30. #define PINRemoteImageManagerDefaultTimeout 30.0
  31. #define PINRemoteImageMaxRetries 3
  32. #define PINRemoteImageRetryDelayBase 4
  33. //A limit of 200 characters is chosen because PINDiskCache
  34. //may expand the length by encoding certain characters
  35. #define PINRemoteImageManagerCacheKeyMaxLength 200
  36. NSOperationQueuePriority operationPriorityWithImageManagerPriority(PINRemoteImageManagerPriority priority) {
  37. switch (priority) {
  38. case PINRemoteImageManagerPriorityVeryLow:
  39. return NSOperationQueuePriorityVeryLow;
  40. break;
  41. case PINRemoteImageManagerPriorityLow:
  42. return NSOperationQueuePriorityLow;
  43. break;
  44. case PINRemoteImageManagerPriorityMedium:
  45. return NSOperationQueuePriorityNormal;
  46. break;
  47. case PINRemoteImageManagerPriorityHigh:
  48. return NSOperationQueuePriorityHigh;
  49. break;
  50. case PINRemoteImageManagerPriorityVeryHigh:
  51. return NSOperationQueuePriorityVeryHigh;
  52. break;
  53. }
  54. }
  55. float dataTaskPriorityWithImageManagerPriority(PINRemoteImageManagerPriority priority) {
  56. switch (priority) {
  57. case PINRemoteImageManagerPriorityVeryLow:
  58. return 0.0;
  59. break;
  60. case PINRemoteImageManagerPriorityLow:
  61. return 0.25;
  62. break;
  63. case PINRemoteImageManagerPriorityMedium:
  64. return 0.5;
  65. break;
  66. case PINRemoteImageManagerPriorityHigh:
  67. return 0.75;
  68. break;
  69. case PINRemoteImageManagerPriorityVeryHigh:
  70. return 1.0;
  71. break;
  72. }
  73. }
  74. NSString * const PINRemoteImageManagerErrorDomain = @"PINRemoteImageManagerErrorDomain";
  75. NSString * const PINRemoteImageCacheKey = @"cacheKey";
  76. typedef void (^PINRemoteImageManagerDataCompletion)(NSData *data, NSError *error);
  77. @interface NSOperationQueue (PINRemoteImageManager)
  78. - (void)pin_addOperationWithQueuePriority:(PINRemoteImageManagerPriority)priority block:(void (^)(void))block;
  79. @end
  80. @interface PINTaskQOS : NSObject
  81. - (instancetype)initWithBPS:(float)bytesPerSecond endDate:(NSDate *)endDate;
  82. @property (nonatomic, strong) NSDate *endDate;
  83. @property (nonatomic, assign) float bytesPerSecond;
  84. @end
  85. @interface PINRemoteImageManager () <PINURLSessionManagerDelegate>
  86. {
  87. dispatch_queue_t _callbackQueue;
  88. PINRemoteLock *_lock;
  89. NSOperationQueue *_concurrentOperationQueue;
  90. NSOperationQueue *_urlSessionTaskQueue;
  91. // Necesarry to have a strong reference to _defaultAlternateRepresentationProvider because _alternateRepProvider is __weak
  92. PINAlternateRepresentationProvider *_defaultAlternateRepresentationProvider;
  93. __weak PINAlternateRepresentationProvider *_alternateRepProvider;
  94. }
  95. @property (nonatomic, strong) id<PINRemoteImageCaching> cache;
  96. @property (nonatomic, strong) PINURLSessionManager *sessionManager;
  97. @property (nonatomic, assign) NSTimeInterval timeout;
  98. @property (nonatomic, strong) NSMutableDictionary <NSString *, __kindof PINRemoteImageTask *> *tasks;
  99. @property (nonatomic, strong) NSHashTable <NSUUID *> *canceledTasks;
  100. @property (nonatomic, strong) NSArray <NSNumber *> *progressThresholds;
  101. @property (nonatomic, assign) BOOL shouldBlurProgressive;
  102. @property (nonatomic, assign) CGSize maxProgressiveRenderSize;
  103. @property (nonatomic, assign) NSTimeInterval estimatedRemainingTimeThreshold;
  104. @property (nonatomic, strong) dispatch_queue_t callbackQueue;
  105. @property (nonatomic, strong) NSOperationQueue *concurrentOperationQueue;
  106. @property (nonatomic, strong) NSOperationQueue *urlSessionTaskQueue;
  107. @property (nonatomic, strong) NSMutableArray <PINTaskQOS *> *taskQOS;
  108. @property (nonatomic, assign) float highQualityBPSThreshold;
  109. @property (nonatomic, assign) float lowQualityBPSThreshold;
  110. @property (nonatomic, assign) BOOL shouldUpgradeLowQualityImages;
  111. @property (nonatomic, copy) PINRemoteImageManagerAuthenticationChallenge authenticationChallengeHandler;
  112. @property (nonatomic, strong) NSMutableDictionary <NSString *, NSString *> *httpHeaderFields;
  113. #if DEBUG
  114. @property (nonatomic, assign) float currentBPS;
  115. @property (nonatomic, assign) BOOL overrideBPS;
  116. @property (nonatomic, assign) NSUInteger totalDownloads;
  117. #endif
  118. @end
  119. #pragma mark PINRemoteImageManager
  120. @implementation PINRemoteImageManager
  121. static PINRemoteImageManager *sharedImageManager = nil;
  122. static dispatch_once_t sharedDispatchToken;
  123. + (instancetype)sharedImageManager
  124. {
  125. dispatch_once(&sharedDispatchToken, ^{
  126. sharedImageManager = [[[self class] alloc] init];
  127. });
  128. return sharedImageManager;
  129. }
  130. + (void)setSharedImageManagerWithConfiguration:(NSURLSessionConfiguration *)configuration
  131. {
  132. NSAssert(sharedImageManager == nil, @"sharedImageManager singleton is already configured");
  133. dispatch_once(&sharedDispatchToken, ^{
  134. sharedImageManager = [[[self class] alloc] initWithSessionConfiguration:configuration];
  135. });
  136. }
  137. - (instancetype)init
  138. {
  139. return [self initWithSessionConfiguration:nil];
  140. }
  141. - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
  142. {
  143. return [self initWithSessionConfiguration:configuration alternativeRepresentationProvider:nil];
  144. }
  145. - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration alternativeRepresentationProvider:(id <PINRemoteImageManagerAlternateRepresentationProvider>)alternateRepProvider
  146. {
  147. return [self initWithSessionConfiguration:configuration alternativeRepresentationProvider:alternateRepProvider imageCache:nil];
  148. }
  149. - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration
  150. alternativeRepresentationProvider:(nullable id <PINRemoteImageManagerAlternateRepresentationProvider>)alternateRepProvider
  151. imageCache:(nullable id<PINRemoteImageCaching>)imageCache
  152. {
  153. if (self = [super init]) {
  154. if (imageCache) {
  155. self.cache = imageCache;
  156. } else {
  157. self.cache = [self defaultImageCache];
  158. }
  159. if (!configuration) {
  160. configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
  161. }
  162. _callbackQueue = dispatch_queue_create("PINRemoteImageManagerCallbackQueue", DISPATCH_QUEUE_CONCURRENT);
  163. _lock = [[PINRemoteLock alloc] initWithName:@"PINRemoteImageManager"];
  164. _concurrentOperationQueue = [[NSOperationQueue alloc] init];
  165. _concurrentOperationQueue.name = @"PINRemoteImageManager Concurrent Operation Queue";
  166. _concurrentOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
  167. if (PINNSOperationSupportsQOS) {
  168. _concurrentOperationQueue.qualityOfService = NSQualityOfServiceUtility;
  169. }
  170. _urlSessionTaskQueue = [[NSOperationQueue alloc] init];
  171. _urlSessionTaskQueue.name = @"PINRemoteImageManager Concurrent URL Session Task Queue";
  172. _urlSessionTaskQueue.maxConcurrentOperationCount = 10;
  173. self.sessionManager = [[PINURLSessionManager alloc] initWithSessionConfiguration:configuration];
  174. self.sessionManager.delegate = self;
  175. self.estimatedRemainingTimeThreshold = 0.1;
  176. self.timeout = PINRemoteImageManagerDefaultTimeout;
  177. _highQualityBPSThreshold = 500000;
  178. _lowQualityBPSThreshold = 50000; // approximately edge speeds
  179. _shouldUpgradeLowQualityImages = NO;
  180. _shouldBlurProgressive = YES;
  181. _maxProgressiveRenderSize = CGSizeMake(1024, 1024);
  182. self.tasks = [[NSMutableDictionary alloc] init];
  183. self.canceledTasks = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:5];
  184. self.taskQOS = [[NSMutableArray alloc] initWithCapacity:5];
  185. if (alternateRepProvider == nil) {
  186. _defaultAlternateRepresentationProvider = [[PINAlternateRepresentationProvider alloc] init];
  187. alternateRepProvider = _defaultAlternateRepresentationProvider;
  188. }
  189. _alternateRepProvider = alternateRepProvider;
  190. _httpHeaderFields = [[NSMutableDictionary alloc] init];
  191. }
  192. return self;
  193. }
  194. - (id<PINRemoteImageCaching>)defaultImageCache
  195. {
  196. #if USE_PINCACHE
  197. NSString * const kPINRemoteImageDiskCacheName = @"PINRemoteImageManagerCache";
  198. NSString * const kPINRemoteImageDiskCacheVersionKey = @"kPINRemoteImageDiskCacheVersionKey";
  199. const NSInteger kPINRemoteImageDiskCacheVersion = 1;
  200. NSUserDefaults *pinDefaults = [[NSUserDefaults alloc] init];
  201. NSString *cacheURLRoot = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  202. if ([pinDefaults integerForKey:kPINRemoteImageDiskCacheVersionKey] != kPINRemoteImageDiskCacheVersion) {
  203. //remove the old version of the disk cache
  204. NSURL *diskCacheURL = [PINDiskCache cacheURLWithRootPath:cacheURLRoot prefix:PINDiskCachePrefix name:kPINRemoteImageDiskCacheName];
  205. [[NSFileManager defaultManager] removeItemAtURL:diskCacheURL error:nil];
  206. [pinDefaults setInteger:kPINRemoteImageDiskCacheVersion forKey:kPINRemoteImageDiskCacheVersionKey];
  207. }
  208. return [[PINCache alloc] initWithName:kPINRemoteImageDiskCacheName rootPath:cacheURLRoot serializer:^NSData * _Nonnull(id<NSCoding> _Nonnull object, NSString * _Nonnull key) {
  209. return (NSData *)object;
  210. } deserializer:^id<NSCoding> _Nonnull(NSData * _Nonnull data, NSString * _Nonnull key) {
  211. return data;
  212. } fileExtension:nil];
  213. #else
  214. return [[PINRemoteImageBasicCache alloc] init];
  215. #endif
  216. }
  217. - (void)lockOnMainThread
  218. {
  219. #if !DEBUG
  220. NSAssert(NO, @"lockOnMainThread should only be called for testing on debug builds!");
  221. #endif
  222. [_lock lock];
  223. }
  224. - (void)lock
  225. {
  226. NSAssert([NSThread isMainThread] == NO, @"lock should not be called from the main thread!");
  227. [_lock lock];
  228. }
  229. - (void)unlock
  230. {
  231. [_lock unlock];
  232. }
  233. - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)header {
  234. __weak typeof(self) weakSelf = self;
  235. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  236. typeof(self) strongSelf = weakSelf;
  237. [strongSelf lock];
  238. strongSelf.httpHeaderFields[[header copy]] = [value copy];
  239. [strongSelf unlock];
  240. });
  241. }
  242. - (void)setAuthenticationChallenge:(PINRemoteImageManagerAuthenticationChallenge)challengeBlock {
  243. __weak typeof(self) weakSelf = self;
  244. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  245. typeof(self) strongSelf = weakSelf;
  246. [strongSelf lock];
  247. strongSelf.authenticationChallengeHandler = challengeBlock;
  248. [strongSelf unlock];
  249. });
  250. }
  251. - (void)setMaxNumberOfConcurrentOperations:(NSInteger)maxNumberOfConcurrentOperations completion:(dispatch_block_t)completion
  252. {
  253. __weak typeof(self) weakSelf = self;
  254. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  255. typeof(self) strongSelf = weakSelf;
  256. [strongSelf lock];
  257. strongSelf.concurrentOperationQueue.maxConcurrentOperationCount = maxNumberOfConcurrentOperations;
  258. [strongSelf unlock];
  259. if (completion) {
  260. completion();
  261. }
  262. });
  263. }
  264. - (void)setMaxNumberOfConcurrentDownloads:(NSInteger)maxNumberOfConcurrentDownloads completion:(dispatch_block_t)completion
  265. {
  266. __weak typeof(self) weakSelf = self;
  267. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  268. typeof(self) strongSelf = weakSelf;
  269. [strongSelf lock];
  270. strongSelf.urlSessionTaskQueue.maxConcurrentOperationCount = maxNumberOfConcurrentDownloads;
  271. [strongSelf unlock];
  272. if (completion) {
  273. completion();
  274. }
  275. });
  276. }
  277. - (void)setEstimatedRemainingTimeThresholdForProgressiveDownloads:(NSTimeInterval)estimatedRemainingTimeThreshold completion:(dispatch_block_t)completion
  278. {
  279. __weak typeof(self) weakSelf = self;
  280. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  281. typeof(self) strongSelf = weakSelf;
  282. [strongSelf lock];
  283. strongSelf.estimatedRemainingTimeThreshold = estimatedRemainingTimeThreshold;
  284. [strongSelf unlock];
  285. if (completion) {
  286. completion();
  287. }
  288. });
  289. }
  290. - (void)setProgressThresholds:(NSArray *)progressThresholds completion:(dispatch_block_t)completion
  291. {
  292. __weak typeof(self) weakSelf = self;
  293. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  294. typeof(self) strongSelf = weakSelf;
  295. [strongSelf lock];
  296. strongSelf.progressThresholds = progressThresholds;
  297. [strongSelf unlock];
  298. if (completion) {
  299. completion();
  300. }
  301. });
  302. }
  303. - (void)setProgressiveRendersShouldBlur:(BOOL)shouldBlur completion:(nullable dispatch_block_t)completion
  304. {
  305. __weak typeof(self) weakSelf = self;
  306. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  307. typeof(self) strongSelf = weakSelf;
  308. [strongSelf lock];
  309. strongSelf.shouldBlurProgressive = shouldBlur;
  310. [strongSelf unlock];
  311. if (completion) {
  312. completion();
  313. }
  314. });
  315. }
  316. - (void)setProgressiveRendersMaxProgressiveRenderSize:(CGSize)maxProgressiveRenderSize completion:(nullable dispatch_block_t)completion
  317. {
  318. __weak typeof(self) weakSelf = self;
  319. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  320. typeof(self) strongSelf = weakSelf;
  321. [strongSelf lock];
  322. strongSelf.maxProgressiveRenderSize = maxProgressiveRenderSize;
  323. [strongSelf unlock];
  324. if (completion) {
  325. completion();
  326. }
  327. });
  328. }
  329. - (void)setHighQualityBPSThreshold:(float)highQualityBPSThreshold completion:(dispatch_block_t)completion
  330. {
  331. __weak typeof(self) weakSelf = self;
  332. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  333. typeof(self) strongSelf = weakSelf;
  334. [strongSelf lock];
  335. strongSelf.highQualityBPSThreshold = highQualityBPSThreshold;
  336. [strongSelf unlock];
  337. if (completion) {
  338. completion();
  339. }
  340. });
  341. }
  342. - (void)setLowQualityBPSThreshold:(float)lowQualityBPSThreshold completion:(dispatch_block_t)completion
  343. {
  344. __weak typeof(self) weakSelf = self;
  345. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  346. typeof(self) strongSelf = weakSelf;
  347. [strongSelf lock];
  348. strongSelf.lowQualityBPSThreshold = lowQualityBPSThreshold;
  349. [strongSelf unlock];
  350. if (completion) {
  351. completion();
  352. }
  353. });
  354. }
  355. - (void)setShouldUpgradeLowQualityImages:(BOOL)shouldUpgradeLowQualityImages completion:(dispatch_block_t)completion
  356. {
  357. __weak typeof(self) weakSelf = self;
  358. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  359. typeof(self) strongSelf = weakSelf;
  360. [strongSelf lock];
  361. strongSelf.shouldUpgradeLowQualityImages = shouldUpgradeLowQualityImages;
  362. [strongSelf unlock];
  363. if (completion) {
  364. completion();
  365. }
  366. });
  367. }
  368. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  369. completion:(PINRemoteImageManagerImageCompletion)completion
  370. {
  371. return [self downloadImageWithURL:url
  372. options:PINRemoteImageManagerDownloadOptionsNone
  373. completion:completion];
  374. }
  375. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  376. options:(PINRemoteImageManagerDownloadOptions)options
  377. completion:(PINRemoteImageManagerImageCompletion)completion
  378. {
  379. return [self downloadImageWithURL:url
  380. options:options
  381. progressImage:nil
  382. completion:completion];
  383. }
  384. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  385. options:(PINRemoteImageManagerDownloadOptions)options
  386. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  387. completion:(PINRemoteImageManagerImageCompletion)completion
  388. {
  389. return [self downloadImageWithURL:url
  390. options:options
  391. priority:PINRemoteImageManagerPriorityMedium
  392. processorKey:nil
  393. processor:nil
  394. progressImage:progressImage
  395. progressDownload:nil
  396. completion:completion
  397. inputUUID:nil];
  398. }
  399. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  400. options:(PINRemoteImageManagerDownloadOptions)options
  401. progressDownload:(PINRemoteImageManagerProgressDownload)progressDownload
  402. completion:(PINRemoteImageManagerImageCompletion)completion
  403. {
  404. return [self downloadImageWithURL:url
  405. options:options
  406. priority:PINRemoteImageManagerPriorityMedium
  407. processorKey:nil
  408. processor:nil
  409. progressImage:nil
  410. progressDownload:progressDownload
  411. completion:completion
  412. inputUUID:nil];
  413. }
  414. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  415. options:(PINRemoteImageManagerDownloadOptions)options
  416. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  417. progressDownload:(PINRemoteImageManagerProgressDownload)progressDownload
  418. completion:(PINRemoteImageManagerImageCompletion)completion
  419. {
  420. return [self downloadImageWithURL:url
  421. options:options
  422. priority:PINRemoteImageManagerPriorityMedium
  423. processorKey:nil
  424. processor:nil
  425. progressImage:progressImage
  426. progressDownload:progressDownload
  427. completion:completion
  428. inputUUID:nil];
  429. }
  430. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  431. options:(PINRemoteImageManagerDownloadOptions)options
  432. processorKey:(NSString *)processorKey
  433. processor:(PINRemoteImageManagerImageProcessor)processor
  434. completion:(PINRemoteImageManagerImageCompletion)completion
  435. {
  436. return [self downloadImageWithURL:url
  437. options:options
  438. priority:PINRemoteImageManagerPriorityMedium
  439. processorKey:processorKey
  440. processor:processor
  441. progressImage:nil
  442. progressDownload:nil
  443. completion:completion
  444. inputUUID:nil];
  445. }
  446. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  447. options:(PINRemoteImageManagerDownloadOptions)options
  448. processorKey:(NSString *)processorKey
  449. processor:(PINRemoteImageManagerImageProcessor)processor
  450. progressDownload:(PINRemoteImageManagerProgressDownload)progressDownload
  451. completion:(PINRemoteImageManagerImageCompletion)completion
  452. {
  453. return [self downloadImageWithURL:url
  454. options:options
  455. priority:PINRemoteImageManagerPriorityMedium
  456. processorKey:processorKey
  457. processor:processor
  458. progressImage:nil
  459. progressDownload:progressDownload
  460. completion:completion
  461. inputUUID:nil];
  462. }
  463. - (NSUUID *)downloadImageWithURL:(NSURL *)url
  464. options:(PINRemoteImageManagerDownloadOptions)options
  465. priority:(PINRemoteImageManagerPriority)priority
  466. processorKey:(NSString *)processorKey
  467. processor:(PINRemoteImageManagerImageProcessor)processor
  468. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  469. progressDownload:(PINRemoteImageManagerProgressDownload)progressDownload
  470. completion:(PINRemoteImageManagerImageCompletion)completion
  471. inputUUID:(NSUUID *)UUID
  472. {
  473. NSAssert((processor != nil && processorKey.length > 0) || (processor == nil && processorKey == nil), @"processor must not be nil and processorKey length must be greater than zero OR processor must be nil and processorKey must be nil");
  474. Class taskClass;
  475. if (processor && processorKey.length > 0) {
  476. taskClass = [PINRemoteImageProcessorTask class];
  477. } else {
  478. taskClass = [PINRemoteImageDownloadTask class];
  479. }
  480. NSString *key = [self cacheKeyForURL:url processorKey:processorKey];
  481. if (url == nil) {
  482. [self earlyReturnWithOptions:options url:nil key:key object:nil completion:completion];
  483. return nil;
  484. }
  485. NSAssert([url isKindOfClass:[NSURL class]], @"url must be of type NSURL, if it's an NSString, we'll try to correct");
  486. if ([url isKindOfClass:[NSString class]]) {
  487. url = [NSURL URLWithString:(NSString *)url];
  488. }
  489. if (UUID == nil) {
  490. UUID = [NSUUID UUID];
  491. }
  492. if ((options & PINRemoteImageManagerDownloadOptionsIgnoreCache) == 0) {
  493. //Check to see if the image is in memory cache and we're on the main thread.
  494. //If so, special case this to avoid flashing the UI
  495. id object = [self.cache objectFromMemoryForKey:key];
  496. if (object) {
  497. if ([self earlyReturnWithOptions:options url:url key:key object:object completion:completion]) {
  498. return nil;
  499. }
  500. }
  501. }
  502. if ([url.scheme isEqualToString:@"data"]) {
  503. NSData *data = [NSData dataWithContentsOfURL:url];
  504. if (data) {
  505. if ([self earlyReturnWithOptions:options url:url key:key object:data completion:completion]) {
  506. return nil;
  507. }
  508. }
  509. }
  510. __weak typeof(self) weakSelf = self;
  511. [_concurrentOperationQueue pin_addOperationWithQueuePriority:priority block:^
  512. {
  513. typeof(self) strongSelf = weakSelf;
  514. [strongSelf lock];
  515. //check canceled tasks first
  516. if ([strongSelf.canceledTasks containsObject:UUID]) {
  517. PINLog(@"skipping starting %@ because it was canceled.", UUID);
  518. [strongSelf unlock];
  519. return;
  520. }
  521. PINRemoteImageTask *task = [strongSelf.tasks objectForKey:key];
  522. BOOL taskExisted = NO;
  523. if (task == nil) {
  524. task = [[taskClass alloc] init];
  525. PINLog(@"Task does not exist creating with key: %@, URL: %@, UUID: %@, task: %p", key, url, UUID, task);
  526. #if PINRemoteImageLogging
  527. task.key = key;
  528. #endif
  529. } else {
  530. taskExisted = YES;
  531. PINLog(@"Task exists, attaching with key: %@, URL: %@, UUID: %@, task: %@", key, url, UUID, task);
  532. }
  533. [task addCallbacksWithCompletionBlock:completion progressImageBlock:progressImage progressDownloadBlock:progressDownload withUUID:UUID];
  534. [strongSelf.tasks setObject:task forKey:key];
  535. BlockAssert(taskClass == [task class], @"Task class should be the same!");
  536. [strongSelf unlock];
  537. if (taskExisted == NO) {
  538. [strongSelf.concurrentOperationQueue pin_addOperationWithQueuePriority:priority block:^
  539. {
  540. typeof(self) strongSelf = weakSelf;
  541. [strongSelf objectForKey:key options:options completion:^(BOOL found, BOOL valid, PINImage *image, id alternativeRepresentation) {
  542. if (found) {
  543. if (valid) {
  544. typeof(self) strongSelf = weakSelf;
  545. [strongSelf callCompletionsWithKey:key image:image alternativeRepresentation:alternativeRepresentation cached:YES error:nil finalized:YES];
  546. } else {
  547. //Remove completion and try again
  548. typeof(self) strongSelf = weakSelf;
  549. [strongSelf lock];
  550. PINRemoteImageTask *task = [strongSelf.tasks objectForKey:key];
  551. [task removeCallbackWithUUID:UUID];
  552. if (task.callbackBlocks.count == 0) {
  553. [strongSelf.tasks removeObjectForKey:key];
  554. }
  555. [strongSelf unlock];
  556. //Skip early check
  557. [strongSelf downloadImageWithURL:url
  558. options:options | PINRemoteImageManagerDownloadOptionsSkipEarlyCheck
  559. priority:priority
  560. processorKey:processorKey
  561. processor:processor
  562. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  563. progressDownload:nil
  564. completion:completion
  565. inputUUID:UUID];
  566. }
  567. } else {
  568. if ([taskClass isSubclassOfClass:[PINRemoteImageProcessorTask class]]) {
  569. //continue processing
  570. [strongSelf downloadImageWithURL:url
  571. options:options
  572. priority:priority
  573. key:key
  574. processor:processor
  575. UUID:UUID];
  576. } else if ([taskClass isSubclassOfClass:[PINRemoteImageDownloadTask class]]) {
  577. //continue downloading
  578. [strongSelf downloadImageWithURL:url
  579. options:options
  580. priority:priority
  581. key:key
  582. progressImage:progressImage
  583. UUID:UUID];
  584. }
  585. }
  586. }];
  587. }];
  588. }
  589. }];
  590. return UUID;
  591. }
  592. - (void)downloadImageWithURL:(NSURL *)url
  593. options:(PINRemoteImageManagerDownloadOptions)options
  594. priority:(PINRemoteImageManagerPriority)priority
  595. key:(NSString *)key
  596. processor:(PINRemoteImageManagerImageProcessor)processor
  597. UUID:(NSUUID *)UUID
  598. {
  599. PINRemoteImageProcessorTask *task = nil;
  600. [self lock];
  601. task = [self.tasks objectForKey:key];
  602. //check processing task still exists and download hasn't been started for another task
  603. if (task == nil || task.downloadTaskUUID != nil) {
  604. [self unlock];
  605. return;
  606. }
  607. __weak typeof(self) weakSelf = self;
  608. NSUUID *downloadTaskUUID = [self downloadImageWithURL:url
  609. options:options | PINRemoteImageManagerDownloadOptionsSkipEarlyCheck
  610. completion:^(PINRemoteImageManagerResult *result)
  611. {
  612. typeof(self) strongSelf = weakSelf;
  613. NSUInteger processCost = 0;
  614. NSError *error = result.error;
  615. PINRemoteImageProcessorTask *task = nil;
  616. [strongSelf lock];
  617. task = [strongSelf.tasks objectForKey:key];
  618. [strongSelf unlock];
  619. //check processing task still exists
  620. if (task == nil) {
  621. return;
  622. }
  623. if (result.image && error == nil) {
  624. //If completionBlocks.count == 0, we've canceled before we were even able to start.
  625. PINImage *image = processor(result, &processCost);
  626. if (image == nil) {
  627. error = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  628. code:PINRemoteImageManagerErrorFailedToProcessImage
  629. userInfo:nil];
  630. }
  631. [strongSelf callCompletionsWithKey:key image:image alternativeRepresentation:nil cached:NO error:error finalized:NO];
  632. if (error == nil && image != nil) {
  633. BOOL saveAsJPEG = (options & PINRemoteImageManagerSaveProcessedImageAsJPEG) != 0;
  634. NSData *diskData = nil;
  635. if (saveAsJPEG) {
  636. diskData = PINImageJPEGRepresentation(image, 1.0);
  637. } else {
  638. diskData = PINImagePNGRepresentation(image);
  639. }
  640. [strongSelf materializeAndCacheObject:image cacheInDisk:diskData additionalCost:processCost url:url key:key options:options outImage:nil outAltRep:nil];
  641. }
  642. [strongSelf callCompletionsWithKey:key image:image alternativeRepresentation:nil cached:NO error:error finalized:YES];
  643. } else {
  644. if (error == nil) {
  645. error = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  646. code:PINRemoteImageManagerErrorFailedToFetchImageForProcessing
  647. userInfo:nil];
  648. }
  649. [strongSelf callCompletionsWithKey:key image:nil alternativeRepresentation:nil cached:NO error:error finalized:YES];
  650. }
  651. }];
  652. task.downloadTaskUUID = downloadTaskUUID;
  653. [self unlock];
  654. }
  655. - (void)downloadImageWithURL:(NSURL *)url
  656. options:(PINRemoteImageManagerDownloadOptions)options
  657. priority:(PINRemoteImageManagerPriority)priority
  658. key:(NSString *)key
  659. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  660. UUID:(NSUUID *)UUID
  661. {
  662. [self lock];
  663. PINRemoteImageDownloadTask *task = [self.tasks objectForKey:key];
  664. if (task.urlSessionTaskOperation == nil && task.callbackBlocks.count > 0 && task.numberOfRetries == 0) {
  665. //If completionBlocks.count == 0, we've canceled before we were even able to start.
  666. CFTimeInterval startTime = CACurrentMediaTime();
  667. PINDataTaskOperation *urlSessionTaskOperation = [self sessionTaskWithURL:url key:key options:options priority:priority];
  668. task.urlSessionTaskOperation = urlSessionTaskOperation;
  669. task.sessionTaskStartTime = startTime;
  670. }
  671. [self unlock];
  672. }
  673. -(BOOL) insertImageDataIntoCache:(nonnull NSData*)data
  674. withURL:(nonnull NSURL *)url
  675. processorKey:(nullable NSString *)processorKey
  676. additionalCost:(NSUInteger)additionalCost
  677. {
  678. if (url != nil) {
  679. NSString *key = [self cacheKeyForURL:url processorKey:processorKey];
  680. PINRemoteImageManagerDownloadOptions options = PINRemoteImageManagerDownloadOptionsSkipDecode & PINRemoteImageManagerDownloadOptionsSkipEarlyCheck;
  681. PINRemoteImageMemoryContainer *container = [[PINRemoteImageMemoryContainer alloc] init];
  682. container.data = data;
  683. return [self materializeAndCacheObject:container cacheInDisk:data additionalCost:additionalCost url:url key:key options:options outImage: nil outAltRep: nil];
  684. }
  685. return NO;
  686. }
  687. - (BOOL)earlyReturnWithOptions:(PINRemoteImageManagerDownloadOptions)options url:(NSURL *)url key:(NSString *)key object:(id)object completion:(PINRemoteImageManagerImageCompletion)completion
  688. {
  689. PINImage *image = nil;
  690. id alternativeRepresentation = nil;
  691. PINRemoteImageResultType resultType = PINRemoteImageResultTypeNone;
  692. BOOL allowEarlyReturn = !(PINRemoteImageManagerDownloadOptionsSkipEarlyCheck & options);
  693. if (url != nil && object != nil) {
  694. resultType = PINRemoteImageResultTypeMemoryCache;
  695. [self materializeAndCacheObject:object url:url key:key options:options outImage:&image outAltRep:&alternativeRepresentation];
  696. }
  697. if (completion && ((image || alternativeRepresentation) || (url == nil))) {
  698. //If we're on the main thread, special case to call completion immediately
  699. NSError *error = nil;
  700. if (!url) {
  701. error = [NSError errorWithDomain:NSURLErrorDomain
  702. code:NSURLErrorUnsupportedURL
  703. userInfo:@{ NSLocalizedDescriptionKey : @"unsupported URL" }];
  704. }
  705. if (allowEarlyReturn && [NSThread isMainThread]) {
  706. completion([PINRemoteImageManagerResult imageResultWithImage:image
  707. alternativeRepresentation:alternativeRepresentation
  708. requestLength:0
  709. error:error
  710. resultType:resultType
  711. UUID:nil]);
  712. } else {
  713. dispatch_async(self.callbackQueue, ^{
  714. completion([PINRemoteImageManagerResult imageResultWithImage:image
  715. alternativeRepresentation:alternativeRepresentation
  716. requestLength:0
  717. error:error
  718. resultType:resultType
  719. UUID:nil]);
  720. });
  721. }
  722. return YES;
  723. }
  724. return NO;
  725. }
  726. - (PINDataTaskOperation *)sessionTaskWithURL:(NSURL *)url
  727. key:(NSString *)key
  728. options:(PINRemoteImageManagerDownloadOptions)options
  729. priority:(PINRemoteImageManagerPriority)priority
  730. {
  731. __weak typeof(self) weakSelf = self;
  732. return [self downloadDataWithURL:url
  733. key:key
  734. priority:priority
  735. completion:^(NSData *data, NSError *error)
  736. {
  737. [_concurrentOperationQueue pin_addOperationWithQueuePriority:priority block:^
  738. {
  739. typeof(self) strongSelf = weakSelf;
  740. NSError *remoteImageError = error;
  741. PINImage *image = nil;
  742. id alternativeRepresentation = nil;
  743. if (remoteImageError && [[self class] retriableError:remoteImageError]) {
  744. //attempt to retry after delay
  745. BOOL retry = NO;
  746. NSUInteger newNumberOfRetries = 0;
  747. [strongSelf lock];
  748. PINRemoteImageDownloadTask *task = [self.tasks objectForKey:key];
  749. if (task.numberOfRetries < PINRemoteImageMaxRetries) {
  750. retry = YES;
  751. newNumberOfRetries = ++task.numberOfRetries;
  752. // Clear out the exsiting progress image or else new data from retry will be appended
  753. task.progressImage = nil;
  754. task.urlSessionTaskOperation = nil;
  755. }
  756. [strongSelf unlock];
  757. if (retry) {
  758. int64_t delay = powf(PINRemoteImageRetryDelayBase, newNumberOfRetries);
  759. PINLog(@"Retrying download of %@ in %d seconds.", URL, delay);
  760. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  761. typeof(self) strongSelf = weakSelf;
  762. [strongSelf lock];
  763. PINRemoteImageDownloadTask *task = [strongSelf.tasks objectForKey:key];
  764. if (task.urlSessionTaskOperation == nil && task.callbackBlocks.count > 0) {
  765. //If completionBlocks.count == 0, we've canceled before we were even able to start.
  766. PINDataTaskOperation *urlSessionTaskOperation = [strongSelf sessionTaskWithURL:url key:key options:options priority:priority];
  767. task.urlSessionTaskOperation = urlSessionTaskOperation;
  768. }
  769. [strongSelf unlock];
  770. });
  771. return;
  772. }
  773. } else if (remoteImageError == nil) {
  774. //stores the object in the caches
  775. [strongSelf materializeAndCacheObject:data cacheInDisk:data additionalCost:0 url:url key:key options:options outImage:&image outAltRep:&alternativeRepresentation];
  776. }
  777. if (error == nil && image == nil && alternativeRepresentation == nil) {
  778. remoteImageError = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  779. code:PINRemoteImageManagerErrorFailedToDecodeImage
  780. userInfo:nil];
  781. }
  782. [strongSelf callCompletionsWithKey:key image:image alternativeRepresentation:alternativeRepresentation cached:NO error:remoteImageError finalized:YES];
  783. }];
  784. }];
  785. }
  786. + (BOOL)retriableError:(NSError *)remoteImageError
  787. {
  788. if ([remoteImageError.domain isEqualToString:PINURLErrorDomain]) {
  789. return remoteImageError.code >= 500;
  790. } else if ([remoteImageError.domain isEqualToString:NSURLErrorDomain] && remoteImageError.code == NSURLErrorUnsupportedURL) {
  791. return NO;
  792. } else if ([remoteImageError.domain isEqualToString:PINRemoteImageManagerErrorDomain]) {
  793. return NO;
  794. }
  795. return YES;
  796. }
  797. - (PINDataTaskOperation *)downloadDataWithURL:(NSURL *)url
  798. key:(NSString *)key
  799. priority:(PINRemoteImageManagerPriority)priority
  800. completion:(PINRemoteImageManagerDataCompletion)completion
  801. {
  802. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
  803. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  804. timeoutInterval:self.timeout];
  805. if (self.httpHeaderFields.count > 0) {
  806. request.allHTTPHeaderFields = [self.httpHeaderFields copy];
  807. }
  808. [NSURLProtocol setProperty:key forKey:PINRemoteImageCacheKey inRequest:request];
  809. __weak typeof(self) weakSelf = self;
  810. PINDataTaskOperation *dataTaskOperation = [PINDataTaskOperation dataTaskOperationWithSessionManager:self.sessionManager
  811. request:request
  812. completionHandler:^(NSURLResponse *response, NSError *error)
  813. {
  814. typeof(self) strongSelf = weakSelf;
  815. #if DEBUG
  816. [strongSelf lock];
  817. strongSelf.totalDownloads++;
  818. [strongSelf unlock];
  819. #endif
  820. #if PINRemoteImageLogging
  821. if (error && error.code != NSURLErrorCancelled) {
  822. PINLog(@"Failed downloading image: %@ with error: %@", url, error);
  823. } else if (error == nil && response.expectedContentLength == 0) {
  824. PINLog(@"image is empty at URL: %@", url);
  825. } else {
  826. PINLog(@"Finished downloading image: %@", url);
  827. }
  828. #endif
  829. if (error.code != NSURLErrorCancelled) {
  830. [strongSelf lock];
  831. PINRemoteImageDownloadTask *task = [strongSelf.tasks objectForKey:key];
  832. NSData *data = task.progressImage.data;
  833. [strongSelf unlock];
  834. if (error == nil && data == nil) {
  835. error = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  836. code:PINRemoteImageManagerErrorImageEmpty
  837. userInfo:nil];
  838. }
  839. completion(data, error);
  840. }
  841. }];
  842. if (PINNSURLSessionTaskSupportsPriority) {
  843. dataTaskOperation.dataTask.priority = dataTaskPriorityWithImageManagerPriority(priority);
  844. }
  845. dataTaskOperation.queuePriority = operationPriorityWithImageManagerPriority(priority);
  846. [self.urlSessionTaskQueue addOperation:dataTaskOperation];
  847. return dataTaskOperation;
  848. }
  849. - (void)callCompletionsWithKey:(NSString *)key image:(PINImage *)image alternativeRepresentation:(id)alternativeRepresentation cached:(BOOL)cached error:(NSError *)error finalized:(BOOL)finalized
  850. {
  851. [self lock];
  852. PINRemoteImageDownloadTask *task = [self.tasks objectForKey:key];
  853. [task callCompletionsWithQueue:self.callbackQueue remove:!finalized withImage:image alternativeRepresentation:alternativeRepresentation cached:cached error:error];
  854. if (finalized) {
  855. [self.tasks removeObjectForKey:key];
  856. }
  857. [self unlock];
  858. }
  859. #pragma mark - Prefetching
  860. - (NSArray<NSUUID *> *)prefetchImagesWithURLs:(NSArray <NSURL *> *)urls
  861. {
  862. return [self prefetchImagesWithURLs:urls options:PINRemoteImageManagerDownloadOptionsNone | PINRemoteImageManagerDownloadOptionsSkipEarlyCheck];
  863. }
  864. - (NSArray<NSUUID *> *)prefetchImagesWithURLs:(NSArray <NSURL *> *)urls options:(PINRemoteImageManagerDownloadOptions)options
  865. {
  866. NSMutableArray *tasks = [NSMutableArray arrayWithCapacity:urls.count];
  867. for (NSURL *url in urls) {
  868. NSUUID *task = [self prefetchImageWithURL:url options:options];
  869. if (task != nil) {
  870. [tasks addObject:task];
  871. }
  872. }
  873. return tasks;
  874. }
  875. - (NSUUID *)prefetchImageWithURL:(NSURL *)url
  876. {
  877. return [self prefetchImageWithURL:url options:PINRemoteImageManagerDownloadOptionsNone | PINRemoteImageManagerDownloadOptionsSkipEarlyCheck];
  878. }
  879. - (NSUUID *)prefetchImageWithURL:(NSURL *)url options:(PINRemoteImageManagerDownloadOptions)options
  880. {
  881. return [self downloadImageWithURL:url
  882. options:options
  883. priority:PINRemoteImageManagerPriorityVeryLow
  884. processorKey:nil
  885. processor:nil
  886. progressImage:nil
  887. progressDownload:nil
  888. completion:nil
  889. inputUUID:nil];
  890. }
  891. #pragma mark - Cancelation & Priority
  892. - (void)cancelTaskWithUUID:(NSUUID *)UUID
  893. {
  894. if (UUID == nil) {
  895. return;
  896. }
  897. PINLog(@"Attempting to cancel UUID: %@", UUID);
  898. __weak typeof(self) weakSelf = self;
  899. [_concurrentOperationQueue pin_addOperationWithQueuePriority:PINRemoteImageManagerPriorityHigh block:^
  900. {
  901. typeof(self) strongSelf = weakSelf;
  902. [strongSelf lock];
  903. NSString *taskKey = nil;
  904. PINRemoteImageTask *taskToEvaluate = [strongSelf _locked_taskForUUID:UUID key:&taskKey];
  905. if (taskToEvaluate == nil) {
  906. //maybe task hasn't been added to task list yet, add it to canceled tasks.
  907. //there's no need to ever remove a UUID from canceledTasks because it is weak.
  908. [strongSelf.canceledTasks addObject:UUID];
  909. }
  910. if ([taskToEvaluate cancelWithUUID:UUID manager:strongSelf]) {
  911. [strongSelf.tasks removeObjectForKey:taskKey];
  912. }
  913. [strongSelf unlock];
  914. }];
  915. }
  916. - (void)setPriority:(PINRemoteImageManagerPriority)priority ofTaskWithUUID:(NSUUID *)UUID
  917. {
  918. if (UUID == nil) {
  919. return;
  920. }
  921. PINLog(@"Setting priority of UUID: %@ priority: %lu", UUID, (unsigned long)priority);
  922. __weak typeof(self) weakSelf = self;
  923. [_concurrentOperationQueue pin_addOperationWithQueuePriority:PINRemoteImageManagerPriorityHigh block:^{
  924. typeof(self) strongSelf = weakSelf;
  925. [strongSelf lock];
  926. PINRemoteImageTask *task = [strongSelf _locked_taskForUUID:UUID key:NULL];
  927. [task setPriority:priority];
  928. [strongSelf unlock];
  929. }];
  930. }
  931. - (void)setProgressImageCallback:(nullable PINRemoteImageManagerImageCompletion)progressImageCallback ofTaskWithUUID:(nonnull NSUUID *)UUID
  932. {
  933. if (UUID == nil) {
  934. return;
  935. }
  936. PINLog(@"setting progress block of UUID: %@ progressBlock: %@", UUID, progressImageCallback);
  937. __weak typeof(self) weakSelf = self;
  938. [_concurrentOperationQueue pin_addOperationWithQueuePriority:PINRemoteImageManagerPriorityHigh block:^{
  939. typeof(self) strongSelf = weakSelf;
  940. [strongSelf lock];
  941. PINRemoteImageTask *task = [strongSelf _locked_taskForUUID:UUID key:NULL];
  942. if ([task isKindOfClass:[PINRemoteImageDownloadTask class]]) {
  943. PINRemoteImageCallbacks *callbacks = task.callbackBlocks[UUID];
  944. callbacks.progressImageBlock = progressImageCallback;
  945. }
  946. [strongSelf unlock];
  947. }];
  948. }
  949. #pragma mark - Caching
  950. - (void)imageFromCacheWithCacheKey:(NSString *)cacheKey
  951. completion:(PINRemoteImageManagerImageCompletion)completion
  952. {
  953. [self imageFromCacheWithCacheKey:cacheKey options:PINRemoteImageManagerDownloadOptionsNone completion:completion];
  954. }
  955. - (void)imageFromCacheWithCacheKey:(NSString *)cacheKey
  956. options:(PINRemoteImageManagerDownloadOptions)options
  957. completion:(PINRemoteImageManagerImageCompletion)completion
  958. {
  959. [self imageFromCacheWithURL:nil processorKey:nil cacheKey:cacheKey options:options completion:completion];
  960. }
  961. - (void)imageFromCacheWithURL:(nonnull NSURL *)url
  962. processorKey:(nullable NSString *)processorKey
  963. options:(PINRemoteImageManagerDownloadOptions)options
  964. completion:(nonnull PINRemoteImageManagerImageCompletion)completion
  965. {
  966. [self imageFromCacheWithURL:url processorKey:processorKey cacheKey:nil options:options completion:completion];
  967. }
  968. - (void)imageFromCacheWithURL:(NSURL *)url
  969. processorKey:(NSString *)processorKey
  970. cacheKey:(NSString *)cacheKey
  971. options:(PINRemoteImageManagerDownloadOptions)options
  972. completion:(PINRemoteImageManagerImageCompletion)completion
  973. {
  974. CFTimeInterval requestTime = CACurrentMediaTime();
  975. if ((PINRemoteImageManagerDownloadOptionsSkipEarlyCheck & options) == NO && [NSThread isMainThread]) {
  976. PINRemoteImageManagerResult *result = [self synchronousImageFromCacheWithURL:url processorKey:processorKey cacheKey:cacheKey options:options];
  977. if (result.image && result.error) {
  978. completion((result));
  979. return;
  980. }
  981. }
  982. [self objectForURL:url processorKey:processorKey key:cacheKey options:options completion:^(BOOL found, BOOL valid, PINImage *image, id alternativeRepresentation) {
  983. NSError *error = nil;
  984. if (valid == NO) {
  985. error = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  986. code:PINRemoteImageManagerErrorInvalidItemInCache
  987. userInfo:nil];
  988. }
  989. dispatch_async(self.callbackQueue, ^{
  990. completion([PINRemoteImageManagerResult imageResultWithImage:image
  991. alternativeRepresentation:alternativeRepresentation
  992. requestLength:CACurrentMediaTime() - requestTime
  993. error:error
  994. resultType:PINRemoteImageResultTypeCache
  995. UUID:nil]);
  996. });
  997. }];
  998. }
  999. - (PINRemoteImageManagerResult *)synchronousImageFromCacheWithCacheKey:(NSString *)cacheKey options:(PINRemoteImageManagerDownloadOptions)options
  1000. {
  1001. return [self synchronousImageFromCacheWithURL:nil processorKey:nil cacheKey:cacheKey options:options];
  1002. }
  1003. - (nonnull PINRemoteImageManagerResult *)synchronousImageFromCacheWithURL:(NSURL *)url processorKey:(nullable NSString *)processorKey options:(PINRemoteImageManagerDownloadOptions)options
  1004. {
  1005. return [self synchronousImageFromCacheWithURL:url processorKey:processorKey cacheKey:nil options:options];
  1006. }
  1007. - (PINRemoteImageManagerResult *)synchronousImageFromCacheWithURL:(NSURL *)url processorKey:(NSString *)processorKey cacheKey:(NSString *)cacheKey options:(PINRemoteImageManagerDownloadOptions)options
  1008. {
  1009. CFTimeInterval requestTime = CACurrentMediaTime();
  1010. if (cacheKey == nil && url == nil) {
  1011. return nil;
  1012. }
  1013. cacheKey = cacheKey ?: [self cacheKeyForURL:url processorKey:processorKey];
  1014. id object = [self.cache objectFromMemoryForKey:cacheKey];
  1015. PINImage *image;
  1016. id alternativeRepresentation;
  1017. NSError *error = nil;
  1018. if (object == nil) {
  1019. image = nil;
  1020. alternativeRepresentation = nil;
  1021. } else if ([self materializeAndCacheObject:object url:url key:cacheKey options:options outImage:&image outAltRep:&alternativeRepresentation] == NO) {
  1022. error = [NSError errorWithDomain:PINRemoteImageManagerErrorDomain
  1023. code:PINRemoteImageManagerErrorInvalidItemInCache
  1024. userInfo:nil];
  1025. }
  1026. return [PINRemoteImageManagerResult imageResultWithImage:image
  1027. alternativeRepresentation:alternativeRepresentation
  1028. requestLength:CACurrentMediaTime() - requestTime
  1029. error:error
  1030. resultType:PINRemoteImageResultTypeMemoryCache
  1031. UUID:nil];
  1032. }
  1033. #pragma mark - Session Task Blocks
  1034. - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge forTask:(NSURLSessionTask *)task completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  1035. [self lock];
  1036. if (self.authenticationChallengeHandler) {
  1037. self.authenticationChallengeHandler(task, challenge, completionHandler);
  1038. } else {
  1039. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  1040. }
  1041. [self unlock];
  1042. }
  1043. - (void)didReceiveData:(NSData *)data forTask:(NSURLSessionDataTask *)dataTask
  1044. {
  1045. [self lock];
  1046. NSString *cacheKey = [NSURLProtocol propertyForKey:PINRemoteImageCacheKey inRequest:dataTask.originalRequest];
  1047. PINRemoteImageDownloadTask *task = [self.tasks objectForKey:cacheKey];
  1048. if (task.progressImage == nil) {
  1049. task.progressImage = [[PINProgressiveImage alloc] init];
  1050. task.progressImage.startTime = task.sessionTaskStartTime;
  1051. task.progressImage.estimatedRemainingTimeThreshold = self.estimatedRemainingTimeThreshold;
  1052. if (self.progressThresholds) {
  1053. task.progressImage.progressThresholds = self.progressThresholds;
  1054. }
  1055. }
  1056. PINProgressiveImage *progressiveImage = task.progressImage;
  1057. BOOL hasProgressBlocks = task.hasProgressBlocks;
  1058. BOOL shouldBlur = self.shouldBlurProgressive;
  1059. CGSize maxProgressiveRenderSize = self.maxProgressiveRenderSize;
  1060. [task callProgressDownloadWithQueue:self.callbackQueue completedBytes:dataTask.countOfBytesReceived totalBytes:dataTask.countOfBytesExpectedToReceive];
  1061. [self unlock];
  1062. [progressiveImage updateProgressiveImageWithData:data expectedNumberOfBytes:[dataTask countOfBytesExpectedToReceive]];
  1063. if (hasProgressBlocks && PINNSOperationSupportsQOS) {
  1064. __weak typeof(self) weakSelf = self;
  1065. [_concurrentOperationQueue pin_addOperationWithQueuePriority:PINRemoteImageManagerPriorityLow block:^{
  1066. typeof(self) strongSelf = weakSelf;
  1067. CGFloat renderedImageQuality = 1.0;
  1068. PINImage *progressImage = [progressiveImage currentImageBlurred:shouldBlur maxProgressiveRenderSize:maxProgressiveRenderSize renderedImageQuality:&renderedImageQuality];
  1069. if (progressImage) {
  1070. [strongSelf lock];
  1071. PINRemoteImageDownloadTask *task = strongSelf.tasks[cacheKey];
  1072. [task callProgressImageWithQueue:strongSelf.callbackQueue withImage:progressImage renderedImageQuality:renderedImageQuality];
  1073. [strongSelf unlock];
  1074. }
  1075. }];
  1076. }
  1077. }
  1078. - (void)didCompleteTask:(NSURLSessionTask *)task withError:(NSError *)error
  1079. {
  1080. if (error == nil && [task isKindOfClass:[NSURLSessionDataTask class]]) {
  1081. NSURLSessionDataTask *dataTask = (NSURLSessionDataTask *)task;
  1082. [self lock];
  1083. NSString *cacheKey = [NSURLProtocol propertyForKey:PINRemoteImageCacheKey inRequest:dataTask.originalRequest];
  1084. PINRemoteImageDownloadTask *task = [self.tasks objectForKey:cacheKey];
  1085. task.sessionTaskEndTime = CACurrentMediaTime();
  1086. CFTimeInterval taskLength = task.sessionTaskEndTime - task.sessionTaskStartTime;
  1087. [self unlock];
  1088. float bytesPerSecond = dataTask.countOfBytesReceived / taskLength;
  1089. [self addTaskBPS:bytesPerSecond endDate:[NSDate date]];
  1090. }
  1091. }
  1092. #pragma mark - QOS
  1093. - (float)currentBytesPerSecond
  1094. {
  1095. [self lock];
  1096. #if DEBUG
  1097. if (self.overrideBPS) {
  1098. float currentBPS = self.currentBPS;
  1099. [self unlock];
  1100. return currentBPS;
  1101. }
  1102. #endif
  1103. const NSTimeInterval validThreshold = 60.0;
  1104. __block NSUInteger count = 0;
  1105. __block float bps = 0;
  1106. __block BOOL valid = NO;
  1107. NSDate *threshold = [NSDate dateWithTimeIntervalSinceNow:-validThreshold];
  1108. [self.taskQOS enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(PINTaskQOS *taskQOS, NSUInteger idx, BOOL *stop) {
  1109. if ([taskQOS.endDate compare:threshold] == NSOrderedAscending) {
  1110. *stop = YES;
  1111. return;
  1112. }
  1113. valid = YES;
  1114. count++;
  1115. bps += taskQOS.bytesPerSecond;
  1116. }];
  1117. [self unlock];
  1118. if (valid == NO) {
  1119. return -1;
  1120. }
  1121. return bps / (float)count;
  1122. }
  1123. - (void)addTaskBPS:(float)bytesPerSecond endDate:(NSDate *)endDate
  1124. {
  1125. //if bytesPerSecond is less than or equal to zero, ignore.
  1126. if (bytesPerSecond <= 0) {
  1127. return;
  1128. }
  1129. [self lock];
  1130. if (self.taskQOS.count >= 5) {
  1131. [self.taskQOS removeObjectAtIndex:0];
  1132. }
  1133. PINTaskQOS *taskQOS = [[PINTaskQOS alloc] initWithBPS:bytesPerSecond endDate:endDate];
  1134. [self.taskQOS addObject:taskQOS];
  1135. [self.taskQOS sortUsingComparator:^NSComparisonResult(PINTaskQOS *obj1, PINTaskQOS *obj2) {
  1136. return [obj1.endDate compare:obj2.endDate];
  1137. }];
  1138. [self unlock];
  1139. }
  1140. #if DEBUG
  1141. - (void)setCurrentBytesPerSecond:(float)currentBPS
  1142. {
  1143. [self lockOnMainThread];
  1144. _overrideBPS = YES;
  1145. _currentBPS = currentBPS;
  1146. [self unlock];
  1147. }
  1148. #endif
  1149. - (NSUUID *)downloadImageWithURLs:(NSArray <NSURL *> *)urls
  1150. options:(PINRemoteImageManagerDownloadOptions)options
  1151. progressImage:(PINRemoteImageManagerImageCompletion)progressImage
  1152. completion:(PINRemoteImageManagerImageCompletion)completion
  1153. {
  1154. NSUUID *UUID = [NSUUID UUID];
  1155. if (urls.count <= 1) {
  1156. NSURL *url = [urls firstObject];
  1157. [self downloadImageWithURL:url
  1158. options:options
  1159. priority:PINRemoteImageManagerPriorityMedium
  1160. processorKey:nil
  1161. processor:nil
  1162. progressImage:progressImage
  1163. progressDownload:nil
  1164. completion:completion
  1165. inputUUID:UUID];
  1166. return UUID;
  1167. }
  1168. __weak typeof(self) weakSelf = self;
  1169. [self.concurrentOperationQueue pin_addOperationWithQueuePriority:PINRemoteImageManagerPriorityMedium block:^{
  1170. __block NSInteger highestQualityDownloadedIdx = -1;
  1171. typeof(self) strongSelf = weakSelf;
  1172. //check for the highest quality image already in cache. It's possible that an image is in the process of being
  1173. //cached when this is being run. In which case two things could happen:
  1174. // - If network conditions dictate that a lower quality image should be downloaded than the one that is currently
  1175. // being cached, it will be downloaded in addition. This is not ideal behavior, worst case scenario and unlikely.
  1176. // - If network conditions dictate that the same quality image should be downloaded as the one being cached, no
  1177. // new image will be downloaded as either the caching will have finished by the time we actually request it or
  1178. // the task will still exist and our callback will be attached. In this case, no detrimental behavior will have
  1179. // occurred.
  1180. [urls enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
  1181. typeof(self) strongSelf = weakSelf;
  1182. BlockAssert([url isKindOfClass:[NSURL class]], @"url must be of type URL");
  1183. NSString *cacheKey = [strongSelf cacheKeyForURL:url processorKey:nil];
  1184. //we don't actually need the object, just need to know it exists so that we can request it later
  1185. BOOL hasObject = [self.cache objectExistsForKey:cacheKey];
  1186. if (hasObject) {
  1187. highestQualityDownloadedIdx = idx;
  1188. *stop = YES;
  1189. }
  1190. }];
  1191. float currentBytesPerSecond = [strongSelf currentBytesPerSecond];
  1192. [strongSelf lock];
  1193. float highQualityQPSThreshold = [strongSelf highQualityBPSThreshold];
  1194. float lowQualityQPSThreshold = [strongSelf lowQualityBPSThreshold];
  1195. BOOL shouldUpgradeLowQualityImages = [strongSelf shouldUpgradeLowQualityImages];
  1196. [strongSelf unlock];
  1197. NSUInteger desiredImageURLIdx;
  1198. if (currentBytesPerSecond == -1 || currentBytesPerSecond >= highQualityQPSThreshold) {
  1199. desiredImageURLIdx = urls.count - 1;
  1200. } else if (currentBytesPerSecond <= lowQualityQPSThreshold) {
  1201. desiredImageURLIdx = 0;
  1202. } else if (urls.count == 2) {
  1203. desiredImageURLIdx = roundf((currentBytesPerSecond - lowQualityQPSThreshold) / ((highQualityQPSThreshold - lowQualityQPSThreshold) / (float)(urls.count - 1)));
  1204. } else {
  1205. desiredImageURLIdx = ceilf((currentBytesPerSecond - lowQualityQPSThreshold) / ((highQualityQPSThreshold - lowQualityQPSThreshold) / (float)(urls.count - 2)));
  1206. }
  1207. NSUInteger downloadIdx;
  1208. //if the highest quality already downloaded is less than what currentBPS would dictate and shouldUpgrade is
  1209. //set, download the new higher quality image. If no image has been cached, download the image dictated by
  1210. //current bps
  1211. if ((highestQualityDownloadedIdx < desiredImageURLIdx && shouldUpgradeLowQualityImages) || highestQualityDownloadedIdx == -1) {
  1212. downloadIdx = desiredImageURLIdx;
  1213. } else {
  1214. downloadIdx = highestQualityDownloadedIdx;
  1215. }
  1216. NSURL *downloadURL = [urls objectAtIndex:downloadIdx];
  1217. [strongSelf downloadImageWithURL:downloadURL
  1218. options:options
  1219. priority:PINRemoteImageManagerPriorityMedium
  1220. processorKey:nil
  1221. processor:nil
  1222. progressImage:progressImage
  1223. progressDownload:nil
  1224. completion:^(PINRemoteImageManagerResult *result) {
  1225. typeof(self) strongSelf = weakSelf;
  1226. //clean out any lower quality images from the cache
  1227. for (NSInteger idx = downloadIdx - 1; idx >= 0; idx--) {
  1228. [[strongSelf cache] removeObjectForKey:[strongSelf cacheKeyForURL:[urls objectAtIndex:idx] processorKey:nil]];
  1229. }
  1230. if (completion) {
  1231. completion(result);
  1232. }
  1233. }
  1234. inputUUID:UUID];
  1235. }];
  1236. return UUID;
  1237. }
  1238. #pragma mark - Caching
  1239. - (BOOL)materializeAndCacheObject:(id)object
  1240. url:(NSURL *)url
  1241. key:(NSString *)key
  1242. options:(PINRemoteImageManagerDownloadOptions)options
  1243. outImage:(PINImage **)outImage
  1244. outAltRep:(id *)outAlternateRepresentation
  1245. {
  1246. return [self materializeAndCacheObject:object cacheInDisk:nil additionalCost:0 url:url key:key options:options outImage:outImage outAltRep:outAlternateRepresentation];
  1247. }
  1248. //takes the object from the cache and returns an image or animated image.
  1249. //if it's a non-alternative representation and skipDecode is not set it also decompresses the image.
  1250. - (BOOL)materializeAndCacheObject:(id)object
  1251. cacheInDisk:(NSData *)diskData
  1252. additionalCost:(NSUInteger)additionalCost
  1253. url:(NSURL *)url
  1254. key:(NSString *)key
  1255. options:(PINRemoteImageManagerDownloadOptions)options
  1256. outImage:(PINImage **)outImage
  1257. outAltRep:(id *)outAlternateRepresentation
  1258. {
  1259. NSAssert(object != nil, @"Object should not be nil.");
  1260. if (object == nil) {
  1261. return NO;
  1262. }
  1263. BOOL alternateRepresentationsAllowed = (PINRemoteImageManagerDisallowAlternateRepresentations & options) == 0;
  1264. BOOL skipDecode = (options & PINRemoteImageManagerDownloadOptionsSkipDecode) != 0;
  1265. __block id alternateRepresentation = nil;
  1266. __block PINImage *image = nil;
  1267. __block NSData *data = nil;
  1268. __block BOOL updateMemoryCache = NO;
  1269. PINRemoteImageMemoryContainer *container = nil;
  1270. if ([object isKindOfClass:[PINRemoteImageMemoryContainer class]]) {
  1271. container = (PINRemoteImageMemoryContainer *)object;
  1272. [container.lock lockWithBlock:^{
  1273. data = container.data;
  1274. }];
  1275. } else {
  1276. updateMemoryCache = YES;
  1277. // don't need to lock the container here because we just init it.
  1278. container = [[PINRemoteImageMemoryContainer alloc] init];
  1279. if ([object isKindOfClass:[PINImage class]]) {
  1280. data = diskData;
  1281. container.image = (PINImage *)object;
  1282. } else if ([object isKindOfClass:[NSData class]]) {
  1283. data = (NSData *)object;
  1284. } else {
  1285. //invalid item in cache
  1286. updateMemoryCache = NO;
  1287. data = nil;
  1288. container = nil;
  1289. }
  1290. container.data = data;
  1291. }
  1292. if (alternateRepresentationsAllowed) {
  1293. alternateRepresentation = [_alternateRepProvider alternateRepresentationWithData:data options:options];
  1294. }
  1295. if (alternateRepresentation == nil) {
  1296. //we need the image
  1297. [container.lock lockWithBlock:^{
  1298. image = container.image;
  1299. }];
  1300. if (image == nil && container.data) {
  1301. image = [PINImage pin_decodedImageWithData:container.data skipDecodeIfPossible:skipDecode];
  1302. if (url != nil) {
  1303. image = [PINImage pin_scaledImageForImage:image withKey:key];
  1304. }
  1305. if (skipDecode == NO) {
  1306. [container.lock lockWithBlock:^{
  1307. updateMemoryCache = YES;
  1308. container.image = image;
  1309. }];
  1310. }
  1311. }
  1312. }
  1313. if (updateMemoryCache) {
  1314. [container.lock lockWithBlock:^{
  1315. NSUInteger cacheCost = additionalCost;
  1316. cacheCost += [container.data length];
  1317. CGImageRef imageRef = container.image.CGImage;
  1318. NSAssert(container.image == nil || imageRef != NULL, @"We only cache a decompressed image if we decompressed it ourselves. In that case, it should be backed by a CGImageRef.");
  1319. if (imageRef) {
  1320. cacheCost += CGImageGetHeight(imageRef) * CGImageGetBytesPerRow(imageRef);
  1321. }
  1322. [self.cache setObjectInMemory:container forKey:key withCost:cacheCost];
  1323. }];
  1324. }
  1325. if (diskData) {
  1326. [self.cache setObjectOnDisk:diskData forKey:key];
  1327. }
  1328. if (outImage) {
  1329. *outImage = image;
  1330. }
  1331. if (outAlternateRepresentation) {
  1332. *outAlternateRepresentation = alternateRepresentation;
  1333. }
  1334. if (image == nil && alternateRepresentation == nil) {
  1335. PINLog(@"Invalid item in cache");
  1336. [self.cache removeObjectForKey:key completion:nil];
  1337. return NO;
  1338. }
  1339. return YES;
  1340. }
  1341. - (NSString *)cacheKeyForURL:(NSURL *)url processorKey:(NSString *)processorKey
  1342. {
  1343. NSString *cacheKey = [url absoluteString];
  1344. if (processorKey.length > 0) {
  1345. cacheKey = [cacheKey stringByAppendingFormat:@"-<%@>", processorKey];
  1346. }
  1347. //PINDiskCache uses this key as the filename of the file written to disk
  1348. //Due to the current filesystem used in Darwin, this name must be limited to 255 chars.
  1349. //In case the generated key exceeds PINRemoteImageManagerCacheKeyMaxLength characters,
  1350. //we return the hash of it instead.
  1351. if (cacheKey.length > PINRemoteImageManagerCacheKeyMaxLength) {
  1352. __block CC_MD5_CTX ctx;
  1353. CC_MD5_Init(&ctx);
  1354. NSData *data = [cacheKey dataUsingEncoding:NSUTF8StringEncoding];
  1355. [data enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) {
  1356. CC_MD5_Update(&ctx, bytes, (CC_LONG)byteRange.length);
  1357. }];
  1358. unsigned char digest[CC_MD5_DIGEST_LENGTH];
  1359. CC_MD5_Final(digest, &ctx);
  1360. NSMutableString *hexString = [NSMutableString stringWithCapacity:(CC_MD5_DIGEST_LENGTH * 2)];
  1361. for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
  1362. [hexString appendFormat:@"%02lx", (unsigned long)digest[i]];
  1363. }
  1364. cacheKey = [hexString copy];
  1365. }
  1366. return cacheKey;
  1367. }
  1368. - (void)objectForKey:(NSString *)key options:(PINRemoteImageManagerDownloadOptions)options completion:(void (^)(BOOL found, BOOL valid, PINImage *image, id alternativeRepresentation))completion
  1369. {
  1370. return [self objectForURL:nil processorKey:nil key:key options:options completion:completion];
  1371. }
  1372. - (void)objectForURL:(NSURL *)url processorKey:(NSString *)processorKey key:(NSString *)key options:(PINRemoteImageManagerDownloadOptions)options completion:(void (^)(BOOL found, BOOL valid, PINImage *image, id alternativeRepresentation))completion
  1373. {
  1374. if ((options & PINRemoteImageManagerDownloadOptionsIgnoreCache) != 0) {
  1375. completion(NO, YES, nil, nil);
  1376. return;
  1377. }
  1378. if (key == nil && url == nil) {
  1379. completion(NO, YES, nil, nil);
  1380. return;
  1381. }
  1382. key = key ?: [self cacheKeyForURL:url processorKey:processorKey];
  1383. void (^materialize)(id object) = ^(id object) {
  1384. PINImage *image = nil;
  1385. id alternativeRepresentation = nil;
  1386. BOOL valid = [self materializeAndCacheObject:object
  1387. url:nil
  1388. key:key
  1389. options:options
  1390. outImage:&image
  1391. outAltRep:&alternativeRepresentation];
  1392. completion(YES, valid, image, alternativeRepresentation);
  1393. };
  1394. PINRemoteImageMemoryContainer *container = [self.cache objectFromMemoryForKey:key];
  1395. if (container) {
  1396. materialize(container);
  1397. } else {
  1398. [self.cache objectFromDiskForKey:key completion:^(id<PINRemoteImageCaching> _Nonnull cache,
  1399. NSString *_Nonnull key,
  1400. id _Nullable object) {
  1401. if (object) {
  1402. materialize(object);
  1403. } else {
  1404. completion(NO, YES, nil, nil);
  1405. }
  1406. }];
  1407. }
  1408. }
  1409. /// Attempt to find the task with the callbacks for the given uuid
  1410. - (nullable PINRemoteImageTask *)_locked_taskForUUID:(NSUUID *)uuid key:(NSString * _Nullable * _Nullable)outKey
  1411. {
  1412. __block PINRemoteImageTask *result = nil;
  1413. [self.tasks enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, __kindof PINRemoteImageTask * _Nonnull task, BOOL * _Nonnull stop) {
  1414. // If this isn't our task, just return.
  1415. if (task.callbackBlocks[uuid] == nil) {
  1416. return;
  1417. }
  1418. // Found it! Save our results and end enumeration
  1419. result = task;
  1420. if (outKey != NULL) {
  1421. *outKey = key;
  1422. }
  1423. *stop = YES;
  1424. }];
  1425. return result;
  1426. }
  1427. @end
  1428. @implementation NSOperationQueue (PINRemoteImageManager)
  1429. - (void)pin_addOperationWithQueuePriority:(PINRemoteImageManagerPriority)priority block:(void (^)(void))block
  1430. {
  1431. NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:block];
  1432. operation.queuePriority = operationPriorityWithImageManagerPriority(priority);
  1433. if (PINNSOperationSupportsQOS == NO) {
  1434. #pragma clang diagnostic push
  1435. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1436. operation.threadPriority = 0.2;
  1437. #pragma clang diagnostic pop
  1438. }
  1439. [self addOperation:operation];
  1440. }
  1441. @end
  1442. @implementation PINTaskQOS
  1443. - (instancetype)initWithBPS:(float)bytesPerSecond endDate:(NSDate *)endDate
  1444. {
  1445. if (self = [super init]) {
  1446. self.endDate = endDate;
  1447. self.bytesPerSecond = bytesPerSecond;
  1448. }
  1449. return self;
  1450. }
  1451. @end