能够异步加载图片的UIImageView,通过调用方法loadImageWithUrl:与loadImageWithUrl:andDefaultImage:来进行异步加载。用到了NSCache、文件缓存、NSOperation、NSQueue来完成。首先是头文件的定义
- //定义缓存地址的宏
- #define kCIVCache [NSHomeDirectory() stringByAppendingString:@"/Library/Caches/CIVCache.txt"]
定义加载Operation接口
- @protocol AsyImageLoadOperation <NSObject>
- - (void)cancel;
- @end
定义单例队列Manager
- @interface QueueManager : NSObject
- +(id)sharedManager;
- @property (nonatomic,retain) NSOperationQueue *queue;
- @end
定义缓存模型
- @interface CIVImageCache : NSObject
- @property (strong,nonatomic) NSURL *url;
- @property (strong,nonatomic) NSData *imageData;
- @end
定义图片加载Operation
- @interface ImageLoadOperation : NSOperation<AsyImageLoadOperation>
- -(id)initWithUrl:(NSURL *)url;
- @property (nonatomic,strong) NSURL *url;
- @property (nonatomic,strong) CIVImageCache *resultCache;
- @end
定义AsyImageView
- @interface AsyImageView : UIImageView
- -(void)loadImageWithUrl:(NSURL *)url;
- -(void)loadImageWithUrl:(NSURL *)url andDefultImage:(UIImage *)image;
- @end
接着就是实现头文件,.m文件中需要引入
- #import "objc/runtime.h"
- #include <sys/sysctl.h>
定义静态的operationKey字符变量
- static char operationKey;
QueueManager的实现
- @implementation QueueManager
- unsigned int countOfCores() {
- unsigned int ncpu;
- size_t len = sizeof(ncpu);
- sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
- return ncpu;
- }
- static QueueManager *instance;
- +(id)sharedManager{
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- instance=[[QueueManager alloc] init];
- });
- return instance;
- }
- -(id)init{
- self=[super init];
- if (self) {
- _queue=[[NSOperationQueue alloc] init];
- _queue.maxConcurrentOperationCount=countOfCores();
- }
- return self;
- }
countOfCores方法用来获取当前机器的CPU核数。在init初始化中初始化一个NSOperationQueue,并为这个队列指定最大并发操作数(最好是CPU有多少核就设置为多少的最大并发数)。单例的实现使用GCD的dispatch_once。
实现缓存模型对象
- @implementation CIVImageCache
- -(id)init{
- self=[super init];
- if (self){
- _imageData=[[NSData alloc] init];
- _url=[[NSURL alloc] init];
- }
- return self;
- }
- -(id)initWithCoder:(NSCoder *)aDecoder{
- self=[super init];
- if (self){
- _imageData = [aDecoder decodeObjectForKey:@"_imageData"];
- _url = [aDecoder decodeObjectForKey:@"_url"];
- }
- return self;
- }
- -(void)encodeWithCoder:(NSCoder *)aCoder{
- [aCoder encodeObject:_imageData forKey:@"_imageData"];
- [aCoder encodeObject:_url forKey:@"_url"];
- }
- -(id)copyWithZone:(NSZone *)zone{
- CIVImageCache *uObject=[[[self class] allocWithZone:zone] init];
- uObject.imageData=self.imageData;
- uObject.url=self.url;
- return uObject;
- }
- @end
这个没有什么好说的,这个对象主要用来在加载完成后传递数据以及将数据保存在本地。 接下来实现图片加载操作(ImageLoadOperation)
- @implementation ImageLoadOperation
- -(id)initWithUrl:(NSURL *)url{
- self=[super init];
- if (self) {
- _url=url;
- }
- return self;
- }
- -(void)main{
- NSData *cacheData=[NSData dataWithContentsOfFile:kCIVCache];
- NSDictionary *cacheDic=[NSKeyedUnarchiver unarchiveObjectWithData:cacheData];
- if (cacheDic!=nil) {
- if ([[cacheDic allKeys] containsObject:_url.description]) {
- CIVImageCache *cache=[[CIVImageCache alloc] init];
- cache.url=_url;
- cache.imageData=[cacheDic objectForKey:_url.description];
- _resultCache=cache;
- }else{
- [self loadFromInternet];
- }
- }else{
- [self loadFromInternet];
- }
- }
- -(void)loadFromInternet{
- NSData *imageData=[NSData dataWithContentsOfURL:_url];
- UIImage *image=[UIImage imageWithData:imageData];
- imageData = UIImageJPEGRepresentation(image, 0.0000001);
- CIVImageCache *cache=[[CIVImageCache alloc] init];
- cache.url=_url;
- cache.imageData=imageData;
- _resultCache=cache;
- }
- @end
main函数中为主要的加载操作,首先从本地缓存中获取数据,判断是否已经存在URL的请求缓存,如果没有调用loadFromInternet方法从网络下载图片。
最后来实现异步
- @interface AsyImageView ()
- @property (nonatomic,weak) NSOperation *lastOperation;
- @property (strong, nonatomic) NSCache *memCache;
- @property (assign, nonatomic) dispatch_queue_t ioQueue;
- @end
定义AsyImageView的“私有”变量,lastOperation用来关联operation的顺序(这个最后好像没有用到)memCache则用来进行缓存,ioQueue是用来缓存文件的操作队列。
AsyImageView使用两种初始化方法来初始化:
- -(id)init{
- self=[super init];
- if (self) {
- _ioQueue = dispatch_queue_create("com.noez.AsyImageCache", DISPATCH_QUEUE_SERIAL);
- NSString *fullNamespace = [@"com.noez.AsyImageCache." stringByAppendingString:@"1"];
- _memCache = [[NSCache alloc] init];
- _memCache.name = fullNamespace;
- }
- return self;
- }
- -(id)initWithFrame:(CGRect)frame{
- self=[super initWithFrame:frame];
- if (self) {
- _ioQueue = dispatch_queue_create("com.noez.AsyImageCache", DISPATCH_QUEUE_SERIAL);
- NSString *fullNamespace = [@"com.noez.AsyImageCache." stringByAppendingString:@"1"];
- _memCache = [[NSCache alloc] init];
- _memCache.name = fullNamespace;
- }
- return self;
- }
在AsyImageView中的layoutSubviews中也需要初始化ioQueue与memCache,如果是直接在XIB中直接设置AsyImageView的话并不会调用两个初始化方法。
loadImageWithUrl方法中开始异步的加载过程:
- -(void)loadImageWithUrl:(NSURL *)url{
- [self cancelCurrentImageLoad];
- NSOperationQueue *queue=[[QueueManager sharedManager] queue];
- __weak AsyImageView *weakSelf=self;
- id<AsyImageLoadOperation> operation=[self downloadWithURL:url queue:queue completed:^(UIImage *image, NSData *data, NSError *error, BOOL isFinished) {
- void (^block)(void) = ^{
- __strong AsyImageView *sself = weakSelf;
- if (!sself) return;
- if (image){
- sself.image = image;
- [sself setNeedsLayout];
- }
- };
- if ([NSThread isMainThread]){
- block();
- }
- else{
- dispatch_sync(dispatch_get_main_queue(), block);
- }
- }];
- objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
在这个方法中首先调用cancelCurrentImageLoad来取消当前的加载操作:
- - (void)cancelCurrentImageLoad
- {
- // Cancel in progress downloader from queue
- id<AsyImageLoadOperation> operation = objc_getAssociatedObject(self, &operationKey);
- if (operation)
- {
- [operation cancel];
- objc_setAssociatedObject(self, &operationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- }
之后获取单例的操作队列后调用downloadWithURL方法开始异步加载,加载完之后通过block将图片赋值给image属性。
- -(id<AsyImageLoadOperation>)downloadWithURL:(NSURL *)url queue:(NSOperationQueue *)queue completed:(void (^)(UIImage *image, NSData *data, NSError *error, BOOL isFinished))completedBlock{
- ImageLoadOperation *op=[[ImageLoadOperation alloc] init];
- [self queryDiskCacheForKey:url.description done:^(UIImage *image) {
- if (image==nil) {
- op.url=url;
- __weak ImageLoadOperation *weakOp=op;
- op.completionBlock=^{
- CIVImageCache *cache=weakOp.resultCache;
- UIImage *dimage=[UIImage imageWithData:cache.imageData];
- completedBlock(dimage,nil,nil,YES);
- [self storeImage:dimage imageData:cache.imageData forKey:cache.url.description toDisk:YES];
- };
- [self.lastOperation addDependency:op];//待定
- self.lastOperation=op;
- [queue addOperation:op];
- }else{
- completedBlock(image,nil,nil,YES);
- }
- }];
- return op;
- }
在加载前首先调用queryDiskCacheForKey方法从缓存中获取图片,如果缓存中没有图片,则使用图片加载操作加载图片,在操作完成时使用block保存图片并调用completedBlock显示图片。如果缓存中有图片则直接调用completedBlock显示图片。一下分别是保存图片与从缓存获取图片的方法:
- - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk{
- if (!image || !key){
- return;
- }
- [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale];
- if (toDisk){
- dispatch_async(self.ioQueue, ^{
- NSData *data = imageData;
- if (!data){
- if (image){
- data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
- }
- }
- if (data){
- NSData *cacheData=[NSData dataWithContentsOfFile:kCIVCache];
- if (cacheData==nil) {
- cacheData=[[NSData alloc] init];
- }
- NSMutableDictionary *dic=[NSKeyedUnarchiver unarchiveObjectWithData:cacheData];
- if (dic==nil) {
- dic=[[NSMutableDictionary alloc] init];
- }
- if (![[dic allKeys] containsObject:key]) {
- [dic setObject:data forKey:key];
- }
- NSData *data=[NSKeyedArchiver archivedDataWithRootObject:dic];
- [data writeToFile:kCIVCache atomically:YES];
- }
- });
- }
- }
- - (void)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image))doneBlock{
- if (!doneBlock) return;
- if (!key){
- doneBlock(nil);
- return;
- }
- UIImage *image = [self imageFromMemoryCacheForKey:key];
- if (image){
- doneBlock(image);
- return;
- }
- if (_ioQueue==nil) {
- _ioQueue = dispatch_queue_create("com.noez.AsyImageCache", DISPATCH_QUEUE_SERIAL);
- }
- dispatch_async(self.ioQueue, ^{
- @autoreleasepool{
- UIImage *diskImage = [self diskImageForKey:key];
- if (diskImage){
- CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;
- [self.memCache setObject:diskImage forKey:key cost:cost];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- doneBlock(diskImage);
- });
- }
- });
- }
- - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
- return [self.memCache objectForKey:key];
- }
转自: http://segmentfault.com/a/1190000000313657?page=1