iOS中的链式编程
前言
作为iOS开发者,很多人看到这个标题最先想到的可能是Masonry和SnapKit。那么什么是链式编程?为什么有人说Masonry/SnapKit是函数式编程,有人说是链式编程?
其实,函数式编程和链式编程并不是一个层面上的概念。函数式编程是一种编程范式,而链式编程可以理解为函数式编程的一种体现。
函数式编程(FP)
在函数式编程中,函数是“第一等公民”。也就是说,函数和其他数据类型一样,可以作为其他函数的参数、返回值。
举个简单的例子:
求:(1+2)*3/4的值
设
f1(a, b) = a + b
f2(c) = c*3
f3(d) = d/4
那么,f(x) = f3(f2(f1(1, 2)))
链式编程
提到链式编程,最醒目的自然是点语法。在OC中,点语法的应用多数仅限于getter、setter,并没有swift中便捷。
这里说一种OC中的特殊点语法。我们知道,OC是通过[receiver message]
来调用方法的,点语法是一种语法糖,最终会调用到对应属性的getter/setter方法。如果我在某个类中写一个方法,是否可以通过点语法来调用这个方法?
@interface Test : NSObject
- (NSString *)hello;
@end
点语法-0
可以看到,并没有报错而是出现警告,意思是没有接收getter方法获取到的值。
点语法-1这样就OK了!
同理可做进一步验证:
@interface Test : NSObject
- (NSString *)hello;
- (void)setHello:(NSString *)hello;
@end
点语法-2
可见,点语法会找到对应的SEL
。利用这个特性同样可以在.m文件
中同时实现getter、setter方法,而不用写完属性后再写@synthesize
,但是由于没有ivar接收这个变量,所以需要手动关联,比较麻烦。.m文件
中不能同时实现getter、setter,终究只是因为没有合成对应的ivar,而不是不能同时写getter、setter方法。
举个例子:
@interface Test : NSObject
@property (nonatomic, strong) NSString *a;
@end
点语法-3
并没有出现什么恶心的爆红。又或者像利用runtime给分类添加属性,同样是在没有写@synthesize
的情况下仍然可以同时实现setter、getter,终其原因是没用到对应的ivar。
拉回主战场,有点小跑题。。
如何实现链式编程?只要在返回值上做手脚就可以了。
@interface Test : NSObject
- (Test *)a;
- (Test *)b;
- (Test *)c;
@end
链式语法-0
这样写的确是连起来了,但是好像不能传参,怎么实现参数的传递?
回归函数式编程,函数是第一等公民的概念,当返回值是个带参block的getter方法就可以实现参数的传递了。
@interface Test : NSObject
- (Test *(^)(NSString *str))blk0;
- (Test *(^)(NSString *str))blk1;
- (Test *(^)(NSString *str))blk2;
@end
链式语法-1
来回顾一下思考过程:调用方法-->如何将方法通过点语法调用-->手写getter方法-->实现点语法的链式调用
到此为止,会发现其实还是通过getter方法来实现各方法之间的链式调用。既然这样,链式语法的调用可以直接通过属性来实现。
@interface Test : NSObject
@property (nonatomic, readonly) Test *a;
@property (nonatomic, readonly) Test *(^blk)(NSString *str);
@end
链式语法-2
注意:上文说的特殊点语法会找到对应的SEL
,并没有提到方法签名
。因此,还可以这样写
@interface Base : NSObject
- (Base *(^)(NSString *))info;
@end
=======================
@implementation Base
- (Base *(^)(NSString *))info {
return ^(NSString *info){
self.info = info;
return self;
};
}
- (void)setInfo:(NSString *)info {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"必须在子类中重写%@方法", NSStringFromSelector(_cmd)] userInfo:nil];
}
@end
这样的getter、setter看起来很奇怪,因为是手写而不是利用属性自动生成,而点语法只找SEL
不找方法签名
,因此完全可以改写。
这样做可以利用getter完成setter赋值,既处理了逻辑关系,又能通过getter完成链式编程。子类的setter怎么实现视具体的需求而定,可以在不同的子类中完成不同的业务逻辑,用起来还是挺方便的。
现在已经可以实现链式编程了,来试试身手吧!
小试牛刀
举个简单的例子,用链式编程撸一遍tableview,这里抛砖引玉只实现简单的数据源方法,感兴趣的童鞋顺着思路继续写。
Talk is cheap, show me the code.
@interface UITableView (JKAdd)
@property (nonatomic, strong) JKTableViewHelper *helper;
- (void)makeConfigure:(void (^)(JKTableViewHelper *helper))tb;
@end
@implementation UITableView (JKAdd)
- (void)setHelper:(JKTableViewHelper *)helper {
objc_setAssociatedObject(self, @selector(helper), helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (JKTableViewHelper *)helper {
return objc_getAssociatedObject(self, @selector(helper));
}
- (void)makeConfigure:(void (^)(JKTableViewHelper *))tb {
JKTableViewHelper *helper = [JKTableViewHelper new];
!tb ? : tb(helper);
self.helper = helper;
}
@end
@interface JKTableViewHelper : NSObject <UITableViewDataSource>
- (JKTableViewHelper *(^)(UITableView *, Class))bindTb;
- (JKTableViewHelper *(^)(NSInteger))totalSection;
- (JKTableViewHelper *(^)(NSInteger))section;
- (JKTableViewHelper *(^)(NSInteger))row;
- (JKTableViewHelper *(^)(NSArray *))configureCell;
@end
@interface JKTableViewHelper ()
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, strong) Class Cls;
@property (nonatomic, assign) NSInteger sections;
@property (nonatomic, assign) NSInteger currentSection;
@property (nonatomic, strong) NSMutableArray *sectionRows;
@property (nonatomic, strong) NSArray *models;
@end
@implementation JKTableViewHelper
- (JKTableViewHelper *(^)(UITableView *, Class))bindTb {
return ^(UITableView *tableView, Class Cls){
tableView.dataSource = self;
self.tableView = tableView;
self.Cls = Cls;
NSCAssert([Cls isSubclassOfClass:[UITableViewCell class]], @"%@必须是UITableViewCell或者它的子类", Cls);
[tableView registerClass:Cls forCellReuseIdentifier:NSStringFromClass(Cls)] ;
return self;
};
}
- (NSMutableArray *)sectionRows {
if (_sectionRows == nil) {
_sectionRows = @[].mutableCopy;
}
return _sectionRows;
}
- (JKTableViewHelper *(^)(NSInteger))totalSection {
return ^(NSInteger sections){
self.sections = sections;
return self;
};
}
- (JKTableViewHelper *(^)(NSInteger))section {
return ^(NSInteger section){
NSCAssert(section <= self.sections-1, @"section越界");
self.currentSection = section;
return self;
};
}
- (JKTableViewHelper *(^)(NSInteger))row {
return ^(NSInteger rows){
[self.sectionRows insertObject:[NSNumber numberWithInteger:rows] atIndex:self.currentSection];
return self;
};
}
- (JKTableViewHelper *(^)(NSArray *))configureCell {
return ^(NSArray *models) {
self.models = models;
return self;
};
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.sections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger i = 0;
for (NSNumber *num in self.sectionRows) {
if (section == i) {
return num.integerValue;
}
i++;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(self.Cls) forIndexPath:indexPath];
cell.textLabel.text = self.models[indexPath.row];
return cell;
}
- (void)dealloc {
NSLog(@"==%@", NSStringFromSelector(_cmd));
}
@end
@implementation JKViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[tableView makeConfigure:^(JKTableViewHelper *helper) {
helper.bindTb(tableView, [UITableViewCell class]).totalSection(1).section(0).row(10).configureCell(@[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]);
}];
[self.view addSubview:tableView];
}
@end
其实swift中的链式编程要容易实现的多,毕竟可以放肆的点起来。