前言

作為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(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中的鏈式編程要容易實現的多,畢竟可以放肆的點起來。

分享