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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容