iOS OC项目优化之路(一)之UIKit篇

这一系列文章是对自己iOS开发OC项目的总结。高效的开发是我比较追求的,在多年的工作中我也时刻提醒自己注意学习和总结。本系列文章是我在对项目优化过程中所做的一些尝试,里面有我不少的经验,是对目前自己的一个总结和展望。

这篇文章主要是讲如何简化对UIKit的使用和维护。github地址

UIKit的使用

众所周知,UIKit库是iOS最核心的几个模块之一,它负责页面的展示和交互,我们所看到的按钮、文本、图片等等都需要用到它。如此高频的使用就造成了一个问题,那就是大量的重复代码。比如,你要设置一个UIButton的属性,那么你可能这样写:

_button = [UIButton new];
[_button setTitle:@"一个按钮" forState:UIControlStateNormal];
[_button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[_button setTitleColor:[UIColor yellowColor] forState:UIControlStateSelected];
_button.titleLabel.font = [UIFont systemFontOfSize:16];
[_button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[_button setImage:[UIImage imageNamed:@"img"] forState:UIControlStateNormal];
[self.view addSubview:_button];

看起来似乎还不是太麻烦,那如果我们在另一个地方需要一个相似的按钮呢?除了工厂方法(因为oc的方法命名特性,如果想要让工厂化方法灵活需要定义非常不方便),我们复制粘贴的话会将_button属性一个个替换,这样就造成了时间的浪费,那么,如何才能减少这部分时间,我们来看下这种创建方法:

UIButtonCreate().makeChain
.text(@"一个按钮", UIControlStateNormal)
.textColor([UIColor redColor], UIControlStateNormal)
.textColor([UIColor redColor], UIControlStateSelected)
.font([UIFont systemFontOfSize:16])
.addTarget(self, @selector(buttonClick:), UIControlEventTouchUpInside)
.image(UIImageNamed(@"img"), UIControlStateNormal)
.addToSuperView(self.view)
.assignTo(^(__kindof UIView * _Nonnull view) {
    _button = view;
});

我们可以看到,这种方式创建的代码可以很好的解决这个问题,在其他地方需要使用到相似的代码时,可以整个copy过去,只需要在赋值的地方,更改赋值对象就行了,而且可以随意添加或删除属性。

这种做法有非常多的好处:

  • 快速创建UI
  • 创建发放集中
  • 方便维护与重用
  • 便于扩展

如何实现

这里我们需要考虑的有几点:

  1. 编程思想如何选
  2. 低入侵并保证控件的专用性
  3. 如何节省代码量

解决了这几点脑力活动,剩下的就是大量的体力活动了。

一、选择何种编程思想

编程思想有许多,但针对我所要达到效果:简单易用,经过筛选后我选择了链式编程。我特别喜欢这种思想,主要是因为它的有一个特点就是可以灵活且方便的对对象进行设置,我接触过的优秀的链式框架诸如:Masonry、SDAutolayout等都是使用的这种思想。

那么如何实现链式变成呢?

其实很简单,它所实现的思路核心就是OC中的block。block在OC中是非常常用的,我们可以用它来处理事件回调,例页面传值,我们的通常做法是这样的:

void (^block) (void) = ^{
        NSLog(@"回调");
    };

在事件回调方设置回调,在事件接收方执行回调:

block()

链式编程便是利用这一特性,将两者的关系转换了一下,设置属性的对象变成了调用方,而使用属性方提前定义好了block方法,并在返回时返回调用对象,这样便可以持续的进行调用:

- (DPPanGestureChainModel * _Nonnull (^)(CGPoint, UIView * _Nonnull))translation{
    return ^ (CGPoint translation, UIView *view){
        [(UIPanGestureRecognizer *)self.gesture setTranslation:translation inView:view];
        return self;
    };
}

二、低入侵并保证控件的专用性

如果我们把链式语法使用分类来进行封装的话,那么势必要加入前缀来防止与系统库冲突的话,那么势必要影响代码的阅读性,以及使用的便利性,如下:

    UIButtonCreate()
    .dp_text(@"一个按钮", UIControlStateNormal)
    .dp_textColor([UIColor redColor], UIControlStateNormal)
    .dp_textColor([UIColor redColor], UIControlStateSelected)
    .dp_font([UIFont systemFontOfSize:16])
    .dp_addTarget(self, @selector(buttonClick:), UIControlEventTouchUpInside)
    .dp_image(UIImageNamed(@"img"), UIControlStateNormal)
    .dp_addToSuperView(self.view)
    .dp_assignTo(^(__kindof UIView * _Nonnull view) {
        _button = view;
    });

这样做虽然并无大碍,但终究还是没有使用原生方法来的舒服。
而且,还有一个问题就是保证控件专用性。
控件专用性指的是UIView不可使用UILabel方法,UILabel方法不能使用UITableView的方法。
综上,这里我们采用新建一个model,并在model中添加与原生属性名相同属性。这样就解决了这两个问题。

#define DPCATEGORY_VIEW_IMPLEMENTATION(DPClass, modelType)\
@implementation DPClass (EXT)\
- (modelType *)makeChain{\
    return [[modelType alloc] initWithTag:self.tag andView:self];\
}\
@end
DPCATEGORY_CHAIN_PROPERTY ObjectType (^ bounds) (CGRect frame);
#pragma mark - frame -
DPCATEGORY_CHAIN_PROPERTY ObjectType (^ frame) (CGRect frame);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ origin) (CGPoint origin);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ x) (CGFloat x);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ y) (CGFloat y);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ size) (CGSize size);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ width) (CGFloat width);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ height) (CGFloat height);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ center) (CGPoint center);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ centerX) (CGFloat centerX);

三、如何节省代码量

节省代码量要从两个方面入手:

  1. UIKit的继承链
  2. UIKit的属性

首先是UIKit的属性,我们都知道,UIKit从UIResponder开始继承,每一个父类都会有许多的属性,而我们封装的控件要保证子类调用父类方法后,仍能使用子类的方法。这个问题一直困扰着我,具体原因是因为如果只是普通的链式调用的话,返回的会是当前类,那么调用父类方法就会返回父类,只能在强转后才能调用子类的方法。

在查阅众多资料后,感谢# ZZFLEX
的作者给我提供了一个思路,那边是泛型。

什么是泛型

泛型,可重用的函数和类型,可以避免重复,以清晰,抽象的方式表达其意图.泛型给予我们更抽象的封装函数或类的能力。很开心,apple可以把swift中的泛型带到OC。举个例子,我们常用的数组可以通过制定数组元素类型,可以在我们取值时直接获取到对象的确定类型而不是id类型:

NSArray array = @[@"",@""];
//进行快速遍历会直接得到obj类型里面得到obj的类型
[array enumerateObjectsUsingBlock:<#^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)block#>]

有了泛型我们可以这样定义:

@interface DPBaseViewChainModel <__covariant  ObjectType> : NSObject
DPCATEGORY_CHAIN_PROPERTY ObjectType (^ bounds) (CGRect frame);
#pragma mark - frame -
DPCATEGORY_CHAIN_PROPERTY ObjectType (^ frame) (CGRect frame);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ origin) (CGPoint origin);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ x) (CGFloat x);

DPCATEGORY_CHAIN_PROPERTY ObjectType (^ y) (CGFloat y);

@end

在子类继承父类时,指明父类的类型,则可实现父类子类无缝调用:

@interface DPViewChainModel : DPBaseViewChainModel

@end

因为泛型指定的都是当前类。

如何遵循继承关系

如果我们每个UIKit类都需要父类属性的话,那么,继承链越长,他的属性越多,这样会增大封装库的工作量和维护的难度。这个问题在ZZFLEX中并没有解决,或者说只是实现了一层集成的封装如UIView->UILable,那么如果是UIView->UIControl->UIButton呢。怎样解决这个问题呢?

我们可以知道,NSMutableArray是这样定义的:

@interface NSMutableArray : NSArray
@end

所以我们也可以继承于UIViewBaseModel

@interface DPBaseControlChainModel <__covariant ObjectType>: DPBaseViewChainModel
@end

然后在UIButton中:

@interface DPButtonChainModel : DPBaseControlChainModel
@end

这样我们就可以模仿UIView的继承关系,而不必要每个类都写一遍集成链上的属性。

经过上面的精简,仍然有着众多的属性需要我们去设置,但这些工作都是大量而繁琐的,可以通过宏定义来简化代码,具体看下面,就不做过多解释了:

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(contentEdgeInsets, UIEdgeInsets)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(titleEdgeInsets, UIEdgeInsets)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(imageEdgeInsets, UIEdgeInsets)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(adjustsImageWhenHighlighted, BOOL)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(showsTouchWhenHighlighted, BOOL)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(adjustsImageWhenDisabled, BOOL)

DPCATEGORY_CHAIN_BUTTON_IMPLEMENTATION(reversesTitleShadowWhenHighlighted, BOOL)

DPCATEGORY_CHAIN_BUTTONLABEL_IMPLEMENTATION(textAlignment, NSTextAlignment)
DPCATEGORY_CHAIN_BUTTONLABEL_IMPLEMENTATION(numberOfLines, NSInteger)
DPCATEGORY_CHAIN_BUTTONLABEL_IMPLEMENTATION(lineBreakMode, NSLineBreakMode)
DPCATEGORY_CHAIN_BUTTONLABEL_IMPLEMENTATION(adjustsFontSizeToFitWidth, BOOL)
DPCATEGORY_CHAIN_BUTTONLABEL_IMPLEMENTATION(baselineAdjustment, UIBaselineAdjustment)

总结

经过脑力活动之后,开始进行体力活动,虽然花费了一些时间,但这些时间是值得的,它为我们以后的工作带来了很大的遍历。github地址不仅有链式的封装,还借鉴了YYCategory集成了很方便的分类。今天的《OC项目优化之路(一)之UIKit篇》就讲解到这里。

你可能感兴趣的:(iOS OC项目优化之路(一)之UIKit篇)