Jun 6

在oc中实践链式语法编程

Lrdcq , 2016/06/06 21:32 , 程序 , 閱讀(2845) , Via 本站原創
链式编程,形如instance.do1().do2().do3().dolast();大家都知道是怎么实现的,即每个实例方法的返回值均为this,以保障可以继续进行方法调用。这样的编程方式对比较连贯的操作中,能有效提升代码可读性,使用起来也非常舒服。在比较复杂的链式编程,比如rxjava,参数包涵lambda表达式或者其它复杂的运算的时候,我们为了代码的可读性,一般会对每个方法进行提行编写,比如下面这段java代码:
FPHttp.call()
.userInfo(String.valueOf(user.id), user.token)
.map(rider -> {
    rider.data.uid = user.id;
    RiderModel.getInstance().saveRider(rider.data);
    return rider.data;
})
.map(rider -> rider != null)
.distinctUntilChanged()
.subscribe(hasRider);

多次对数据进行筛选,操作,变换一气呵成,代码排版清晰流畅,非常漂亮。所以我们现在要在oc上也用链式编程吧!不过到写oc的时候,我们就会发现一个特别特别蛋疼的事儿——oc的方法调用好奇怪啊,oc写调用一串方法,就会变成这样:[[[[instance do1] do2] do3] dolast];还能不能看啊!如果要提行的话,还有一格的自动缩进,就会变成这样:
[[[[[[FPHttp call]
     userInfo:[String valueOf:user.id], user.token]
    map:^(id rider) {
        rider.data.uid = user.id;
        [[RiderModel getInstance] saveRider:rider.data];
        return rider.data;
    }]
   map:^(id rider) {return rider != nil;}]
  distinctUntilChanged]
 subscribe:hasRider];
 

什么鬼,别说流畅清晰了,这逆天的缩进简直没法看嘛。因此,我们需要别的方法来实现长的复杂的链式编程。

正确的方法

那么,有没有办法写看起来比较正常的链式编程呢?我们想到我们常用的第三方库Masonry,在使用Masonry的过程中,对约束的操作就是看起来很舒服的链式的,形如:make.width.equalTo(self.view).offset(10);的代码。明明oc中并没有.method()这样的方法语法,那这是什么。稍微阅读一下源码,就恍然大悟了。
我们可以看到,比如Masonry的equalTo方法,它的原型是这样的东西:
- (MASConstraint * (^)(id attr))equalTo;

喔,原来是一个没有参数的方法,利用语法特性,比如[object copy]方法可以直接object.copy,它和一个实例属性并没有区别(当然,也可以直接定义为实例属性),既然是属性,当然就可以通过点语法访问,它也会自动调用对应的getter方法了。再看它的返回值,是一个(MASConstraint * (^)(id attr))的block,也就是是返回参数为id attr,返回值为实例对象的lambda表达式块。而整好,oc中的block就是用括号运行的。这样的两步一组合,就有了我们看到的.method()样式语法了。比如拆拆看Masonry的单步掉用equalTo,是这样的:
MASConstraint *instance = make.top;
//一般写法
instance.equalTo(self.view);
//分解写法
MASConstraint* (^lambda)(id) = instance.equalTo;
MASConstraint *instanceNext = lambda(self.view);

这样就很清晰了。那么明白到底是怎么构成的,我们可以尝试写每一个链式调用的method的实现方法了。抽象一下我们定义的方法,我们定义的方法和它最简单的实现是:
- (instanceType (^)(inputType input, ...))methodName;

- (instanceType (^)(inputType input, ...))methodName {
    return ^instanceType (inputType input, ...) {
        //do something
        return self;
    };
}

其中的要点并不多,主要有:
1.一定要返回实例对象或者其它可以进行链式操作的对象,除非这是链式操作的重点(比如toast的show())。
2.一般代码的主要内容是写在lambda表达式里边的,不过如果有需要的话,写在getter方法的return前面也可以的,利用block强持有和闭包的特性,在外面的运算数据可以让lambda持有的(务必注意循环引用问题)
3.在定义的时候,入参的input名是可以不写的,比如- (instanceType (^)(inputType, ...))methodName;也挺常见的,同时如果没有入参的话,写一个void或者id在那儿都可以。
4.复杂的入参类型。如果入参数还是一个block的话,直接定义的类型定义起来会十分复杂并且可读性很差。对于block嵌套的或者block类型多次出现的情况,强烈建议把block定义为自定义类型,例如:
typedef outputType (^BlockType) (inputType);
- (instanceType (^)(BlockType, ...))methodName;

这样方法的数据结构定义就会清晰很多。

最后,还会有一些坑。最大的一个坑是在oc中,对一个nil发送消息,即正常调用方法是不会导致crash的,然而现在链式编程是用调用属性实现的,对一个nil调用属性,就会崩溃。所以在编写链式过程中,要保障每次返回的self有效,链条起始点不能为nil。另外,xcode的断点和各种测试与warm信息似乎会错行,也是一些不太好的地方,请务必注意。
logo