为View增加平滑的圆角
项目改版,需要在项目中实现整体的一个平滑的圆角的方案,实现过程遇到诸多问题,记录一下,以免日后再次踩坑。
因为是使用category实现的,想通过category给view增加一个可以设置四个角弧度的属性,category里面增加属性的时候,自定义了一个struct类型的属性,
typedef struct Corner {
CGFloat topLeft;
CGFloat topRight;
CGFloat bottomLeft;
CGFloat bottomRight;
} Corner
其实这里就是自定义了一个结果提,然后对他进行了重命名 叫Corner,也可以改成 AA,BB 一个名字而已;就等价于
struct Corner {
CGFloat topLeft;
CGFloat topRight;
CGFloat bottomLeft;
CGFloat bottomRight;
}
typedef struct Corner Corner
然后用到了一个内联函数,关于内联函数,这里复习一下,内联函数称为在线函数,或者编译时期展开函数,用inline 修饰的函数,普通函数之间的调用,是内存地址之间的调用,在被调用的时候需要CPU执行CALL执行不同,需要完成程序计数器压栈->执行要执行的函数语句->出栈程序计数器,会有一个时间开销,内联函数不需要这个调用过程,内联函数是使用空间换时间的典型案例,在编译的时候,内联函数会直接在需要执行内联函数的地方(普通函数执行CALL的地方),将内联函数汇编片段copy一份并插到此处,代替CALL指令,类似于宏定义,但是宏定义只是简单的字符串替换,是不能做类型检查的,内联函数相对比而言更安全;所以执行速度比一般函数的执行速度提高了这里的效率;但是由于copy了代码片段,需要内存消耗,所以是空间换时间的一种方法;内联函数一般是比较简单的代码片段,不能包括循环、递归(递归函数的内联扩展可能引起部分编译器的无穷编译)等复杂流程,代码最好不超过5行,在YYKit 和 AFNetworking 这些三方框架中,有大量的内联函数的使用;
UIKIT_STATIC_INLINE SquirCleRadius SOCornerRadiusMake(CGFloat topLeft, CGFloat topRight, CGFloat bottomLeft, CGFloat bottomRight) {
SquirCleRadius radius = {topLeft, topRight, bottomLeft, bottomRight};
return radius;
}
这里 UIKIT_STATIC_INLINE 是一个宏定义 - > #define UIKIT_STATIC_INLINE static inline
在category.h中,代码如下
#import <UIKit/UIKit.h>
typedef struct SquirCleRadius {
CGFloat topLeft;
CGFloat topRight;
CGFloat bottomLeft;
CGFloat bottomRight;
}SquirCleRadius;
UIKIT_STATIC_INLINE SquirCleRadius SOCornerRadiusMake(CGFloat topLeft, CGFloat topRight, CGFloat bottomLeft, CGFloat bottomRight) {
SquirCleRadius radius = {topLeft, topRight, bottomLeft, bottomRight};
return radius;
}
NS_ASSUME_NONNULL_BEGIN
@interface UIView (Squircle)
@property (nonatomic,assign) CGFloat controlRatio;
@property (nonatomic,assign) SquirCleRadius radii;
@end
NS_ASSUME_NONNULL_END
category.m
@implementation UIView (Squircle)
- (void)setRadii:(SquirCleRadius)radii{
NSValue *radiiValue = [NSValue valueWithBytes:&radii objCType:@encode(struct SquirCleRadius)];
objc_setAssociatedObject(self, @"radiiKey", radiiValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updateSquirclePath];
}
- (SquirCleRadius)radii{
NSValue *value = objc_getAssociatedObject(self, @"radiiKey");
if(value) {
SquirCleRadius radii;
[value getValue:&radii];
return radii;
}else {
return SOCornerRadiusMake(0, 0, 0, 0);
}
}
- (void)setControlRatio:(CGFloat)controlRatio{
objc_setAssociatedObject(self, @"controlRatioKey", @(controlRatio), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updateSquirclePath];
}
- (CGFloat)controlRatio{
id controlRatio = objc_getAssociatedObject(self, @"controlRatioKey");
if (controlRatio == nil || [controlRatio isEqual:[NSNull null]]) {
return 1;
}else{
return [controlRatio floatValue];
}
}
- (void)updateSquirclePath{
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.path = [self getSquirCleBezierPath].CGPath;
shapelayer.fillColor = [UIColor blueColor].CGColor;
[self.layer addSublayer:shapelayer];
self.layer.mask = shapelayer;
}
- (UIBezierPath *)getSquirCleBezierPath{
UIBezierPath *path = [UIBezierPath bezierPath];
return path;
}
@end
在.m 里面可以看到,使用关联对象的只是,给category增加属性,最后存储的都是id类型
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
所以这里要把struct 转换为id类型,转换为value类型
NSValue *customValue = [NSValue valueWithBytes:&tmpTest objCType:@encode(struct Test)];
再把get得到的id类型转换为struct类型
NSValue *value = objc_getAssociatedObject(self, @"radiiKey");
if(value) {
SquirCleRadius radii;
[value getValue:&radii];
return radii;
}else {
return SOCornerRadiusMake(0, 0, 0, 0);
}
让实际存储数据以NSValue的形式存储;
那么又有一个问题,我以为,Struct是用assign来修饰的,那么使用关联对象的增加属性的时候,就自然而言的选择了 OBJC_ASSOCIATION_ASSIGN
,使用assign策略的特点是,对象释放以后,不会主动将修饰对象置为nil,这样会有访问僵尸对象导致崩溃的风险,为了解决这个问题,可以创建一个替身对象,使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC
策略来强引用对象,既然关联对象存取都是对象,那么 大多数应该都是用 OBJC_ASSOCIATION_RETAIN_NONATOMIC
;