iOS开发笔记--异步加载UIImageView----AsyImageView

来源:互联网 发布:网络电视看不了直播 编辑:程序博客网 时间:2024/06/16 00:01

能够异步加载图片的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 QueueManagerunsigned 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

0 0
原创粉丝点击