### 背景
最近涉及到画折线图业务,由于以前工程代码用的是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
```

这不就实现了链式调用了吗!其实原理就是这样子。我们来分下下。
首先要了解类名可以用点调用类方法。对象可以用点调用对象方法,也可以获取属性。
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
```
再次调用测试

好了,到这里以后我们就可以使用宏去简化我们代码了。再次对比三方源代码会发现。它就是这样做的。那以后我们就可以这样进行链式编程了。
特别是当我们封装个控件给别人用时候,该控件可能需要传入很多样式属性,如果我们使用链式定义该属性。那别人调用时候就会显得更加简单。
### 3. 宏定义说明
1. 对于一行显示不下的要用反斜杠(\)来换行。
2. ##两个井号键代表左右有需要拼接的字串。