mirror of
https://github.com/TextureGroup/Texture.git
synced 2026-04-07 19:17:39 +00:00
* [PINCache] Set a default .byteLimit to reduce disk usage & startup time. This default is fairly low - only 20MB - but for most apps with images in the size range of 10-50KB, this is still 400-1000 images. Once some optimizations land to PINCache, we'll match the PINCache default of 50MB to ensure the default better serves users with larger objects in the cache. Apps should preferably set their own byteLimit to an optimal value. @garrettmoon - one interesting question for us is the best place to set .byteLimit as an app. Digging into the ASPINRemoteImageDownloader and doing this type cast is a bit complicated, so a passthrough API to get the PIN* objects directly might be worthwhile. * [PINCache] Declare necessary APIs to avoid a direct dependency.
374 lines
13 KiB
Objective-C
374 lines
13 KiB
Objective-C
//
|
|
// ASPINRemoteImageDownloader.m
|
|
// Texture
|
|
//
|
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
|
// grant of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
|
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
|
|
#import <AsyncDisplayKit/ASAvailability.h>
|
|
|
|
#if AS_PIN_REMOTE_IMAGE
|
|
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
|
|
|
|
#import <AsyncDisplayKit/ASAssert.h>
|
|
#import <AsyncDisplayKit/ASThread.h>
|
|
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
|
|
|
#if __has_include (<PINRemoteImage/PINAnimatedImage.h>)
|
|
#define PIN_ANIMATED_AVAILABLE 1
|
|
#import <PINRemoteImage/PINAnimatedImage.h>
|
|
#import <PINRemoteImage/PINAlternateRepresentationProvider.h>
|
|
#else
|
|
#define PIN_ANIMATED_AVAILABLE 0
|
|
#endif
|
|
|
|
#import <PINRemoteImage/PINRemoteImageManager.h>
|
|
#import <PINRemoteImage/NSData+ImageDetectors.h>
|
|
#import <PINRemoteImage/PINRemoteImageCaching.h>
|
|
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
|
|
@interface ASPINRemoteImageDownloader () <PINRemoteImageManagerAlternateRepresentationProvider>
|
|
|
|
@end
|
|
|
|
@interface PINAnimatedImage (ASPINRemoteImageDownloader) <ASAnimatedImageProtocol>
|
|
|
|
@end
|
|
|
|
@implementation PINAnimatedImage (ASPINRemoteImageDownloader)
|
|
|
|
- (void)setCoverImageReadyCallback:(void (^)(UIImage * _Nonnull))coverImageReadyCallback
|
|
{
|
|
self.infoCompletion = coverImageReadyCallback;
|
|
}
|
|
|
|
- (void (^)(UIImage * _Nonnull))coverImageReadyCallback
|
|
{
|
|
return self.infoCompletion;
|
|
}
|
|
|
|
- (void)setPlaybackReadyCallback:(dispatch_block_t)playbackReadyCallback
|
|
{
|
|
self.fileReady = playbackReadyCallback;
|
|
}
|
|
|
|
- (dispatch_block_t)playbackReadyCallback
|
|
{
|
|
return self.fileReady;
|
|
}
|
|
|
|
- (BOOL)isDataSupported:(NSData *)data
|
|
{
|
|
return [data pin_isGIF];
|
|
}
|
|
|
|
@end
|
|
#endif
|
|
|
|
// Declare two key methods on PINCache objects, avoiding a direct dependency on PINCache.h
|
|
@protocol ASPINCache
|
|
- (id)diskCache;
|
|
@end
|
|
|
|
@protocol ASPINDiskCache
|
|
@property (assign) NSUInteger byteLimit;
|
|
@end
|
|
|
|
@interface ASPINRemoteImageManager : PINRemoteImageManager
|
|
@end
|
|
|
|
@implementation ASPINRemoteImageManager
|
|
|
|
//Share image cache with sharedImageManager image cache.
|
|
- (id <PINRemoteImageCaching>)defaultImageCache
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
static id <PINRemoteImageCaching> cache = nil;
|
|
dispatch_once(&onceToken, ^{
|
|
cache = [[PINRemoteImageManager sharedImageManager] cache];
|
|
if ([cache respondsToSelector:@selector(diskCache)]) {
|
|
id diskCache = [(id <ASPINCache>)cache diskCache];
|
|
if ([diskCache respondsToSelector:@selector(setByteLimit:)]) {
|
|
// Set a default byteLimit. PINCache recently implemented a 50MB default (PR #201).
|
|
// Ensure that older versions of PINCache also have a byteLimit applied.
|
|
// NOTE: Using 20MB limit while large cache initialization is being optimized (Issue #144).
|
|
((id <ASPINDiskCache>)diskCache).byteLimit = 20 * 1024 * 1024;
|
|
}
|
|
}
|
|
});
|
|
return cache;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
static ASPINRemoteImageDownloader *sharedDownloader = nil;
|
|
|
|
@interface ASPINRemoteImageDownloader ()
|
|
@end
|
|
|
|
@implementation ASPINRemoteImageDownloader
|
|
|
|
+ (instancetype)sharedDownloader
|
|
{
|
|
|
|
static dispatch_once_t onceToken = 0;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedDownloader = [[ASPINRemoteImageDownloader alloc] init];
|
|
});
|
|
return sharedDownloader;
|
|
}
|
|
|
|
+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration
|
|
{
|
|
NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured.");
|
|
__unused PINRemoteImageManager *sharedManager = [self sharedPINRemoteImageManagerWithConfiguration:configuration];
|
|
}
|
|
|
|
+ (PINRemoteImageManager *)sharedPINRemoteImageManagerWithConfiguration:(NSURLSessionConfiguration *)configuration
|
|
{
|
|
static ASPINRemoteImageManager *sharedPINRemoteImageManager;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
// Check that Carthage users have linked both PINRemoteImage & PINCache by testing for one file each
|
|
if (!(NSClassFromString(@"PINRemoteImageManager"))) {
|
|
NSException *e = [NSException
|
|
exceptionWithName:@"FrameworkSetupException"
|
|
reason:@"Missing the path to the PINRemoteImage framework."
|
|
userInfo:nil];
|
|
@throw e;
|
|
}
|
|
if (!(NSClassFromString(@"PINCache"))) {
|
|
NSException *e = [NSException
|
|
exceptionWithName:@"FrameworkSetupException"
|
|
reason:@"Missing the path to the PINCache framework."
|
|
userInfo:nil];
|
|
@throw e;
|
|
}
|
|
sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration
|
|
alternativeRepresentationProvider:[self sharedDownloader]];
|
|
#else
|
|
sharedPINRemoteImageManager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration];
|
|
#endif
|
|
});
|
|
return sharedPINRemoteImageManager;
|
|
}
|
|
|
|
- (PINRemoteImageManager *)sharedPINRemoteImageManager
|
|
{
|
|
return [ASPINRemoteImageDownloader sharedPINRemoteImageManagerWithConfiguration:nil];
|
|
}
|
|
|
|
- (BOOL)sharedImageManagerSupportsMemoryRemoval
|
|
{
|
|
static BOOL sharedImageManagerSupportsMemoryRemoval = NO;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
sharedImageManagerSupportsMemoryRemoval = [[[self sharedPINRemoteImageManager] cache] respondsToSelector:@selector(removeObjectForKeyFromMemory:)];
|
|
});
|
|
return sharedImageManagerSupportsMemoryRemoval;
|
|
}
|
|
|
|
#pragma mark ASImageProtocols
|
|
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
- (nullable id <ASAnimatedImageProtocol>)animatedImageWithData:(NSData *)animatedImageData
|
|
{
|
|
return [[PINAnimatedImage alloc] initWithAnimatedImageData:animatedImageData];
|
|
}
|
|
#endif
|
|
|
|
- (id <ASImageContainerProtocol>)synchronouslyFetchedCachedImageWithURL:(NSURL *)URL;
|
|
{
|
|
PINRemoteImageManager *manager = [self sharedPINRemoteImageManager];
|
|
PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode];
|
|
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
if (result.alternativeRepresentation) {
|
|
return result.alternativeRepresentation;
|
|
}
|
|
#endif
|
|
return result.image;
|
|
}
|
|
|
|
- (void)cachedImageWithURL:(NSURL *)URL
|
|
callbackQueue:(dispatch_queue_t)callbackQueue
|
|
completion:(ASImageCacherCompletion)completion
|
|
{
|
|
// We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice.
|
|
// If we're targeting the main queue and we're on the main thread, complete immediately.
|
|
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
|
|
completion(nil);
|
|
} else {
|
|
dispatch_async(callbackQueue, ^{
|
|
completion(nil);
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs
|
|
callbackQueue:(dispatch_queue_t)callbackQueue
|
|
completion:(ASImageCacherCompletion)completion
|
|
{
|
|
[self cachedImageWithURL:[URLs lastObject]
|
|
callbackQueue:callbackQueue
|
|
completion:^(id<ASImageContainerProtocol> _Nullable imageFromCache) {
|
|
if (imageFromCache.asdk_image == nil && URLs.count > 1) {
|
|
[self cachedImageWithURLs:[URLs subarrayWithRange:NSMakeRange(0, URLs.count - 1)]
|
|
callbackQueue:callbackQueue
|
|
completion:completion];
|
|
} else {
|
|
completion(imageFromCache);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL
|
|
{
|
|
if ([self sharedImageManagerSupportsMemoryRemoval]) {
|
|
PINRemoteImageManager *manager = [self sharedPINRemoteImageManager];
|
|
NSString *key = [manager cacheKeyForURL:URL processorKey:nil];
|
|
[[manager cache] removeObjectForKeyFromMemory:key];
|
|
}
|
|
}
|
|
|
|
- (nullable id)downloadImageWithURL:(NSURL *)URL
|
|
callbackQueue:(dispatch_queue_t)callbackQueue
|
|
downloadProgress:(ASImageDownloaderProgress)downloadProgress
|
|
completion:(ASImageDownloaderCompletion)completion;
|
|
{
|
|
NSArray <NSURL *>*URLs = nil;
|
|
if (URL) {
|
|
URLs = @[URL];
|
|
}
|
|
return [self downloadImageWithURLs:URLs callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion];
|
|
}
|
|
|
|
- (nullable id)downloadImageWithURLs:(NSArray <NSURL *> *)URLs
|
|
callbackQueue:(dispatch_queue_t)callbackQueue
|
|
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
|
|
completion:(ASImageDownloaderCompletion)completion
|
|
{
|
|
PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) {
|
|
if (downloadProgress == nil) { return; }
|
|
|
|
/// If we're targeting the main queue and we're on the main thread, call immediately.
|
|
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
|
|
downloadProgress(completedBytes / (CGFloat)totalBytes);
|
|
} else {
|
|
dispatch_async(callbackQueue, ^{
|
|
downloadProgress(completedBytes / (CGFloat)totalBytes);
|
|
});
|
|
}
|
|
};
|
|
|
|
PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) {
|
|
/// If we're targeting the main queue and we're on the main thread, complete immediately.
|
|
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
if (result.alternativeRepresentation) {
|
|
completion(result.alternativeRepresentation, result.error, result.UUID);
|
|
} else {
|
|
completion(result.image, result.error, result.UUID);
|
|
}
|
|
#else
|
|
completion(result.image, result.error, result.UUID);
|
|
#endif
|
|
} else {
|
|
dispatch_async(callbackQueue, ^{
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
if (result.alternativeRepresentation) {
|
|
completion(result.alternativeRepresentation, result.error, result.UUID);
|
|
} else {
|
|
completion(result.image, result.error, result.UUID);
|
|
}
|
|
#else
|
|
completion(result.image, result.error, result.UUID);
|
|
#endif
|
|
});
|
|
}
|
|
};
|
|
|
|
return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs
|
|
options:PINRemoteImageManagerDownloadOptionsSkipDecode
|
|
progressImage:nil
|
|
progressDownload:progressDownload
|
|
completion:imageCompletion];
|
|
}
|
|
|
|
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
|
|
{
|
|
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
|
|
[[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO];
|
|
}
|
|
|
|
- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier
|
|
{
|
|
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
|
|
[[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES];
|
|
}
|
|
|
|
- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier
|
|
{
|
|
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
|
|
|
|
if (progressBlock) {
|
|
[[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) {
|
|
dispatch_async(callbackQueue, ^{
|
|
progressBlock(result.image, result.renderedImageQuality, result.UUID);
|
|
});
|
|
} ofTaskWithUUID:downloadIdentifier];
|
|
} else {
|
|
[[self sharedPINRemoteImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier];
|
|
}
|
|
}
|
|
|
|
- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier
|
|
{
|
|
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
|
|
|
|
PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityDefault;
|
|
switch (priority) {
|
|
case ASImageDownloaderPriorityPreload:
|
|
pi_priority = PINRemoteImageManagerPriorityLow;
|
|
break;
|
|
|
|
case ASImageDownloaderPriorityImminent:
|
|
pi_priority = PINRemoteImageManagerPriorityDefault;
|
|
break;
|
|
|
|
case ASImageDownloaderPriorityVisible:
|
|
pi_priority = PINRemoteImageManagerPriorityHigh;
|
|
break;
|
|
}
|
|
[[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier];
|
|
}
|
|
|
|
#pragma mark - PINRemoteImageManagerAlternateRepresentationProvider
|
|
|
|
- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options
|
|
{
|
|
#if PIN_ANIMATED_AVAILABLE
|
|
if ([data pin_isGIF]) {
|
|
return data;
|
|
}
|
|
#endif
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
#endif
|