iOS 中的链式编程、函数式编程入门

字数 1452阅读 314

对一个程序猿来说,从一开始接触编程后会逐渐经历好几个不同的编程思想。包括过程式编程、面向对象编程、响应式编程、函数式编程、链式编程等等。

过程式编程的特点是随着程序的编写逐步进行,写到哪儿运行到哪儿。

面向对象的特点是万物皆对象。很著名的例子:把大象放进冰箱。

响应式编程的特点是一方触发,多方响应。OC中的KVO、通知中心就是这种。触发者只负责触发,不理会结果。

函数式编程的特点是将函数作为一等公民,当作参数和返回值使用。典型的如OC和Swift 中的 map函数、fiflt函数、reduce函数等。每个函数的处理结果给到下一个函数,最后的结果由自身函数调出。

链式编程的特点是运用点语法将很多个函数串联起来。典型的例子是OC中的Masnary和Swift 中的Snpkit。

这篇文章主要介绍函数式编程和链式编程的实现

链式编程

链式编程的特点是使用点语法将对象的多个函数连起来调用。就像这样

    obj.func1(par).func2(par).func3(par)

以一个例子来实现。假设我需要做一个字符串处理的扩展,其中包涵拼接字符删除最后一个字符修改最后一个字符这三个功能。在OC和Swift 中,这些函数都有现成,但是我想它们能够联合起来调用。假设我已经给这三个函数分别去名字叫:addString, removeLastString,alterLastString.而我想像下面这样调用:

  NSString *str = @"abcdefg";
  NSString *newStr = str. addString(@"123").removeLastString().add(@"555"). alter LastString("*")
  NSLog(@"%@",newStr );

最后输出的结果是:abcdefg1255*.这个应该很容易理解了。
接下来我分别使用OC和Swift来实现。

在OC中(链式编程)

新建一个工具类StringManegeTool,这个类包涵了一个初始化的函数:initWithString以及一个启动函数doSomething.并且包含了若干个block做为属性,以便使用点语法。

//
//  StringManegeTool.h
//  funTest
//
//  Created by JD on 2017/11/4.
//  Copyright © 2017年 JD. All rights reserved.
//

#import <Foundation/Foundation.h>
@class StringManegeTool;

typedef StringManegeTool* (^TESTBlock1)(NSString*);
typedef StringManegeTool* (^TESTBlock2)(void);

@interface StringManegeTool: NSObject

- (instancetype)initWithString:(NSString*)str;

- (NSString*)doSomething:(void (^)(StringManegeTool*))maker;

@property (nonatomic,copy) TESTBlock1 addString;

@property (nonatomic,copy) TESTBlock2 removeLastString;

@property (nonatomic,copy) TESTBlock1 alertLastString;


@end

.m文件中实现。在初始化方法中实现了block,并且在doSomthing中实现了调用block。

//
//  StringManegeTool.m
//  funTest
//
//  Created by JD on 2017/11/4.
//  Copyright © 2017年 JD. All rights reserved.
//

#import "StringManegeTool.h"
#import <Foundation/Foundation.h>

@interface StringManegeTool ()

@property(nonatomic,strong) NSMutableString* buffStr;

@end

@implementation StringManegeTool

- (instancetype)initWithString:(NSString*)str{
    self = [super init];
    if (self) {
        self.buffStr = [[NSMutableString alloc] initWithString:str];
        __weak StringManegeTool *weakSelf = self;
        self.addString = ^StringManegeTool *(NSString *str) {
            [weakSelf.buffStr appendString:str];
            return  weakSelf;
        };
        
        self.removeLastString = ^StringManegeTool *{
            [weakSelf.buffStr deleteCharactersInRange:NSMakeRange(weakSelf.buffStr.length-1, 1)];
            return  weakSelf;
        };
        
        self.alertLastString = ^StringManegeTool *(NSString *str) {
            [weakSelf.buffStr replaceCharactersInRange:NSMakeRange(weakSelf.buffStr.length-1, 1) withString:str];
            return weakSelf;
        };
    }
    return self;
}


- (NSString*)doSomething:(void (^)(StringManegeTool*))maker{
    
    maker(self);
    return self.buffStr;
}

@end

接下来我可以这样调用:

 StringManegeTool *tool = [[StringManegeTool alloc] initWithString:@"abcdefg"];  
 NSString *newStr = [tool doSomething:^(StringManegeTool *make) {
        make.addString(@"123").removeLastString().addString(@"555").alertLastString(@"*");
    }];
    
 NSLog(@"%@",newStr);

打印结果
//2017-11-04 15:36:08.495 funTest[1661:121049] abcdefg1255*

回忆一下Masnary。熟悉的味道!

再来看看Swift中如何实现链式编程
//
//  StringManegeToolInSwift.swift
//  funTest
//
//  Created by JD on 2017/11/4.
//  Copyright © 2017年 JD. All rights reserved.
//

import UIKit

class StringManegeToolInSwift: NSObject {

    var bufStr:String = String()
    
    convenience init(_ withString:String){
        self.init();
        self.bufStr = withString
    }
    
    func doSomething(_ make: (StringManegeToolInSwift)->())->String{
        make(self)
        return self.bufStr
    }
    
    func addString(_ string:String)->StringManegeToolInSwift{
        self.bufStr.append(string)
        return self
    }
    
    func remoLastString()-> StringManegeToolInSwift{
        let end  = self.bufStr.endIndex
        let start = self.bufStr.index(before: end)
        
        self.bufStr.removeSubrange(start..<end)
        return self
    }
    
    func alertLastString(_ string:String) -> StringManegeToolInSwift{
        let end  = self.bufStr.endIndex
        let start = self.bufStr.index(before: end)

        self.bufStr.replaceSubrange(start..<end, with: string)
        return self
    }
    
    /// 调用
    class func actionDo(){
        let tool = StringManegeToolInSwift.init("abcdefg")
        
        let newString = tool.doSomething { (maker) in
            maker.addString("123").remoLastString().addString("555").alertLastString("*")
        }        
        print(newString)
    }
//打印:
         abcdefg1255*
}

说实话,Swift写起来要简单太多了。

总结一下:iOS中,链式编程的特点是调用返回值为自身的函数或者Block。再结合map等函数的映射转换,这样可以使得代码变的非常灵活。

函数式编程

我之前一直把函数式编程理解为高阶函数,其实这就是高阶函数。先看一个很典型的例子,照样,我们从CO开始。

    NSArray<NSString*> *arr = @[@"252",@"55541",@"2295",@"21"];  
    arr = [arr sortedArrayUsingComparator:^NSComparisonResult(NSString*  _Nonnull obj1, NSString*   _Nonnull obj2) {
        return obj1.length < obj2.length;
    }];
    NSLog(@"%@",arr);
打印:2017-11-04 16:41:22.846 funTest[2478:150288] (
    252,
    55541,
    2295,
    21
)

这是一个数组排序的函数。我们经常把在函数中使用函数作为参数称作高阶函数(这实际上就是函数式编程的一种特质)。上面的排序函数中,我们将两个数组内的元素比较的结果拿来使用给我本体函数,实现排序的目的。下面这个函数可能回更加复杂一点:

 NSString *result = [self testWithStr:@"adcf" One:^BOOL(NSString *str) {
        return [str isEqualToString:@"111"];
    } two:^NSString *(BOOL abool) {
        return abool?@"相等":@"不相等";
    }];
    NSLog(@"%@",result);

    //打印: 
    2017-11-08 09:24:49.011487+0800 funTest[7487:1779986] 不相等

它是一个字符判断函数,在第一个参数中,我们输入想要比较的字符,在第一个block中进行比较并返回一个bool值,这个bool值将使用在第二个block的参数中,第二个block是根据bool值返回需要比较的两个字符是否相等的描述。
那具体是怎么实现的呢? 根据这个函数的特点,第一个block的调用结果将被用在第二个block上,对于第二个block来讲,他的参数本身就不是固定的,而是由第一个block决定,由此我们应该能想到,第二个block的参数其实就是第一个block才对。实现如下:

- (NSString *)testWithStr:(NSString *)str  One: (BOOL(^)(NSString* str))one two:(NSString*(^)(BOOL abool))Two{
    return Two(one(str));
}

如果是Swift,其实是及其相似的:

func testWith(Str:String, one:(String)->Bool,two:(Bool)->String) -> String {
        return two(one(Str))
    }
//调用
self.testWith(Str: "abc", one: { (str) -> Bool in
            return str == "123"
        }) { (abool) -> String in
            return abool?"相等":"不相等"
        }

这里的确是函数式编程, 但是我们发现其中多了东西:虽然这里将一些函数作为另一函数的参数,但是,这些函数的返回值和参数都具有一定的关联,没错,上面的函数中,block one的返回值的类型恰恰是block two的参数类型。他们有一些必然的联系。这就涉及到了响应式编程的范畴,这里其实算是响应式函数编程,不在本文的范畴之内,有兴趣的可以自行了解,本文不做介绍.
但是无可厚非的是,这里我们可以看到函数式编程的特点:将函数做为参数来使用,这就使得函数本身具备更大的灵活性,通过参数函数适应不同的应用场景。
将函数配合泛型,又能继续扩大函数的灵活性。下面修改一下上面的例子看看泛型中的函数式编程:

func testWith<T:Comparable>(_ obj:T, one:(T)->Bool,two:(Bool)->String) -> String {
        return two(one(obj))
    }

调用

 let result = tool.testWith("abc", one: { (str) -> Bool in
            return str == "123"
        }) { (abool) -> String in
            guard abool else{
               return "不相等"
            }
            return "相等"
        }
        
        print(result)
        
        let result2 = tool.testWith(5, one: { (int) -> Bool in
            return int == 5
        }) { (abool) -> String in
            guard abool else{
                return "不相等"
            }
            return "相等"
        }
        
        print(result2)

以上就是函数式编程结合泛型。

提一下我们经常听到的RAC,这是一个响应式函数编程,同时也会使用到泛型。 关于响应式编程, 我会在以后的文章中写出,同时将包含RAC的知识。

推荐阅读更多精彩内容