iOSiOS Developer程序员

iOS中的链式编程

2017-05-24  本文已影响730人  01_Jack

前言

作为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中的链式编程要容易实现的多,毕竟可以放肆的点起来。

上一篇下一篇

猜你喜欢

热点阅读