一:相對簡便的圓角圖片的實現(xiàn)方式
imageView.layer.cornerRadius = CGFloat(10);
imageView.layer.masksToBounds = YES;
由于這樣的處理機制是GPU在當(dāng)前緩沖區(qū)以外新開辟一個渲染緩沖區(qū)進行工作,也就是我們常說的離屏渲染;
這會給我們帶來額外的性能消耗,如果這樣的圓角達到一定數(shù)量,會觸發(fā)渲染緩沖區(qū)的頻煩合并和上下文的頻繁切換,性能的代價會很明顯的表現(xiàn)在用戶體驗上,因為這些效果均被認為不能直接呈現(xiàn)于屏幕,而需要在別的地方做額外的處理預(yù)合成。具體的檢測我們可以使用Instruments的CoreAnimation
二: 使用ZYCornerRadius解決設(shè)置圓角時GPU會觸發(fā)離屏渲染的解決思路的問題
先上一張性能對比圖
測試設(shè)備6P,屏幕中有40張尺寸為20*20的小圖片,使用masksToBounds切角處理時幀率大大下降至20+,使用ZYCornerRadius時幀率保持在57+,性能接近0損耗

另外內(nèi)存使用的對比(使用ZYCornerRadius處理大量的圓角圖片幾乎沒有帶來內(nèi)存增長):

既然我們要避免讓GPU觸發(fā)離屏,那么只能把兵符交給CPU,雖然CPU對圖形的處理能力不及GPU,但由于這種處理的難度不大,且代價肯定遠小于上下文切換。
其實一開始的想法就是從-drawRect下手,但是看了某篇文章(找不回來了)后打消了這個念頭,-drawRect的確存在很多性能坑。
思路
既然不能讓控件masksToBounds,ZYCornerRadius就從圖片本身下手,我使用在UIKit中對Core Graphics有一定封裝的應(yīng)用層類UIBezierPath,對圖片進行破壞性的切角,破壞性僅僅是對切去部分而言,當(dāng)然這操作是在CPU內(nèi)完成的,而后我只需要取到處理完成的bitmap(可為UIImage對象)交給GPU顯示于屏幕即可。
/**
* @brief clip the cornerRadius with image, UIImageView must be setFrame before, no off-screen-rendered
*/
- (void)zy_cornerRadiusWithImage:(UIImage *)image cornerRadius:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType {
CGSize size = self.bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, YES, scale);
if (nil == UIGraphicsGetCurrentContext()) {
return;
}
UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:rectCornerType cornerRadii:cornerRadii];
[cornerPath addClip];
[image drawInRect:self.bounds];
id processedImageRef = (__bridge id _Nullable)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = processedImageRef;
});
});
}
可見,我對圖片進行了切角處理后,將得到的含圓角UIImage通過-setImage傳給了UIImageView。操作沒有觸發(fā)GPU離屏渲染,過程在CPU內(nèi)完成,而后我在Demo中證實了這個方法。
順便一提這里還存在一個性能問題,==Color Blended Layers==,UIGraphicsBeginImageContextWithOptions(<#CGSize size#>, <#BOOL opaque#>, <#CGFloat scale#>)的第二個參數(shù)是透明通道的開關(guān),true則為不透明。以下兩張圖是參數(shù)傳NO or YES在模擬器中
左邊圖片傳入的參數(shù)為YES,右邊的圖片傳入的參數(shù)為NO

打開打開了Color Blended Layers Debug所看見的區(qū)別:

一些沒有被設(shè)置為opacity的圖層,因為透明通道的存在,系統(tǒng)需要去計算圖層堆疊后像素點的真實顏色,在Instruments的測試中也是可以高亮標顯出來,這種性能的損耗程度我還沒有專門去測試。但是在上圖可以看見如果設(shè)置為不包含透明通道,我們圖片被剪去的部分就沒有了顏色(黑漆漆一片),這里使用的解決方案就是在圖片上下文中先畫一層backgroundColor,缺點就是需要傳入:
/**
* @brief clip the cornerRadius with image, draw the backgroundColor you want, UIImageView must be setFrame before, no off-screen-rendered
*/
- (void)zy_cornerRadiusWithImage:(UIImage *)image cornerRadius:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType backgroundColor:(UIColor *)backgroundColor {
CGSize size = self.bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, YES, scale);
if (nil == UIGraphicsGetCurrentContext()) {
return;
}
UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:rectCornerType cornerRadii:cornerRadii];
UIBezierPath *backgroundRect = [UIBezierPath bezierPathWithRect:self.bounds];
[backgroundColor setFill];
[backgroundRect fill];
[cornerPath addClip];
[image drawInRect:self.bounds];
id processedImageRef = (__bridge id _Nullable)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = processedImageRef;
});
});
}
傳入紅色的背景顏色,打開Color Blended Layers Debug與原先對比:

實際生產(chǎn)
目前我們解決了離屏渲染的問題,可這并不符合實際生產(chǎn),在app中顯示的網(wǎng)絡(luò)圖片我們不可能事先知道并且調(diào)用
- (void)zy_cornerRadiusWithImage:cornerRadius:rectCornerType:
來進行切角,也不可能每次都還要寫個SDWedImage的complete回調(diào)去做這個操作,我決定用swizzleMethod的辦法來處理
我們把對self.image切角處理放在每次layoutSubviews的時候完成,大家看到這里頓時把我臭罵了一頓。。。在Category里重寫-layoutSubviews的致命的,這的確會導(dǎo)致整個項目下所有的UIImageView都會去執(zhí)行這個山寨的-layoutSubviews,別慌關(guān)掉文章,給個機會繼續(xù)看下去。
首先我們需要將使用者傳入的切角參數(shù)保存起來,供-layoutSubviews切角時使用,因為category不支持擴展屬性,所以我們可以用runtime來做:
/**
* @brief set cornerRadius for UIImageView, no off-screen-rendered
*/
- (void)zy_cornerRadiusAdvance:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType {
objc_setAssociatedObject(self, &kRadius, @(cornerRadius), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &kRoundingCorners, @(rectCornerType), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, &kIsRounding, @(0), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.class swizzleMethod:@selector(layoutSubviews) anotherMethod:@selector(zy_LayoutSubviews)];
}
細心的朋友可以看見上面這段代碼里的+swizzleMethod,我將調(diào)用了- (void)zy_cornerRadiusAdvance:cornerRadius:rectCornerType:的UIImageView對象的-layoutSubviews方法的實現(xiàn)轉(zhuǎn)移到了我自己的方法-zy_LayoutSubviews上,也就是說我不需要去重寫-layoutSubviews,而主動調(diào)用過-zy_cornerRadiusAdvance的UIImageView對象的-layoutSubviews的實現(xiàn)卻被我換成了-zy_LayoutSubviews,源代碼在Demo中有。ok,于是在-zy_LayoutSubviews中收官:
- (void)zy_LayoutSubviews {
[super layoutSubviews];
NSNumber *radius = objc_getAssociatedObject(self, &kRadius);
NSNumber *roundingCorners = objc_getAssociatedObject(self, &kRoundingCorners);
[self zy_cornerRadiusWithImage:self.image cornerRadius:radius.floatValue rectCornerType:roundingCorners.unsignedLongValue];
}
同時結(jié)合KVO維持切角效果做持久化:
UIImageView的內(nèi)容image可能會因為許多動作而導(dǎo)致被重新設(shè)置,我們的切角效果就會因此而消失掉,我們需要對image屬性進行監(jiān)聽,一旦發(fā)生變化即對改變后的新值再次作切角處理,再次賦值給imageView.layer.content顯示。KVO響應(yīng)部分源碼:
#pragma mark - KVO for .image
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"image"]) {
UIImage *newImage = change[NSKeyValueChangeNewKey];
if ([newImage isMemberOfClass:[NSNull class]]) {
return;
} else if ([objc_getAssociatedObject(newImage, &kZYProcessedImage) intValue] == 1) {
return;
}
if (_isRounding) {
[self zy_cornerRadiusWithImage:newImage cornerRadius:self.frame.size.width/2 rectCornerType:UIRectCornerAllCorners];
} else if (0 != _cornerRadius && _rectCornerType && nil != self.image) {
[self zy_cornerRadiusWithImage:newImage cornerRadius:_cornerRadius rectCornerType:_rectCornerType];
}
}
}
這樣不需要離屏渲染的UIImageView圓角工具ZYCornerRadius就完成了
思考這個問題從這里出發(fā):我們將處理后的圖片顯示于屏幕之上是通過KVO對imageView.image的監(jiān)聽,再將處理后的圖片上下文賦值給imageView的mainLayer,這個過程完成后其實imageView持有的image就沒有被玷污的,同時_image也是,當(dāng)tableViewCell被selected后觸發(fā)了subViews的重繪后,小三還是會被正室所取代。
解決這個問題:
回到過去,使用setImage讓處理后的圖片顯示于屏幕,使用runtime對image對象綁定一個標識符,因KVO存在的無限遞歸。
** Usage(一句代碼,圓角風(fēng)雨無阻)**
ZYCornerRadius提供兩種使用方式
Category方式:
導(dǎo)入頭文件
#import "UIImageView+CornerRadius.h"
創(chuàng)建圓角半徑為6的UIImageView(三種方式):
//1
UIImageView *imageView1 = [[UIImageView alloc]init];
[imageView1 zy_cornerRadiusAdvance:75.0f rectCornerType:UIRectCornerAllCorners];
[imageView1 setFrame:CGRectMake(130, 80, 150, 150)];
imageView1.image = [UIImage imageNamed:@"mac_dog"];
[self.view addSubview:imageView1];
子類ZYImageView方式同理:
導(dǎo)入頭文件
#import "ZYImageView.h"
使用方式同理
以下列出ZYCornerRadius所開放的主要的func:
配置一個圓角UIImageView,傳入圓角半徑和圓角類型
+ (UIImageView *)zy_cornerRadiusAdvance:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType;
- (instancetype)initWithCornerRadiusAdvance:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType;
配置一個圓形的UIImageView
(UIImageView *)zy_roundingRectImageView;
- (instancetype)initWithRoundingRectImageView;
直接為UIImageView設(shè)置圓角圖片,傳入UIImage,圓角半徑和圓角類型,當(dāng)次有效!
- (void)zy_cornerRadiusWithImage:(UIImage *)image cornerRadius:(CGFloat)cornerRadius rectCornerType:(UIRectCorner)rectCornerType;
CocoaPods:
pod 'ZYCornerRadius', '~> 0.8.1'
以下記錄失敗過程。。。
嘗試在-drawRect中做切角操作
1.內(nèi)存使用過大,造成更多的性能損耗
嘗試從init出發(fā)
1.需要事先傳入Image,而且當(dāng)Image改變后無效,不適合實際生產(chǎn)
嘗試從-layoutSubviews下手
1.在Category中重寫該方法會造成不可挽回的結(jié)果
在setImage中設(shè)置好標識符開關(guān),在layoutSubviews中判斷開關(guān)狀態(tài)再執(zhí)行操作
1.雖然解決了對其他UIImageView的影響,可實現(xiàn)方式過于投機取巧過于費力。
嘗試直接從重寫-setImage下手
1.直接重寫會導(dǎo)致無限遞歸
2.自己重寫為UIImageView顯示圖片的機制,不熟悉源碼實現(xiàn),擔(dān)心造成什么遺漏。
最壞的打算,大膽使用swizzleMethod。
轉(zhuǎn)發(fā):http://www.itdecent.cn/p/2fbb4c8fb1fa
繪圖iOS Quarat2D http://www.itdecent.cn/p/8cf8d4b724d2