UIImageLoader 是一个从网络加载图片的助手。它在磁盘上缓存图片,并且可选地在内存中。
它使得编写对缓存的图片处理代码、设置占位符图片或展示加载动画,以及处理请求完成时出现的任何错误变得非常简单。
它支持服务器缓存控制,在过期时重新下载图片。出于性能考虑,缓存控制逻辑是手动实现的,而不是使用 NSURLCache。
您也可以完全忽略服务器缓存控制,并手动清理图片。
它与 iOS 和 Mac 兼容。并且体积很小,大约在一个头文件/实现文件中有 600 多行代码。
所有操作都是异步的,并使用现代 Objective-C 与 libdispatch 和 NSURLSession。
是的。只需将 UIImageLoader.h 导入您的 Project-bridge-header.h 文件。
它支持带有 Cache-Control 最大存活时间、ETag 和 Last-Modified 标头的响应。
它以 If-None-Match 和 If-Modified-Since 发送请求。
如果服务器没有响应 Cache-Control 标头,您可以设置一个默认的缓存控制最大存活时间,以便缓存图片指定的时间。
如果响应状态为 304,它使用磁盘上可用的缓存图片。
对于 4XX 和 5XX 响应,您可以指定获取图片时允许尝试的次数,和一个缓存控制最大存活时间 - 以防止发生错误时发送相同的请求。
有一个非常简单的 iOS/Mac 示例应用程序,展示了将图片加载到集合视图中。
该应用程序从 Dribbble 加载 1000 张图片。
该应用程序展示了如何设置单元格以优雅地处理
有一个默认配置的加载器,您可以根据自己的需要配置。
//this is the default configuration:
UIImageLoader * loader = [UIImageLoader defaultLoader];
loader.cacheImagesInMemory = FALSE;
loader.trustAnySSLCertificate = FALSE;
loader.useServerCachePolicy = TRUE;
loader.logCacheMisses = TRUE;
loader.defaultCacheControlMaxAge = 0;
loader.acceptedContentTypes = @[@"image/png",@"image/jpg",@"image/jpeg",@"image/bmp",@"image/gif",@"image/tiff"];
loader.defaultCacheControlMaxAgeForErrors = 0;
loader.maxAtemptsForErrors = 0;
[loader setMemoryCacheMaxBytes:25 * (1024 * 1024)]; //25 MB
默认的缓存目录为 ~/Library/Caches/com.my.app.id/UIImageLoader/
或者您也可以设置自己的,并进行配置
//create loader
UIImageLoader * loader = [[UIImageLoader alloc] initWithCacheDirectory:myCustomDiskURL];
//set loader properties here.
载入图片非常简单
NSURL * imageURL = myURL;
[[UIImageLoader defaultLoader] loadImageWithURL:imageURL \
hasCache:^(UIImageLoaderImage * image, UIImageLoadSource loadedFromSource) {
//there was a cached image available. use that.
self.imageView.image = image;
} sendingRequest:^(BOOL didHaveCachedImage) {
//a request is being made for the image.
if(!didHaveCachedImage) {
//there was not a cached image available, set a placeholder or do nothing.
self.loader.hidden = FALSE;
[self.loader startAnimating];
self.imageView.image = [UIImage imageNamed:@"placeholder"];
}
} requestCompleted:^(NSError *error, UIImageLoaderImage * image, UIImageLoadSource loadedFromSource) {
//network request finished.
[self.loader stopAnimating];
self.loader.hidden = TRUE;
if(loadedFromSource == UIImageLoadSourceNetworkToDisk) {
//the image was downloaded and saved to disk.
//since it was downloaded it has been updated since
//last cached version, or is brand new
self.imageView.image = image;
}
}];
枚举对象 UIImageLoadSource 提供了图片从何处加载的信息
//image source passed in completion callbacks.
typedef NS_ENUM(NSInteger,UIImageLoadSource) {
//this is passed to callbacks when there's an error, no image is provided.
UIImageLoadSourceNone, //no image source as there was an error.
//these will be passed to your hasCache callback
UIImageLoadSourceDisk, //image was cached on disk already and loaded from disk
UIImageLoadSourceMemory, //image was in memory cache
//these will be passed to your requestCompleted callback
UIImageLoadSourceNetworkNotModified, //a network request was sent but existing content is still valid
UIImageLoadSourceNetworkToDisk, //a network request was sent, image was updated on disk
};
当您使用 UIImageLoader 载入图片时,第一个回调是 hasCache 回调。它定义为:
typedef void(^UIImageLoader_HasCacheBlock)(UIImageLoaderImage * image, UIImageLoadSource loadedFromSource);
如果存在缓存的图片,您将获得一张图片,并且 loadedFromSource 将是 UIImageLoadSourceDisk 或 UIImageLoadSourceMemory。
如果没有可用的缓存图片,则不会调用此回调。
如果缓存的图片仍然有效(尚未过期),这将仅调用此回调。
第二个回调是 sendingRequest。在将要发送网络请求获取图片之前将调用此回调。您可以使用此回调显示占位符图片或开始进度条。其定义如下:
typedef void(^UIImageLoader_SendingRequestBlock)(BOOL didHaveCachedImage);
如果缓存的图片不可用,此回调将使用 didHaveCachedImage=false 调用,这表示没有调用 hasCache 回调。
如果缓存的图片可用但已过期,此回调将使用 didHaveCachedImage=true 调用。
当请求完成后会运行 requestCompleted 回调。其定义如下:
typedef void(^UIImageLoader_RequestCompletedBlock)(NSError * error, UIImageLoaderImage * image, UIImageLoadSource loadedFromSource);
如果发生网络错误,您将接收到一个错误对象和 UIImageLoadSourceNone。
如果加载来源是 UIImageLoadSourceNetworkToDisk,这意味着已下载了一张图片。
如果加载来源是 UIImageLoadSourceNetworkNotModified,这意味着缓存的图片仍然有效且 image=nil,因为该图片已经被传递到您的 hasCache 回调中。
您可以使用以下方式来自定义服务器接受的 content-types 类型:
loader.acceptedContentTypes = @[@"image/png",@"image/jpg",@"image/jpeg",@"image/bmp",@"image/gif",@"image/tiff"];
您可以轻松启用内存缓存
UIImageLoader * loader = [UIImageLoader defaultLoader];
loader.cacheImagesInMemory = TRUE;
您可以使用以下方式更改内存限制:
UIImageLoader * loader = [UIImageLoader defaultLoader];
[loader setMemoryCacheMaxBytes:50 * (1024 * 1024)]; //50MB
您可以使用以下方式清除内存:
UIImageLoader * loader = [UIImageLoader defaultLoader];
[loader purgeMemoryCache];
内存缓存不会在加载器之间共享,每个加载器将有自己的缓存。
当使用 UIImageLoader 访问图片时,文件的修改日期将被更新。
这些方法使用文件的修改日期来决定删除哪些内容。您可以使用这些方法确保经常使用的文件不会被删除。
- (void) clearCachedFilesModifiedOlderThan1Day;
- (void) clearCachedFilesModifiedOlderThan1Week;
- (void) clearCachedFilesModifiedOlderThan:(NSTimeInterval) timeInterval;
这些方法使用文件的创建日期来决定删除哪些内容。
- (void) clearCachedFilesCreatedOlderThan1Day;
- (void) clearCachedFilesCreatedOlderThan1Week;
- (void) clearCachedFilesCreatedOlderThan:(NSTimeInterval) timeInterval;
您可以使用以下方式清除整个磁盘缓存:
- (void) purgeDiskCache;
在 app 委托人中添加一些清理代码很容易。使用提供的任何方法,您可以在保留常用图片的同时保持磁盘缓存清洁。
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIImageLoader * loader = [UIImageLoader defaultLoader];
[loader clearCachedFilesModifiedOlderThan1Week];
}
对于返回 304 但不包含 Cache-Control 头(过期时间)的图片响应,默认行为是始终发送请求以检查新内容。即使有可用的缓存版本,也仍然会发送网络请求。
您可以为这种场景设置默认的缓存时间以停止这些请求。
myLoader.defaultCacheControlMaxAge = 604800; //1 week;
myLoader.defaultCacheControlMaxAge = 0; //(default) always send request to see if there's new content.
对于返回错误的图片响应,您可以在那些情况下进行配置。
您可以允许尝试多次检索收到错误响应的图像
myLoader.maxAttemptsForErrors = 3; (default) Allow three attempts to get the image.
myLoader.maxAttemptsforErrors = 1; Only allow one error before the cache takes effect.
您可以设置错误缓存的默认最大年龄
myLoader.defaultCacheControlMaxAgeForErrors = 604800; //1 week;
myLoader.defaultCacheControlMaxAgeForErrors = 0; //(default) always send request to try and get the image.
您可以自定义用于下载图像的NSURLSession,如下所示:
myLoader.session = myNSURLSession;
如果您自定义会话,请确保使用在后台线程上运行的会话
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
loader.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
如有必要,您需要负责实现其委托。如有必要,实现自签名的SSL证书的信任。
每个加载方法都返回用于网络请求的NSURLSessionDataTask。您可以忽略它,或者保留它。如果需要,取消请求时很有用。
这些扩展适用于 UIImage 和 NSImage
#if TARGET_OS_IPHONE
@interface UIImageView (UIImageLoader)
#elif TARGET_OS_MAC
@interface NSImageView (UIImageLoader)
#endif
#if TARGET_OS_IOS
//The views contentMode after the image has loaded.
- (void) uiImageLoader_setCompletedContentMode:(UIViewContentMode) completedContentMode;
#elif TARGET_OS_MAC
//The views image scaling value after the image has loaded.
- (void) uiImageLoader_setCompletedImageScaling:(NSImageScaling) imageScaling;
#endif
//Whether or not existing running download task should be canceled. You can safely
//ignore this if you want to let images download to be cached.
- (void) uiImageLoader_setCancelsRunningTask:(BOOL) cancelsRunningTask;
//Set a spinner instance. This is retained so you should set it to nil at some point.
- (void) uiImageLoader_setSpinner:(UIImageLoaderSpinner * _Nullable) spinner;
//Set the image with a URL.
- (void) uiImageLoader_setImageWithURL:(NSURL * _Nullable) url;
//Set the image with a URLRequest.
- (void) uiImageLoader_setImageWithRequest:(NSURLRequest * _Nullable) request;
@end
如果您需要支持自签名证书,可以使用(默认为false)。
myLoader.trustAnySSLCertificate = TRUE;
您可以通过以下方式设置默认的用户/密码,它会在每个请求中发送:
[myLoader setAuthUsername:@"username" password:@"password"];
为了实现平台间的兼容性,UIImageLoader 用一个 typedef 来切换图像类型。
// UIImageLoaderImage - typedef for ios/mac compatibility
#if TARGET_OS_IPHONE
typedef UIImage UIImageLoaderImage;
#elif TARGET_OS_MAC
typedef NSImage UIImageLoaderImage;
#endif
此示例取自仓库中的 DribbbleSample。
头文件
#import <UIKit/UIKit.h>
@interface DribbbleShotCell : UICollectionViewCell
@property IBOutlet UIImageView * imageView;
@property IBOutlet UIActivityIndicatorView * indicator;
- (void) setShot:(NSDictionary *) shot;
@end
实现
#import "DribbbleShotCell.h"
#import "UIImageLoader.h"
@interface DribbbleShotCell ()
@property BOOL cancelsTask;
@property NSURLSessionDataTask * task;
@property NSURL * activeImageURL;
@end
@implementation DribbbleShotCell
- (void) awakeFromNib {
//set to FALSE to let images download even if this cells image has changed while scrolling.
self.cancelsTask = FALSE;
//set to TRUE to cause downloads to cancel if a cell is being reused.
//self.cancelsTask = TRUE;
}
- (void) prepareForReuse {
self.imageView.image = nil;
if(self.cancelsTask) {
[self.task cancel];
}
}
- (void) setShot:(NSDictionary *) shot {
NSDictionary * images = shot[@"images"];
NSURL * url = [NSURL URLWithString:images[@"normal"]];
self.activeImageURL = url;
self.task = [[UIImageLoader defaultLoader] loadImageWithURL:url hasCache:^(UIImageLoaderImage *image, UIImageLoadSource loadedFromSource) {
//hide indicator as we have a cached image available.
self.indicator.hidden = TRUE;
//use cached image
self.imageView.image = image;
} sendingRequest:^(BOOL didHaveCachedImage) {
if(!didHaveCachedImage) {
//a cached image wasn't available, a network request is being sent, show spinner.
[self.indicator startAnimating];
self.indicator.hidden = FALSE;
}
} requestCompleted:^(NSError *error, UIImageLoaderImage *image, UIImageLoadSource loadedFromSource) {
//request complete.
//check if url above matches self.activeURL.
//If they don't match it means the request that finished was for a previous image. don't use it.
if(!self.cancelsTask && ![self.activeImageURL.absoluteString isEqualToString:url.absoluteString]) {
//NSLog(@"request finished, but images don't match.");
return;
}
//hide spinner
self.indicator.hidden = TRUE;
[self.indicator stopAnimating];
//if image was downloaded, use it.
if(loadedFromSource == UIImageLoadSourceNetworkToDisk) {
self.imageView.image = image;
}
}];
}
@end
MIT 许可协议(MIT)版权所有(c)2016 阿aron Smith
任何获得此软件及其相关文档文件(“软件”)副本的人均可免费用其软件,不受任何限制,包括但不限于使用、复制、修改、合并、发布、分发、许可和/或转让软件副本的权利,以及允许将软件提供给他人以供其使用,前提是遵守以下条件
上述版权声明和本许可声明应包括在软件的所有副本或主要部分中。
软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性保证。在任何情况下,作者或版权所有者对因合同、侵权或其他任何行为而产生的任何索赔、损害或其他责任不承担任何责任,这些索赔、损害或其他责任源于、产生于或与软件或软件的使用或其他交易有关。