资讯专栏INFORMATION COLUMN

[iOS]异步加载UIImageView----AsyImageView

付永刚 / 392人阅读

摘要:能够异步加载图片的,通过调用方法与来进行异步加载。单例的实现使用的。实现缓存模型对象这个没有什么好说的,这个对象主要用来在加载完成后传递数据以及将数据保存在本地。

能够异步加载图片的UIImageview,通过调用方法loadImageWithUrl:与loadImageWithUrl:andDefaultImage:来进行异步加载。用到了NSCache、文件缓存、NSOperation、NSQueue来完成。
首先是头文件的定义

定义缓存地址的宏

#define kCIVCache [NSHomeDirectory() stringByAppendingString:@"/Library/Caches/CIVCache.txt"]

定义加载Operation接口

@protocol AsyImageLoadOperation 
- (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
-(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 

定义静态的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 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 operation = objc_getAssociatedObject(self, &operationKey);
    if (operation)
    {
        [operation cancel];
        objc_setAssociatedObject(self, &operationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

之后获取单例的操作队列后调用downloadWithURL方法开始异步加载,加载完之后通过block将图片赋值给image属性。

-(id)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);
                                          });
                       }
                   });
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/15386.html

相关文章

  • iOS 异步图片加载优化与常用开源库分析

    摘要:异步任务一般有种实现方式这几种方式就不细说了,是通过自定义来抽象下载任务的,并结合了来做一些主线程与子线程的切换。关于图片解压缩通用的解压缩方案主体的思路是在子线程,将原始的图片渲染成一张的新的可以字节显示的图片,来获取一个解压缩过的图片。 1. 网络图片显示大体步骤: 下载图片 图片处理(裁剪,边框等) 写入磁盘 从磁盘读取数据到内核缓冲区 从内核缓冲区复制到用户空间(内存级别拷...

    Sunxb 评论0 收藏0
  • iOS图片缓存库基准对比

    摘要:一个强大的图像缓存需包含以下部分异步下载图像,尽可能减少使用主线程队列使用后台队列解压图像。 1. 引言 过去的几年里,iOS 应用在视觉方面越来越吸引人。图像展示是其中很关键的部分,因为大部分图像展示都需要下载并且渲染。大部分开发者都要使用图像填充表格视图(table views)或者集合视图(collection views)。下载图片消耗一些资源(如蜂窝数据、电池以及 CPU ...

    LeviDing 评论0 收藏0
  • iOS 开发一定要尝试的 Texture(ASDK)

    摘要:原文链接开发一定要尝试的排版正常包含视频前言本篇所涉及的性能问题我都将根据滑动的流畅性来评判包括掉帧情况和一些实际体验已经改名为我习惯称作编译环境参与测试机型默认包含的默认复杂程度一般包含张图片和条文本展示图片有圆角列表滑动卡顿的原因及优 原文链接 - iOS 开发一定要尝试的 Texture(ASDK)(排版正常, 包含视频) 前言 本篇所涉及的性能问题我都将根据滑动的流畅性来评判...

    levinit 评论0 收藏0
  • 关于数据异步加载引起Iscroll.js布局出错的解决方案

    摘要:今天要说的就是用实现局部滚动出现页面布局的问题。问题页面底部多出一部分或是页面显示不全,拉动回弹后内容又显示不全。原因数据异步加载,无法正确获取页面元素的真实高度。自定义方法请求成功切换页面后刷新解决异步加载数据布局出错或 相信对于前端攻城狮来说Iscroll.js,大家并不陌生,lite版本只有24kb。但可以解决 1、position:fixed在IOS端的兼容性问题 >移动端vi...

    MonoLog 评论0 收藏0
  • 关于数据异步加载引起Iscroll.js布局出错的解决方案

    摘要:今天要说的就是用实现局部滚动出现页面布局的问题。问题页面底部多出一部分或是页面显示不全,拉动回弹后内容又显示不全。原因数据异步加载,无法正确获取页面元素的真实高度。自定义方法请求成功切换页面后刷新解决异步加载数据布局出错或 相信对于前端攻城狮来说Iscroll.js,大家并不陌生,lite版本只有24kb。但可以解决 1、position:fixed在IOS端的兼容性问题 >移动端vi...

    sixleaves 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<