可以異步加載圖片的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
定義圖片加載Operationasync
@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文件中須要引入atom
#import "objc/runtime.h" #include <sys/sysctl.h>
定義靜態的operationKey字符變量url
static char operationKey;
QueueManager的實現spa
@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。code
實現緩存模型對象
@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); }); } }); }