UIImageLoader 1.2.0

UIImageLoader 1.2.0

测试测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最后发布2017年6月

Aaron Smith 维护。



  • 作者
  • Aaron Smith

UIImageLoader 是一个从网络加载图片的助手。它在磁盘上缓存图片,并且可选地在内存中。

它使得编写对缓存的图片处理代码、设置占位符图片或展示加载动画,以及处理请求完成时出现的任何错误变得非常简单。

它支持服务器缓存控制,在过期时重新下载图片。出于性能考虑,缓存控制逻辑是手动实现的,而不是使用 NSURLCache。

您也可以完全忽略服务器缓存控制,并手动清理图片。

它与 iOS 和 Mac 兼容。并且体积很小,大约在一个头文件/实现文件中有 600 多行代码。

所有操作都是异步的,并使用现代 Objective-C 与 libdispatch 和 NSURLSession。

Swift 兼容性

是的。只需将 UIImageLoader.h 导入您的 Project-bridge-header.h 文件。

服务器缓存控制

它支持带有 Cache-Control 最大存活时间、ETag 和 Last-Modified 标头的响应。

它以 If-None-Match 和 If-Modified-Since 发送请求。

如果服务器没有响应 Cache-Control 标头,您可以设置一个默认的缓存控制最大存活时间,以便缓存图片指定的时间。

如果响应状态为 304,它使用磁盘上可用的缓存图片。

4XX & 5XX 响应

对于 4XX 和 5XX 响应,您可以指定获取图片时允许尝试的次数,和一个缓存控制最大存活时间 - 以防止发生错误时发送相同的请求。

安装

  • 下载本仓库的 zip 文件
  • 将 UIImageLoader.h 和 UIImageLoader.m 添加到您的 Xcode 项目中

Dribbble 示例

有一个非常简单的 iOS/Mac 示例应用程序,展示了将图片加载到集合视图中。

该应用程序从 Dribbble 加载 1000 张图片。

该应用程序展示了如何设置单元格以优雅地处理

  • 下载图片
  • 使用旋转器展示加载活动
  • 在单元格复用时取消图片下载
  • 或者让图片下载完成以便缓存

sample screenshots

UIImageLoader 对象

有一个默认配置的加载器,您可以根据自己的需要配置。

//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 未修改的图片

对于返回 304 但不包含 Cache-Control 头(过期时间)的图片响应,默认行为是始终发送请求以检查新内容。即使有可用的缓存版本,也仍然会发送网络请求。

您可以为这种场景设置默认的缓存时间以停止这些请求。

myLoader.defaultCacheControlMaxAge = 604800; //1 week;
myLoader.defaultCacheControlMaxAge = 0;      //(default) always send request to see if there's new content.

4XX & 5XX 错误

对于返回错误的图片响应,您可以在那些情况下进行配置。

您可以允许尝试多次检索收到错误响应的图像

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

您可以自定义用于下载图像的NSURLSession,如下所示:

myLoader.session = myNSURLSession;

如果您自定义会话,请确保使用在后台线程上运行的会话

NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
loader.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];

如有必要,您需要负责实现其委托。如有必要,实现自签名的SSL证书的信任。

NSURLSessionDataTask

每个加载方法都返回用于网络请求的NSURLSessionDataTask。您可以忽略它,或者保留它。如果需要,取消请求时很有用。

其他有用功能

UIImage 和 NSImage 扩展。

这些扩展适用于 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

SSL

如果您需要支持自签名证书,可以使用(默认为false)。

myLoader.trustAnySSLCertificate = TRUE;

Auth Basic 密码保护目录/图像

您可以通过以下方式设置默认的用户/密码,它会在每个请求中发送:

[myLoader setAuthUsername:@"username" password:@"password"];

UIImageLoaderImage 用于 Mac OS X

为了实现平台间的兼容性,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

任何获得此软件及其相关文档文件(“软件”)副本的人均可免费用其软件,不受任何限制,包括但不限于使用、复制、修改、合并、发布、分发、许可和/或转让软件副本的权利,以及允许将软件提供给他人以供其使用,前提是遵守以下条件

上述版权声明和本许可声明应包括在软件的所有副本或主要部分中。

软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性和非侵权性保证。在任何情况下,作者或版权所有者对因合同、侵权或其他任何行为而产生的任何索赔、损害或其他责任不承担任何责任,这些索赔、损害或其他责任源于、产生于或与软件或软件的使用或其他交易有关。