目前很多應(yīng)用很多地方如頭像、背景等都喜歡大量使用帶圓角的圖片,若是簡單粗暴的使用設(shè)置cornerRadius和maskTOBounds方法會引起大家都知道的離屏渲染從而帶來嚴重的性能問題導(dǎo)致用戶滑動界面會感受到明顯的卡頓。
一般來說我們可以通過在后臺把方形圖片進行重繪成帶圓角的,然后再主線程中去更新。這篇文章的重點在于繪制完這些圓角圖片后該怎么處理它們的問題,關(guān)鍵詞是 “緩存與持久化”。
處理思路
1.png
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;
}```
###圖片的內(nèi)存緩存
上面單例初始化中的內(nèi)存緩存使用的是第三方庫[YYCache](https://github.com/ibireme/YYCache)。這里使用了兩個cache,一個cache是以圖片的URL為KEY去儲存圖片URL的MD5,一個cache是以圖片URL的MD5去儲存圖片本身。同時磁盤也是用圖片URL的MD5值去儲存圓角圖片
###圖片的磁盤緩存
上面單例初始化中調(diào)用checkAvatarCachePath方法是檢查是否已經(jīng)生成了放置圓角圖片的文件夾,沒有則生成對應(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];///這個是生成指定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是因為不用每次都要計算圖片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(@"通過內(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(@"通過本地緩存獲取圖片");
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];//圓角圖片寫入內(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使用圓角圖片管理單例對外提供獲取圓角圖片的功能。
@implementation UIImageView (JZ)
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(sentinel, setSentinel, RETAIN, JZSentinel *) //使用runtime實現(xiàn)category屬性的宏,自定義的UIImageView的設(shè)置圓角圖片的次數(shù)統(tǒng)計
JZSYNTH_DYNAMIC_PROPERTY_OBJECT(jzImageURL, setJzImageURL, COPY, NSString *)//使用runtime實現(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);
//下面通過圓角圖片單例獲得圓角圖片
[[JZAvatarManager shareInstance] getAvatar:url type:AvatarTypeRoundCorner completion:^(UIImage *img) {
@strongify(self);
if (!self) {return;}
if (self.sentinel.value == value) { //從請求圓角圖片到獲得的過程中這個UIImageView沒有被再次設(shè)置圖片
self.image = img;
}
}];
}