转载请注明出处:http://www.olinone.com/
Hi,又到了更新博客的时间,很高兴再次与大家见面。最近,关于图片圆角的话题讨论非常激烈,出现了许多好的文章。恰逢工作需要,用到了大量圆角图片。然而,系统圆角会导致离屏渲染的问题,出于性能考虑,于是有了图片圆角渲染工具HJCornerRadius,其最大优势在于使用简单,一行搞定图片圆角
1 |
imageview.aliCornerRadius = 5.0f; |
核心思想就是使用圆角图片替换系统圆角。实际使用时,确保layer对象的masksToBounds属性为NO
1 |
imageview.layer.masksToBounds = NO |
支持pod方式安装,如果你在实际使用中遇到什么问题,欢迎在文章后面跟我留言
1 |
pod 'HJCornerRadius', :git => "https://github.com/panghaijiao/HJCornerRadius.git" |
当然,本文的目的不是介绍如何使用该工具,而是想跟大家分享开发时用到的几点设计思想
自观察的巧妙应用
既然要生成圆角图片,首先要解决生成时机问题。可能会有朋友想到swizzle类UIImageView的setImage方法,但我个人并不推荐,毕竟Swizzle类方法影响范围太广,对于大型开发团队,出问题后很难排查定位问题所在。定义UIImageView子类?实用性不强!
还记得我在文章《“自释放”在iOS开发中的应用》 中提到的实现自释放的三种方式吗?其中一种方式就是动态属性观察者——通过创建一个动态属性KVO被观察对象的某一属性,从而达到自监控的目的。
通过创建动态属性观察者HJImageObserver,监听UIImageView的image属性,当image发生变化时,能够立即触发圆角图片的生成,从而达到自动实现圆角图片替换的目的,具体实现可以参考源码 HJCornerRadius
RunLoop的适当切换
细心的朋友可能注意到,本工具并不是直接渲染原始Image对象,而是先截取UIImageView视图Layer生成的Image,然后再做渲染。原因很简单,因为UIImageView呈现方式涉及多种ContentMode,通过渲染UIImageView视图Layer生成的图片可以巧妙的解决UIImageView显示模式的问题
此外,由于系统原因,对于像UITableViewCell的UIImageView,第一次创建赋图时,可能无法获取UIImageView视图Layer的图片,此时,可以通过切换异步RunLoop达到延时渲染的目的
众所周知,截图渲染的逻辑是可以运行在工作线程,但是本工具并没有把它们放在工作线程执行,因为放在工作线程会有延迟,从而导致图片闪烁的现象
具体截图渲染代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
UIGraphicsBeginImageContextWithOptions(self.originImageView.bounds.size, NO, [UIScreen mainScreen].scale); CGContextRef currnetContext = UIGraphicsGetCurrentContext(); CGContextAddPath(currnetContext, [UIBezierPath bezierPathWithRoundedRect:self.originImageView.bounds cornerRadius:self.cornerRadius].CGPath); CGContextClip(currnetContext); [self.originImageView.layer renderInContext:currnetContext]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if ([image isKindOfClass:[UIImage class]]) { image.aliCornerRadius = YES; self.originImageView.image = image; } else { dispatch_async(dispatch_get_main_queue(), ^{ [self updateImageView]; }); } |
经过实际测试验证,效果还是比较理想的,即使需要显示大量圆角图片,显示帧数也可以稳定在50帧以上。CPU消耗影响较小,以iPhone6测试显示,CPU实际消耗上涨不会超过5%,属于合理范围之内
自从发表博客《生命不息,折腾不止——致敬逝去的2015》后,收到了许多朋友的关注和问候,在此谢谢大家的祝福。新的一年里,希望各位一如既往支持小生,多多为我点赞,本人也会更加努力,争取为大家奉献更多更好的东西。你可以follow本人的Github,也可以关注我的个人微博,再次感谢你的来访
博主,这个工具性能怎样?看起来很不错!
恩,测试过,性能不错!
博主加油~
不错,不错,看看了!
我又来了,您高兴吗?!
@property (nonatomic, assign) UIImageView *originImageView;
为什么不用weak,用assign?
哈哈,还是你看的比较仔细,这里没用weak是的原因是因为在释放阶段,即使weak对象没有释放,返回也是nil,从而导致无法移除KVO监听
我也在考虑这个问题,但是有一点不明白,如果设置为assign是不是HJImageObserver的dealloc中应该把originImageView置为nil?
这样更安全,不过ARC下可以不用考虑这些
您说的这个释放阶段是指什么时机呢?
看看!
不错,不错,看看了!
if ([image isKindOfClass:[UIImage class]]) {
image.aliCornerRadius = YES;
self.originImageView.image = image;
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateImageView];
});
}
感觉这里else是不是逻辑不太合理。如果想让他在主线程生成图片,那在方法执行的时候,就进入主线程不是更好?如果是在生成图片的过程中,导致生成图片失败,那这样会不会一直失败而导致死循环?
这个地方不是为了切换线程,只是通过runloop的方式优先执行系统代码,因为特定情况下,在系统赋值图片时是没法截取图片的,当然,如果真出现死循环,那也是系统BUG导致永远无法截图,有可能性,但基本不会出现!
导航控制器,pop回来的时候 会出现kvo监听没有移除的情况,强行挂机咯。
能定位到具体原因吗?我这边没法重现!
报错
reason: ‘An instance 0x7fe9ea9b4e20 of class UIImageView was deallocated while key value observers were still registered with it. Current observation info: (
<NSKeyValueObservance 0x7fe9ea9b5010: Observer: 0x7fe9ea9b4ff0, Key path: image, Options: Context: 0x0, Property: 0x7fe9ea91bad0>
<NSKeyValueObservance 0x7fe9ea9b50b0: Observer: 0x7fe9ea9b4ff0, Key path: contentMode, Options: Context: 0x0, Property: 0x7fe9ea922db0>
– (void)dealloc { [self.originImageView removeObserver:self forKeyPath:@”image”]; [self.originImageView removeObserver:self forKeyPath:@”contentMode”]; }
这个方法并没有调用。
抱歉!是我自身的原因,cell内存泄露导致imageview没有得到释放,不好意思啦。
嘿嘿,欢迎来访
*** -[NSKVONotifying_UIImageView retain]: message sent to deallocated instance 0x7fa345164780
经过探索,是因为imageView所在控制器使用的FDTemplateLayoutCell的高度计算机制,这个扩展会自动生成一个临时的cell,然后通过systemLayoutSizeFittingSize计算出cell的高度,但是问题来了,systemLayoutSizeFittingSize这个方法只会计算出layout之后的size并不会让cell上的imageView进行layout,所以imageView.frame一直是{0,0,0,0},会一直执行dispatch_async(dispatch_get_main_queue(), ^{
[self updateImageView];
});
当cell释放后,还在执行,因为originImageView用的assign属性,访问了坏地址,所以出现闪退,我临时的解决方法是,HJImageObserver再加上对frame的监听,在updateImageView判断如果frame是empty直接return掉,但是不知道是不是最优的解决方案,所以和庞兄探讨探讨。
hi,感谢来访,当时其实我是想着监听frame的,但是考虑到autolayout时frame变化频率非常快,此时就会触发大量计算,导致程序卡死,所以不是很建议监听frame,我的建议还是优先考虑系统layer方式实现圆角,性能OK就别用这个了,可以试一下看看~正常一张图片圆角是没问题的