简单尝试链式编程 for iOS

背景

说起链式编程,在iOS领域内,最有名的莫过于Masonry框架了,它是一个轻量级的布局框架。其拥有自己的描述语法,同时采用更优雅的链式语法封装自动布局,使得代码简洁明了并具有高可读性。应该算是在iOS开发上,链式编程的典范了。

就因为Masonry成功的将链式语法引入到了iOS开发中,才使我这种一脸懵逼的吃瓜群众开始喜欢上链式编程的风格,更因为其出色的可读性而心持向往。因此,也开始自己尝试在某些场景下运用链式编程。

通常来讲,我们需要一些固定成场景(需求)来使用链式编程,就如同Masonry来解决自动布局一样(当然,同时屏蔽了大量繁琐的Autolayout约束的语法),我自己也需要设计一个场景。

为UIView添加链式属性

其实,我的场景很简单,就是对UI层进行大量属性设置时,希望使用链式编程。大概设计成这个样子:

 UIView* theView = [UIView new];
 theView.left(5).top(150).size(100,200).backgroundColor([UIColor redColor]).addToView(superView);

就这么简单,只要完成这个东西,我就心满意足了,为啥呢?因为目前其实对链式编程所知甚少,仍旧是一脸懵逼,所以不要贪多嚼不烂嘛(说白了就是能力有限)~
根据上面的语法设计,我们大概能知道这么几点:

  1. 支持()这种形式的语法入参
  2. 要支持"."(点)语法的连续调用
  3. 请注意上面的addToView(superView),也就是对于自己的操作始终保持在自己的对象上。想想之前我们是如何加一个UIView上去的?[superView addSubview:theView];这时,你发现,如果还按照这个思路来,链式不就打断了嘛,所以还是要进行一些必要的函数改造

基于以上三点,我们来看看应该来如何实现。首先括号"( )"和点" . "语法在OC里就比较受限制,换句话说,如果打算这么做,那基本上实现形式也就定了。在OC里,属性的getter方法是可以使用点" . "语法来调用的,而要想使用括号传参,第一反应就是使用block。

基于前面说的,我们就得出了以下结论:

  1. 为需要链式的方法设置类似于getter的方法
  2. 返回值必须是个block,这个block可以接收参数,然后执行
  3. 执行后,block的返回值必须是自己(即 self),否则就链接不到一起了

除了以上三点外,我肯定是希望所有的UIView都能有这样的能力,所以我打算给UIView写个Category,我们以backgroundColor为例。这个属性本来就是UIView的基本属性,而我们想让它支持链式的语法调用,就需要这么写

  @interface UIView (Chained)
  (UIView* (^)(UIColor* newcolor))ch_backgroundColor;
  @end

我们首先定一个ch_backgroundColor的方法,由于系统本身就已经用了backgroundColor了,同时,也为了体现该方法与backgroundColor的getter方法的区别,因此我们增加了方法前缀ch_,即chain的简写(暂且说是简写吧,不是也凑合用了 0

这个方法说起来,作用就是当你调用它时,他会返回一个block,这个block的返回值是一个UIView的指针,同时这个block会接收一个( UIColor* )的入参,并执行。这就算完事了。

接着,我们在实现文件里把它的实现完成,具体如下:

 -(UIView*(^)(UIColor*))ch_backgroundColor
 {
    WEAKENING_SELF;
    return ^(UIColor* newcolor){
      USING_SELF;
      self.backgroundColor = newcolor;
      return self;
    };
 }

代码中的WEAKENING_SELF和USING_SELF,两个宏实际上就是__weak和__strong的封装,具体的可以查看ReactCocoa里,@weakify和strongly的代码,意思差不多,除了没有装逼般的添加对@符的支持(如果不知道的同学可以去看看实现,记得第一次看的时候,恍然大明白~颇有技巧,颇感装逼呀~~)。

另外,我们还需要对addSubview做一些修改,具体如下:

-(UIView*(^)(UIView* parentView))ch_addToView;

-(UIView*(^)(UIView*))ch_addToView
{
    WEAKENING_SELF;
    return ^(UIView* parentView){
      USING_SELF;
      if(self.superview == nil)
      {
          [parentView addSubview:self];
      }
      else if(self.superview && parentView != self.superview)
      {
          [self removeFromSuperview];
          [parentView addSubview:self];
      }
      return self;
  };
}

之后,我们可以用代码验证一下:

  UIView* theView = [UIView new];
  theView.ch_backgroundColor([UIColor redColor])
         .ch_addToView(self.view); //self.view是当前UIViewController的view

在验证完没有问题后,我开始针对UIView增加其他属性,我会在这片文字的最后面全部贴出来,有需要的拿走不谢!!!

为UIView的子类添加链式属性

在编写了一大堆UIView的属性后,我开始不满足于这些基本的UIView属性了。之后,我们还希望让所有的子类都支持。

由于之前写的是Category,因此,只要引入,所有UIView的子类就都具有之前定义的所有的链式属性了。因此,我们只需要针对UILabel自有的属性进行链式化(自己发明的词,其实就是添加链式语法的属性设置,实在不想写这么多字~~)就可以了。

当然,此时作为吃瓜群众的我,还不知道困难来临了。

我们招葫芦画瓢,对UILabel的text属性进行链式化(哎呦,这词儿还挺好使 0

@interface UILabel (Chained)
-(UILabel*(^)(NSString* text))ch_text;
@end

-(UILabel*(^)(NSString*))ch_text
{
    WEAKENING_SELF;
    return ^(NSString* text){
      USING_SELF;
      self.text = text;
      return self;
    };
}

根之前一样,就这么搞起,木有悬念~
好了,让我们试试吧

UILabel* theLabel = [[UILabel alloc] init];
theLabel.ch_left(10)
        .ch_top(50)
        .ch_size(CGSizeMake(80,30))
        .ch_te

哎呦我靠~ 当设置完UIView的属性后,我本想去设置一下文本,即调用ch_text(@""),但是,我发现我找不到这个属性!我立即意识到,完了,是类型问题!由于我们在UIView的Category中返回的都是UIView的对象,所以在调用ch_left,ch_top,ch_size时,其实返回的都是UIView对象,即使我们知道这个对象其实就是theLabel本身....
如果我们把顺序调整一下,先调用ch_text,再调用ch_left,ch_size等等,就没有问题了。这也验证了就是返回类型导致的问题,毕竟,子类调父类方法当然没问题,而当父类型要调子类型方法时当然就不行。
好吧,我们来着手解决这一问题,虽然不知道能不能解决......

首先可以排除id 和 instancetype这两个了,原因很简单,返回id更是什么都“点”不出来。而instancetype的问题也一样,它最后认的还是UIView,因为之前的方法都是写在了UIView的Category上了嘛。

之后,我尝试用模版类去解决,由于我们无法将UIView改写成模版类(Category内也不允许将UIView改写为模版类,即@interface UIView (Chained)这么写是不被允许的)所以我试图使用模版对象,再从模版对象的方法中返回对应类型的对象。

模版类方案(其实这个方案并没有成功,我只是在这里写出来表明我挣扎过......)

具体方法如下:

  @interface TAnyType : NSObject
  typedef T MyType;
  -(__kindof T(^)(T tobj))transfom;
  @end

首先定义了模版T,之后,将T定义为MyType。
之后,定义了一个方法,它也是返回了一个block,其返回值和入参都是T,其实这个方法的设想很简单,就是将UILabel传进去,再把它的对象反出来。具体实现如下:

 @implementation TAnyType
-(__kindof MyType(^)(MyType))transfom
{
    return ^(MyType tobj){
        return tobj;
    };
}
@end

随后,我又在之前UIView的Category上增加了一个方法,具体如下:

-(TAnyType*(^)(TAnyType* obj))ch_change;

-(TAnyType*(^)(TAnyType*))ch_change
{
    return ^(TAnyType* obj){
        return obj;
    };
}

实际上,我的想法很简单,就是想通过模版类,返回真正的指针,设计的语法是下面这个样子:

 UILabel* theLabel = [UILabel new];
 theLabel.ch_size(CGSizeMake(50,50))
         .ch_change([TAnyType new]).transform(self) 

ch_size()返回的是UIView的对象,然后通过调用ch_change()来生成并返回一个模版对象,模版指定为UILabel,然后,通过transform()方法把当前的self以UILabel对象的类型再返回出来。
一切设想的是那么美好,但现实是操蛋的.....transform(self)返回的是id类型,尽管这个id对象就是UILabel的对象,但是你仍旧“点”不出来UILabel的方法,宣告失败!

最后的粗糙方案

到最后,我索性把事情搞的简单点,不就是要个类型转换吗,行~我直接给你。于是我做了这么一件事

 @interface UIView (Chained)
 ...   
 DefChainProp(UILabel);
 @end

 @implementation UIView (Chained)
 ...
 ImpChainProp(UILabel);
 @end

两个宏分别如下:

#define DefChainProp(Type) -(Type*)Type
#define ImpChainProp(Type) -(Type*)Type{ return (Type*)self;}

哈~ 是的,我将你需要的类型转换,以类型同名的方式添加了getter方法,而返回值就是强转后的对象本身,最后,我们的调用成了这个样子:

   UILabel* theLabel = [[UILabel alloc] init];
   theLabel.ch_left(10)
           .ch_top(50)
           .ch_size(CGSizeMake(80,30))
           .UILabel.ch_text(@"12345");

最终我达到了效果,至少是表面上的效果,保证了整个链式的连贯性。同时,整个继承上也不是问题,即使是UILabel的子类也同样可以调用,当然,如果你仍旧需要调用UILabel的字类,只需要在之前的UIView的Category或者UILabel的Category定义信的同名getter方法即可,虽然不是什么特别好的办法,但至少可以顺利的用下去。阿门~~

附上之前的类,有需要的请拿走~

// .h file
#define DefChainProp(Type) -(Type*)Type
#define ImpChainProp(Type) -(Type*)Type{ return (Type*)self;}

@interface UIView (Chained)

@property CGPoint origin;
@property CGSize size;

@property (readonly) CGPoint bottomLeft;
@property (readonly) CGPoint bottomRight;
@property (readonly) CGPoint topRight;

@property CGFloat height;
@property CGFloat width;

@property CGFloat top;
@property CGFloat left;

@property CGFloat bottom;
@property CGFloat right;

-(UIView*(^)(CGSize newsize))ch_size;
-(UIView*(^)(CGPoint neworigin))ch_origin;
-(UIView*(^)(CGFloat newheight))ch_height;
-(UIView*(^)(CGFloat newwidth))ch_width;
-(UIView*(^)(CGFloat newtop))ch_top;
-(UIView*(^)(CGFloat newleft))ch_left;
-(UIView*(^)(CGFloat newbottom))ch_bottom;
-(UIView*(^)(CGFloat newright))ch_right;
-(UIView*(^)(UIColor* newcolor))ch_backgroundColor;
-(UIView*(^)(BOOL newinteractive))ch_userInteractionEnabled;
-(UIView*(^)(CGFloat newradius))ch_cornerRadius;
-(UIView*(^)(BOOL newclipbounds))ch_clipsToBounds;
-(UIView*(^)(UIView* parentView))ch_addToView;
-(UIView*(^)(NSInteger newtag))ch_tag;
-(UIView*(^)(CGFloat newalpha))ch_alpha;
-(UIView*(^)(BOOL newhidden))ch_hidden;

DefChainProp(UILabel); //此处只是举个栗子

@end

下面是实现文件

//.m file
#define WEAKENING(param) __weak typeof(param) __weak##param__ = param
#define STRONGING(param) __strong typeof(__weak##param__) param = __weak##param__
#define WEAKENING_SELF WEAKENING(self)
#define USING_SELF STRONGING(self)

@implementation UIView (Chained)

- (CGPoint) origin{
  return self.frame.origin;
}

- (void) setOrigin: (CGPoint) aPoint{
    CGRect newframe = self.frame;
    newframe.origin = aPoint;
    self.frame = newframe;
}

-(UIView*(^)(CGPoint))ch_origin{
    WEAKENING_SELF;
    return ^(CGPoint neworigin){
    
        USING_SELF;
        self.origin = neworigin;
        return self;
    };
}

- (CGSize) size{
    return self.frame.size;
}

- (void) setSize: (CGSize) aSize{
    CGRect newframe = self.frame;
    newframe.size = aSize;
    self.frame = newframe;
}

-(UIView*(^)(CGSize))ch_size{
    WEAKENING_SELF;
    return ^(CGSize newsize){
        USING_SELF;
        self.size = newsize;
        return self;
    };
}

// Query other frame locations
- (CGPoint) bottomRight{
    CGFloat x = self.frame.origin.x + self.frame.size.width;
    CGFloat y = self.frame.origin.y + self.frame.size.height;
    return CGPointMake(x, y);
}

- (CGPoint) bottomLeft{
    CGFloat x = self.frame.origin.x;
    CGFloat y = self.frame.origin.y + self.frame.size.height;
    return CGPointMake(x, y);
}

- (CGPoint) topRight{
    CGFloat x = self.frame.origin.x + self.frame.size.width;
    CGFloat y = self.frame.origin.y;
    return CGPointMake(x, y);
}

- (CGFloat) height{
    return self.frame.size.height;
}

- (void) setHeight: (CGFloat) newheight{
    CGRect newframe = self.frame;
    newframe.size.height = newheight;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_height{
    WEAKENING_SELF;
    return ^(CGFloat newheight){
        USING_SELF;
        self.height = newheight;
    
        return self;
    };
}

- (CGFloat) width{
    return self.frame.size.width;
}

- (void) setWidth: (CGFloat) newwidth{
    CGRect newframe = self.frame;
    newframe.size.width = newwidth;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_width{
    WEAKENING_SELF;
    return ^(CGFloat newwidth){
        USING_SELF;
        self.width = newwidth;
   
        return self;
    };
}

- (CGFloat) top{
    return self.frame.origin.y;
}

- (void) setTop: (CGFloat) newtop{
    CGRect newframe = self.frame;
    newframe.origin.y = newtop;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_top{
    WEAKENING_SELF;
    return ^(CGFloat newtop){
        USING_SELF;
        self.top = newtop;
    
        return self;
    };
}

- (CGFloat) left{
    return self.frame.origin.x;
}

- (void) setLeft: (CGFloat) newleft{
    CGRect newframe = self.frame;
    newframe.origin.x = newleft;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_left{
    WEAKENING_SELF;
    return ^(CGFloat newleft){
    
        USING_SELF;
        self.left = newleft;
        return self;
    };
}

- (CGFloat) bottom{
    return self.frame.origin.y + self.frame.size.height;
}

- (void) setBottom: (CGFloat) newbottom{
    CGRect newframe = self.frame;
    newframe.origin.y = newbottom - self.frame.size.height;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_bottom{
    WEAKENING_SELF;
    return ^(CGFloat newbottom){
        USING_SELF;
        self.bottom = newbottom;
        return self;
    };
}

- (CGFloat) right{
    return self.frame.origin.x + self.frame.size.width;  
}

- (void) setRight: (CGFloat) newright{
    CGFloat delta = newright - (self.frame.origin.x + self.frame.size.width);
    CGRect newframe = self.frame;
    newframe.origin.x += delta ;
    self.frame = newframe;
}

-(UIView*(^)(CGFloat))ch_right{
    WEAKENING_SELF;
    return ^(CGFloat newright){
    
        USING_SELF;
        self.right = newright;
        return self;
    };
}

-(UIView*(^)(UIColor*))ch_backgroundColor{
    WEAKENING_SELF;
    return ^(UIColor* newcolor){
        USING_SELF;
        self.backgroundColor = newcolor;
        return self;
    };
}

-(UIView*(^)(BOOL))ch_userInteractionEnabled{
    WEAKENING_SELF;
    return ^(BOOL newinteraction){
        USING_SELF;
        self.userInteractionEnabled = newinteraction;
        return self;
    };
}

-(UIView*(^)(CGFloat))ch_cornerRadius{
    WEAKENING_SELF;
    return ^(CGFloat newradius){
        USING_SELF;
        self.layer.cornerRadius = newradius;
        return self;
    };
}

-(UIView*(^)(BOOL))ch_clipsToBounds{
    WEAKENING_SELF;
    return ^(BOOL newclipbounds){
        USING_SELF;
        self.clipsToBounds = newclipbounds;
        return self;
    };
}

-(UIView*(^)(UIView*))ch_addToView{
    WEAKENING_SELF;
    return ^(UIView* parentView){
        USING_SELF;
        if(self.superview == nil){
            [parentView addSubview:self];
        }
        else if(self.superview && parentView != self.superview){
            [self removeFromSuperview];
            [parentView addSubview:self];
        }
        return self;
    };
}

-(UIView*(^)(NSInteger))ch_tag{
    WEAKENING_SELF;
    return ^(NSInteger newtag){
        USING_SELF;
        self.tag = newtag;
        return self;
    };
}

-(UIView*(^)(CGFloat))ch_alpha{
    WEAKENING_SELF;
    return ^(CGFloat newalpha){
        USING_SELF;
        self.alpha = newalpha;
        return self;
    };
}

-(UIView*(^)(BOOL))ch_hidden{
    WEAKENING_SELF;
    return ^(BOOL newhidden){
        USING_SELF;
        self.hidden = newhidden;
        return self;
    };
}

ImpChainProp(UILabel);

@end

以上就是我近期对于链式的简单尝试,感觉还是挺有用的。另外,对于链式化的定义,其实也是可以用宏来统一定义的,但这样的话,为了避免不同类型的问题,可能入餐都要以对象的形式。比如入参都变为ch_width(@(100)),ch_hidden(@(YES))这类的,感觉有些费事,所以我还是保持了原有的基本入参类型,而最终也没有以宏的方式做成统一的定义。
不过,没准哪天写烦了,也就会去做定义了吧,也许......

你可能感兴趣的:(简单尝试链式编程 for iOS)