目前很多應(yīng)用很多地方如頭像、背景等都喜歡大量使用帶圓角的圖片,若是簡(jiǎn)單粗暴的使用設(shè)置cornerRadius和maskTOBounds方法會(huì)引起大家都知道的離屏渲染從而帶來(lái)嚴(yán)重的性能問(wèn)題導(dǎo)致用戶滑動(dòng)界面會(huì)感受到明顯的卡頓。
一般來(lái)說(shuō)我們可以通過(guò)在后臺(tái)把方形圖片進(jìn)行重繪成帶圓角的继准,然后再主線程中去更新茅姜。這篇文章的重點(diǎn)在于繪制完這些圓角圖片后該怎么處理它們的問(wèn)題,關(guān)鍵詞是 “緩存與持久化”怕篷。
處理思路
1.png
RoundCornerManager
定義一個(gè)RoundCornerManager的單例,它也是按照上圖的思路去處理圓角圖片酗昼,圓角都需要用到圓角圖片的可以通過(guò)它去獲得廊谓。
@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;
}```
###圖片的內(nèi)存緩存
上面單例初始化中的內(nèi)存緩存使用的是第三方庫(kù)[YYCache](https://github.com/ibireme/YYCache)。這里使用了兩個(gè)cache麻削,一個(gè)cache是以圖片的URL為KEY去儲(chǔ)存圖片URL的MD5蹂析,一個(gè)cache是以圖片URL的MD5去儲(chǔ)存圖片本身。同時(shí)磁盤也是用圖片URL的MD5值去儲(chǔ)存圓角圖片
###圖片的磁盤緩存
上面單例初始化中調(diào)用checkAvatarCachePath方法是檢查是否已經(jīng)生成了放置圓角圖片的文件夾碟婆,沒有則生成對(duì)應(yīng)的文件夾电抚。
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *folderPath = [pathDocuments stringByAppendingPathComponent:@"avatar"];
self.roundCornerFolderPath = folderPath;
// 判斷文件夾是否存在,如果不存在竖共,則創(chuàng)建
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
JZLog(@"創(chuàng)建文件夾成功 %@", 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];///這個(gè)是生成指定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中蝙叛。
}
//獲得根據(jù)圖片URL生成的MD5,采用cache是因?yàn)椴挥妹看味家?jì)算圖片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是有已經(jīng)有圓角圖片
UIImage *img = [self.memoryCache objectForKey:componentMD5];
dispatch_async(dispatch_get_main_queue(), ^{
//JZLog(@"通過(guò)內(nèi)存緩存獲取圖片");
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(@"通過(guò)本地緩存獲取圖片");
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(@"通過(guò)sd下載");
completion(img);
});
}
[UIImagePNGRepresentation(img) writeToFile:completePath atomically:true]; //圓角圖片寫入磁盤
[self.memoryCache setObject:img forKey:componentMD5];//圓角圖片寫入內(nèi)存
});
}
}];
}
}
}
}
//繪制圓角圖片
- (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使用圓角圖片管理單例對(duì)外提供獲取圓角圖片的功能公给。
@implementation UIImageView (JZ)
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(sentinel, setSentinel, RETAIN, JZSentinel *) //使用runtime實(shí)現(xiàn)category屬性的宏借帘,自定義的UIImageView的設(shè)置圓角圖片的次數(shù)統(tǒng)計(jì)
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(jzImageURL, setJzImageURL, COPY, NSString *)//使用runtime實(shí)現(xiàn)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設(shè)置圖片的次數(shù)加1
@weakify(self);
//下面通過(guò)圓角圖片單例獲得圓角圖片
[[JZAvatarManager shareInstance] getAvatar:url type:AvatarTypeRoundCorner completion:^(UIImage *img) {
@strongify(self);
if (!self) {return;}
if (self.sentinel.value == value) { //從請(qǐng)求圓角圖片到獲得的過(guò)程中這個(gè)UIImageView沒有被再次設(shè)置圖片
self.image = img;
}
}];
}