前言:之前记得有看过链式编程的实现,主要就是用 block 实现的,现在特地再记录一下。
1. 简介
在了解链式编程的时候,不得不提起 Masonry 这个框架了,代码如下:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.bottom.and.right.equalTo(@(0)); // 链式调用
}];
当时看到这种写法的时候,很惊叹,毕竟和 OC 中常见的方法调用大相径庭,但是写起来很简单,看起来很方便,所以我们可以学习一下这种方式,应用到自己的项目中,提高代码的阅读性。
2. 实现
2.1 链式调用的初步实现
我们创建一个 Student
类,将它改造为链式调用,初始的代码如下:
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)sayName;
- (void)sayAge;
@end
@implementation Student
- (void)sayName{
NSLog(@"name is %@", self.name);
}
- (void)sayAge{
NSLog(@"age is %ld", self.age);
}
@end
在使用时的调用如下:
Student *student = [[Student alloc] init];
student.name = @"张三";
student.age = 22;
[student sayName];
[student sayAge];
我们如果要实现连续调用sayName
和sayAge
方法,那么sayName
方法返回值就不能为空,应该为Student
对象:
修改 sayName 方法实现为:
- (Student *)sayName{
NSLog(@"name is %@", self.name);
return self;
}
这样就可以[[student sayName] sayAge]
进行调用了。
但是 Masonry 方法调用是通过.
+ ()
进行调用的,所以对sayAge
方法进行修改:
- (void(^)(void))sayAge{
return ^(){
NSLog(@"age is %ld", self.age);
};
}
这样可以实现[student sayName].sayAge()
调用了。
我们继续对sayName
方法进行修改,让它的调用方式也支持 sayName(),代码如下:
- (Student *(^)(void))sayName{
return ^(){
NSLog(@"name is %@", self.name);
return self;
};
}
这样就可以实现student.sayName().sayAge();
链式调用了,主要实现就是方法返回一个 block,block 的返回值为当前对象
,这样当链式调用 block的时候,其返回值是当前对象,这样就可以一直链式调用下去了。
2.2 构造方法的实现
主要也是通过 block,将当前实例对象传到外面,在外部进行赋值,代码如下:
- (instancetype)initWithConfig:(void(^)(Student *student))config{
Student *student = [[Student alloc] init];
config(student);
return student;
}
// 构造时使用:
Student *student = [[Student alloc] initWithConfig:^(Student * _Nonnull stu) {
stu.name = @"张三";
stu.age = 22;
}];
目前经过上面两个步骤之后,Student 类中的代码如下:
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithConfig:(void(^)(Student *student))config;
- (Student *(^)(void))sayName;
- (void(^)(void))sayAge;
@end
@implementation Student
- (instancetype)initWithConfig:(void(^)(Student *student))config{
Student *student = [[Student alloc] init];
config(student);
return student;
}
- (Student *(^)(void))sayName{
return ^(){
NSLog(@"name is %@", self.name);
return self;
};
}
- (void(^)(void))sayAge{
return ^(){
NSLog(@"age is %ld", self.age);
};
}
@end
调用时:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
Student *student = [[Student alloc] initWithConfig:^(Student * _Nonnull stu) {
stu.name = @"张三";
stu.age = 22;
}];
student.sayName().sayAge();
}
3. 改进
在上面的实现中,.
点语法调用实际上是方法调用,但是我们习惯用点语法对属性进行操作。所以我们应该将 block 声明为属性,然后重写 getter 方法,改进后的 Student 类:
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) Student *(^sayName)(void);
@property (nonatomic, copy) Student *(^sayAge)(void);
- (instancetype)initWithConfig:(void(^)(Student *student))config;
@end
@implementation Student
- (instancetype)initWithConfig:(void(^)(Student *student))config{
Student *student = [[Student alloc] init];
config(student);
return student;
}
- (Student *(^)(void))sayName{
return ^(){
NSLog(@"name is %@", self.name);
return self;
};
}
- (Student * _Nonnull (^)(void))sayAge{
return ^(){
NSLog(@"age is %ld", self.age);
return self;
};
}
@end
大概就是这么个样子了,上面没有涉及到一些参数的传递,下面的例子中会有补充。
4. 举例
我们继承系统的 UILabel,然后实现一个子类TTLabel,子类的初始化配置就使用链式调用,具体代码如下:
@interface TTLabel : UILabel
@property (nonatomic, copy, readonly) TTLabel *(^setFontColor)(UIColor *color);
@property (nonatomic, copy, readonly) TTLabel *(^setFont)(UIFont *font);
@property (nonatomic, copy, readonly) TTLabel *(^setBgColor)(UIColor* bgColor);
@property (nonatomic, copy, readonly) TTLabel *(^setContent)(NSString *text);
@end
@implementation TTLabel
- (TTLabel * _Nonnull (^)(UIColor * _Nonnull color))setFontColor{
return ^(UIColor *color){
self.textColor = color;
return self;
};
}
- (TTLabel * _Nonnull (^)(UIFont * _Nonnull font))setFont{
return ^(UIFont *font){
self.font = font;
return self;
};
}
- (TTLabel * _Nonnull (^)(UIColor * _Nonnull bgColor))setBgColor{
return ^(UIColor *bgColor){
self.backgroundColor = bgColor;
return self;
};
}
- (TTLabel * _Nonnull (^)(NSString * _Nonnull text))setContent{
return ^(NSString *text){
self.text = text;
return self;
};
}
@end
调用时:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
TTLabel *label = [[TTLabel alloc] initWithFrame:CGRectMake(100, 100, 300, 50)];
[self.view addSubview:label];
label.setContent(@"你好啊").setFont([UIFont systemFontOfSize:20]).setFontColor(UIColor.redColor).setBgColor(UIColor.blueColor);
}
定义属性相比定义方法一个突出的优点就是输入的时候会有输入提示,所以更常用。
对于这种链式调用有了一个了解之后,可以看下 Masonry 中的使用了,不得不赞叹,开源框架的作者真的厉害!!!