目前很多应用很多地方如头像、背景等都喜欢大量使用带圆角的图片,若是简单粗暴的使用设置cornerRadius和maskTOBounds方法会引起大家都知道的离屏渲染从而带来严重的性能问题导致用户滑动界面会感受到明显的卡顿。
一般来说我们可以通过在后台把方形图片进行重绘成带圆角的,然后再主线程中去更新。这篇文章的重点在于绘制完这些圆角图片后该怎么处理它们的问题,关键词是 “缓存与持久化”。
处理思路
RoundCornerManager
定义一个RoundCornerManager的单例,它也是按照上图的思路去处理圆角图片,圆角都需要用到圆角图片的可以通过它去获得。
@interface JZAvatarManager()
@property (nonatomic, copy) NSString *roundCornerFolderPath;
@property (nonatomic, strong) dispatch_queue_t conQueue;
@property (nonatomic, strong) YYMemoryCache *memoryCache;
@property (nonatomic, strong) YYMemoryCache *md5Cache;
@end
@implementation JZAvatarManager
static JZAvatarManager *_instance;
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [JZAvatarManager new];
});
return _instance;
}
- (instancetype)init {
self = [super init];
self.conQueue = dispatch_queue_create("cn.n8n8.circle.Avatar", DISPATCH_QUEUE_CONCURRENT);
self.memoryCache = [YYMemoryCache new];
self.memoryCache.shouldRemoveAllObjectsOnMemoryWarning = true;
self.memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = false;
self.memoryCache.releaseOnMainThread = true;
self.memoryCache.name = @"avatarCache";
self.md5Cache = [YYMemoryCache new];
self.md5Cache.shouldRemoveAllObjectsOnMemoryWarning = false;
self.md5Cache.shouldRemoveAllObjectsWhenEnteringBackground = false;
self.md5Cache.name = @"md5Cache";
[self checkAvatarCachePath];
return self;
}```
###图片的内存缓存
上面单例初始化中的内存缓存使用的是第三方库[YYCache](https://github.com/ibireme/YYCache)。这里使用了两个cache,一个cache是以图片的URL为KEY去储存图片URL的MD5,一个cache是以图片URL的MD5去储存图片本身。同时磁盘也是用图片URL的MD5值去储存圆角图片
###图片的磁盘缓存
上面单例初始化中调用checkAvatarCachePath方法是检查是否已经生成了放置圆角图片的文件夹,没有则生成对应的文件夹。
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *folderPath = [pathDocuments stringByAppendingPathComponent:@"avatar"];
self.roundCornerFolderPath = folderPath;
// 判断文件夹是否存在,如果不存在,则创建
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
JZLog(@"创建文件夹成功 %@", folderPath);
[fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
[fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
}```
单例的处理流程
-(void)getAvatar:(NSString *)url type:(AvatarType)type completion:(void (^)(UIImage *))completion {
static dispatch_once_t onceToken;
static NSString *avatarSizeStr;
dispatch_once(&onceToken, ^{
CGFloat scale =[[UIScreen mainScreen] scale];
NSInteger width = 50 * scale;
NSInteger height = 50 * scale;
avatarSizeStr = [NSString stringWithFormat:@"?imageView2/1/w/%ld/h/%ld",width,height];
});
NSString *newURL = [NSString stringWithFormat:@"%@%@",url,avatarSizeStr];///这个是生成指定size的图片URL
NSURL *imgURL = [NSURL URLWithString:newURL];
NSString *componentMD5;
if ([self.md5Cache containsObjectForKey:newURL]) {
componentMD5 = [self.md5Cache objectForKey:newURL];
} else {
componentMD5 = [newURL jz_md5String];
[self.md5Cache setObject:componentMD5 forKey:newURL]; //图片URL的MD5放入cache中。
}
//获得根据图片URL生成的MD5,采用cache是因为不用每次都要计算图片URL的MD5
if (type == AvatarTypeOrigin) {
[[SDWebImageManager sharedManager] downloadImageWithURL:imgURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(image);
});
}
}];
} else {
NSString *completePath = [self.roundCornerFolderPath stringByAppendingPathComponent:componentMD5];
if ([self.memoryCache containsObjectForKey:componentMD5]) { //查询cache是有已经有圆角图片
UIImage *img = [self.memoryCache objectForKey:componentMD5];
dispatch_async(dispatch_get_main_queue(), ^{
//JZLog(@"通过内存缓存获取图片");
completion(img);
});
} else {
if ([[NSFileManager defaultManager] fileExistsAtPath:completePath]) { //查询本地是否有圆角图片
NSData *data = [NSData dataWithContentsOfFile:completePath];
UIImage *img = [UIImage imageWithData:data];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
//JZLog(@"通过本地缓存获取图片");
completion(img);
[self.memoryCache setObject:img forKey:componentMD5];
});
}
} else {
[[SDWebImageManager sharedManager] downloadImageWithURL:imgURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image && finished) {
dispatch_async(self.conQueue, ^{
UIImage *img = [self AddRoundCornerToImage:image];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
//JZLog(@"通过sd下载");
completion(img);
});
}
[UIImagePNGRepresentation(img) writeToFile:completePath atomically:true]; //圆角图片写入磁盘
[self.memoryCache setObject:img forKey:componentMD5];//圆角图片写入内存
});
}
}];
}
}
}
}
//绘制圆角图片
- (UIImage *)AddRoundCornerToImage: (UIImage *)source {
CGFloat w = source.size.width;
CGFloat h = source.size.height;
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat cornerRadius = MIN(w, h) / 2.;
CGRect newImgRect = CGRectMake((w - MIN(w, h))/2, 0, MIN(w, h), MIN(w, h));
CGImageRef newCGImg = CGImageCreateWithImageInRect(source.CGImage, newImgRect);
UIImage *newImg = [UIImage imageWithCGImage:newCGImg];
CGImageRelease(newCGImg);
UIImage *roundImage = nil;
CGRect imageFrame = CGRectMake(0.0, 0.0, newImg.size.width, newImg.size.height);
UIGraphicsBeginImageContextWithOptions(newImg.size, NO, scale);
[[UIBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:cornerRadius] addClip];
[newImg drawInRect:imageFrame];
roundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundImage;
}
提供便利的Category获取圆角图片
我们可以利用UIImageView或UIButton的Category使用圆角图片管理单例对外提供获取圆角图片的功能。
@implementation UIImageView (JZ)
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(sentinel, setSentinel, RETAIN, JZSentinel *) //使用runtime实现category属性的宏,自定义的UIImageView的设置圆角图片的次数统计
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(jzImageURL, setJzImageURL, COPY, NSString *)//使用runtime实现category属性的宏,自定义的UIImageView的原地址的属性
-(void)jz_setRoundCornerAvatarImageWithURL:(NSString *)url placeHolderImage:(UIImage *)placeHolder {
dispatch_async(dispatch_get_main_queue(), ^{
if (placeHolder) {
self.image = placeHolder;
} else {
self.image = [UIImage imageNamed:@"placeHolder_avatar"];
}
});
if (!self.sentinel) {
self.sentinel = [JZSentinel new];
}
int32_t value = [self.sentinel increase]; //UIImageView设置图片的次数加1
@weakify(self);
//下面通过圆角图片单例获得圆角图片
[[JZAvatarManager shareInstance] getAvatar:url type:AvatarTypeRoundCorner completion:^(UIImage *img) {
@strongify(self);
if (!self) {return;}
if (self.sentinel.value == value) { //从请求圆角图片到获得的过程中这个UIImageView没有被再次设置图片
self.image = img;
}
}];
}