链式编程

253 阅读3分钟

背景

最近涉及到画折线图业务,由于以前工程代码用的是AAChartKitLib三方,这次app迭代涉及到修改折线图样式,所以自己看了下这个三方源代码。发现使用时候用的是链式属性赋值,自己研究了下。

原理

self.aaChartModel= AAChartModel.new
.chartTypeSet(AAChartTypeLine)//图表类型
.titleSet(@"")//图表主标题
.subtitleSet(@"")//图表副标题
.yAxisLineWidthSet(@0)//Y轴轴线线宽为0即是隐藏Y轴轴线

该三方使用方法如上所示,使用点语法链式调用给model赋值。先说下原理,之后再慢慢分析。

其实点语法后面的如.chartTypeSet(AAChartTypeLine)分为两步,第一步是调用对象方法chartTypeSe返回block,第二步在调用这个block传入参数AAChartTypeLine然后内部model的AAChartTypeLine属性开始赋值,再返回model对象本身,经过这一次调用执行其实model就赋值了一个属性,并且返回了该对象本身。由于是返回了该对象本身,所以接下来可以继续利用该对象进行调用对象方法,再次执行block赋值,形成循环,导致可以无限链式调用。

代码解析

1. 写个测试等价代码来分析

新建一个TestModel类

@interface TestModel : NSObject
@property (assign, nonatomic) NSUInteger age;
- (TestModel *(^)(NSUInteger age))ageSet;


@property (copy, nonatomic) NSString* job;
- (TestModel *(^)(NSString * job))jobSet;
@end

@implementation TestModel
- (TestModel *(^)(NSUInteger age))ageSet {
    return ^(NSUInteger age) {
        self.age = age;
        return self;
    };
}

- (TestModel *(^)(NSString * job))jobSet {
    return ^(NSString * job) {
        self.job = job;
        return self;
    };
}

@end

在ViewController类里调用

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    TestModel *model = TestModel.new
    .ageSet(32)
    .jobSet(@"work");
    NSLog(@"model = %@",model);
}
@end

image

这不就实现了链式调用了吗!其实原理就是这样子。我们来分下下。 首先要了解类名可以用点调用类方法。对象可以用点调用对象方法,也可以获取属性。

  1. TestModel.new这是类名在调用类方法,返回一个model对象。
  2. .ageSet(32)之后返回的model对象调用该ageSet对象方法,返回一个block。之后执行该block传入参数32,内部给改age属性赋值为32之后返回该model对象。
  3. .jobSet(@"work")和上边一样,首先用刚才赋完age值的model对象调用jobSet对象方法,返回block,之后执行该block传入属性work字串,内部给job属性赋值为work字串,再返回该model对象。 这样经过前两个步骤之后该model的两个属性都赋值完了。

2. 简化以上测试代码

这里我们利用宏定义来简化上面测试代码

#import <Foundation/Foundation.h>
#define PropStatementAndPropSetFuncStatement(propertyModifier, className, propertyType, propertyName)           \
@property(propertyModifier, nonatomic) propertyType propertyName;                                               \
- (className *(^)(propertyType propertyName))propertyName##Set;                                                 \

#define PropSetFuncImplementation(className, propertyType, propertyName)                                        \
- (className * (^)(propertyType propertyName))propertyName##Set{                                                \
return ^(propertyType propertyName){                                                                            \
self.propertyName  =  propertyName;                                                                             \
return self;                                                                                                    \
};                                                                                                              \
}

NS_ASSUME_NONNULL_BEGIN

@interface TestModel : NSObject
@property (assign, nonatomic) NSUInteger age;
- (TestModel *(^)(NSUInteger age))ageSet;

@property (copy, nonatomic) NSString* job;
- (TestModel *(^)(NSString * job))jobSet;

PropStatementAndPropSetFuncStatement(copy, TestModel, NSString *, name)
PropStatementAndPropSetFuncStatement(copy, TestModel, NSString *, address)
@end


#import "TestModel.h"
@implementation TestModel
- (TestModel *(^)(NSUInteger age))ageSet {
    return ^(NSUInteger age) {
        self.age = age;
        return self;
    };
}

- (TestModel *(^)(NSString * job))jobSet {
    return ^(NSString * job) {
        self.job = job;
        return self;
    };
}

PropSetFuncImplementation(TestModel, NSString *, name)
PropSetFuncImplementation(TestModel, NSString *, address)
@end

再次调用测试

image

好了,到这里以后我们就可以使用宏去简化我们代码了。再次对比三方源代码会发现。它就是这样做的。那以后我们就可以这样进行链式编程了。 特别是当我们封装个控件给别人用时候,该控件可能需要传入很多样式属性,如果我们使用链式定义该属性。那别人调用时候就会显得更加简单。

3. 宏定义说明

  1. 对于一行显示不下的要用反斜杠(\)来换行。
  2. ##两个井号键代表左右有需要拼接的字串。