Swift-函数式编程与面向协议编程

1,021 阅读9分钟

学习整理了下关于Swift函数式编程与面向协议编程的一些知识点, Swift真的是奥妙无穷啊, 总会有无限的可能等待被发现挖掘!

函数式编程(FP)

/// 把接受两个参数的函数, 变为接受一个参数
let num = 1
func add(_ v: Int) -> (Int) -> Int{
  reutrn {
    $0 + v
  }
}
let fn = add(3)
fn(num)
//简写: add(3)(num) //函数式编程的特征, 先接受一个参数, 再接受一个参数
print(fn(5), fn(100), fn(1000))

/// 函数合成
let fn1 = add(3)
let fn2 = multiple(5)
let fn3 = sub(1)
let fn4 = mod(10)
let fn5 = divide(2)

// 假设要实现以下功能: [(num + 3) * 5 - 1] % 10 / 2
//print(fn5(fn4(fn3(fn2(fn1(num))))))
func composite(_ f1: @escaping (Int) -> Int, f2: @escaping (Int) -> Int) -> (Int) -> Int{
  return {
    f2(f1($0))
  }
}
let fn = composite(add(3), multiple(5))
print(fn(num))//20

//对上面👆优化:
infix operator >>> : AdditionPrecedence //定义>>> 使它跟加法差不多
/*
func >>>(_ f1: @escaping (Int) -> Int, f2: @escaping (Int) -> Int) -> (Int) -> Int{
  return {
    f2(f1($0))
  }
}
*/
func >>><A, B, C>(_ f1: @escaping (A) -> B, f2: @escaping (B) -> C) -> (A) -> C {
  return {
    f2(f1($0))
  }
}
let fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2)
print(fn(num))//20

一、函数式编程(FP)-高阶函数

高阶函数至少满足下面的一个条件的函数:
接受一个或多个函数作为输入(mapfilterreduce等)
返回一个函数

二、函数式编程(FP) - 柯里化(Currying)

什么是柯里化? 将一个接受多参数的函数变换为一系列只接受单个参数的函数.
ArrayOptional的map函数就是柯里化函数.

给定任何一个函数都能柯里化, 比如下面👇
func add1(_ v1: Int, _ v2: Int) -> Int {v1 + v2}
add1(10, 20)
//柯里化后的效果: add3(20)(10)
func add3(_ v: Int) -> (Int) -> Int { { $0 + v } }

func add2(_ v1: Int, _ v2: Int, _ v3: Int) -> Int { v1 + v2 + v3 }
add2(10, 20, 30)
//柯里化后的效果: add4(30)(20)(10) 
func add4(_ v3: Int) -> (Int) -> (Int) -> Int { 
  return { v2 in //v2 == 20
      return { v1 in // v1 == 10
        return v1 + v2 + v3
      }
  } 
}


/// 柯里化最终版本: 传一个两个参数的函数过来, 自动柯里化
func currying<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) ->(A) -> C{
  return { b in
    return { a in
      return fn(a, b)
    }
  }
}
curring(add1)(20)(10)//使用效果
//对上面👆简化:
prefix func ~<A, B, C>(_ fn: @escaping(A, B)-> C) ->(B) ->(A) -> C {
  {b in { a in fn(a, b) } }
}
print((~sub)(20)(10))


infix operator >>> : AdditionPrecedence
func >>> <A, B, C>(_ f1: @escaping (A) -> B,
                  _ f2: @escaping(B) -> C)
	-> (A) -> C { { f2(f1($0)) } }
let fn = (~add)(3) >>> (~multiple)(5) >>> (~sub)(1) >>> (~mod)(10) >>> (~divide)(2)
print(fn(1))

/// 柯里化最终版本: 传一个三个参数的函数过来, 实现自动柯里化
prefix func ~<A, B, C, D>(_ fn: @escaping(A, B, C)-> D) ->(C) ->(B) ->(A) -> D {
  {c in {b in { a in fn(a, b, c) } } }
}
print((~add2)(30)(20)(10))

三、函数式编程-函子(Functor)

ArrayOptional这样支持map运算的类型, 称为函子(Functor).

// Array<Element>
public func map<T>(_ transform: (Element) ->T) -> Array<T>
// Optional<Wrapped>
public func map<U>(_ transform: (Wrapped) -> U) -> Optional<U>

//怎么样的Type才能称为函子(Functor)?
func map<T>(_ fn: (Inner) -> T) > Type<T>//支持这个运算格式的就是函子

适用函子

四、函数式编程-适用函子(Applicative Functor)

对任意一个函子F, 如果能支持一下运算, 该函子就是一个适用函子
func pure<A>(_ value: A) -> F<A>
func <*><A, B>(fn: F<(A)->B>, value: F<A>) -> F<B>

五、函数式编程-单子(Monad)

对任意一个类型F, 如果能支持一下运算, 那么就可以称为是一个单子(Monad)
func pure<A>(_ value: A) -> F<A>
func flatMap<A, B>(_ value: F<A>, _ fn:(A) -> F<B>) ->F<B>

很显然Array和Optional是单子

面向协议编程(POP)

面向协议编程(Protocol Oriented Programming, 简称POP)
  是Swift的一种编程范式, Apple于2015年WWDC提出Swift的标准库中, 能见到大量POP的影子

同时, Swift也是一门面向对象的编程语言(Object Oriented Programming, 简称OOP)
	在Swift开发中, OOP和POP相辅相成的

OOP的三大特性: 封装继承多态
继承的经典使用场景:
	当多个类(比如ABC类)具有很多共性时, 可以将这些共性抽取到一个父类中(比如D类),最后ABC都继承D类
	
OOP的不足:
比如👇如何将BVCDVC的公共方法run抽取出来?
class  BVC: UIViewController {
  func run() {
    print("run")
  }
}
class DVC: UITableViewController {
  func run() {
    print("run")
  }
}
//基于OOP想到的一些解决方案?❌
1.将run方法放到另一个对象A中, 然后BVCDVC拥有对象A属性
	💣多了一些额外的一类关系
2.将run方法增加到UIViewController分类中
	💣UIVIewController会越来越臃肿, 而且会影响它的其他所有子类
3.将run方法抽取到新的父类, 采用多继承? (C++支持多继承)
	💣会增加程序设计的复杂度, 产生菱形继承等问题, 需要开发者额外解决
//POP的解决方案:✅
protocol Runnable {
  func run()
}
extension Runnable { //Swift支持扩展协议的具体实现(类似Java的接口)
  func run() {
    print("run")
  }
}
class BVC: UIViewController, RunNable {}
class DVC: UITableViewController, Runnable {}

//POP的注意点
优先考虑创建协议, 而不是父类(基类)
优先考虑值类型( structenum), 而不是引用类型(class)
巧用协议的扩展功能
不要为了面向协议而使用协议

一、给类拓展功能

var str = "123rrr"
/// 需求: 查找字符串中有几个数字
//方法一: 通过扩展
extension String{
	var mj_numberCount: Int {//计算属性
    var count = 0
    for c in self where ("0"..."9").contains(c){
      count += 1
    }
    return count
  }
}
print(str.mj_numberCount)

/// 对上面👆进行优化升级, 效果: print(str.mj.numberCount)
var str = "123rrr"
struct MJ {
    var string: String
    init(_ string: String){//持有外部传入的参数
        self.string = string
    }

    var numberCount: Int {//扩展功能方法
        var count = 0
        for c in string where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension String{
    var mj: MJ { return MJ(self) }
}
print("123ooo999".mj.numberCount)

/// 对上面👆进行优化升级, 不能只支持String类型, 让它更通用
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
extension String{
    var mj: MJ<String> { return MJ(self) }
}

class Person {}
extension Person{
    var mj: MJ<Person> { return MJ(self) }
}
//扩展的是对象方法
extension MJ where Base == String { //此处是计算属性, 本质是一个方法
    var numberCount: Int {//扩展功能方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension MJ where Base: Person {
    func run() {//扩展功能方法
        print("跑起来了")
    }
}

print("12kkk999".mj.numberCount)
Person().mj.run()

/// 对上面👆进行优化升级, 支持拓展类方法. 效果String.mj.test()
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
//sp2.让想扩充方法的类型, 扩充前缀属性
extension String{
    var mj: MJ<String> { MJ(self) }//返回实例属性
    static var mj: MJ<String>.Type { return MJ<String>.self } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
//sp3.给前缀扩展方法
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
String.mj.test()

/// 对上面👆sp2进行优化升级, 利用协议protocol扩展前缀属性
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}

//sp2.利用协议protocol扩展前缀属性
protocol MJCompatible {} //协议里面只能声明一些东西,想给协议扩展一些东西的话要用extension
extension MJCompatible{//扩展协议
    var mj: MJ<Self> { MJ(self) }//返回实例属性
    static var mj: MJ<Self>.Type { MJ<Self>.self } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
//以后谁想给前缀扩展这个功能, 就让让它遵守这个协议
extension String: MJCompatible{} //让String拥有mj前缀属性

//sp3.给String.mj、String().mj前缀扩展功能
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
String.mj.test()

/// 对上面👆进行优化升级, 支持扩展mutating功能 ✅
//sp1.前缀类型
struct MJ<Base> {//使用泛型增加通用性
    var base: Base
    init(_ base: Base){//持有外部传入的参数
        self.base = base
    }
}
//sp2.利用协议protocol扩展前缀属性
protocol MJCompatible {} //协议里面只能声明一些东西,想给协议扩展一些东西的话要用extension
extension MJCompatible{//扩展协议
    var mj: MJ<Self> {//不要返回只读的计算属性, 为了以后扩展mutating, 换成普通计算属性
        set {} //为了mutating语法能够通过
        get { MJ(self) }
    }
    static var mj: MJ<Self>.Type {
        set {}
        get { MJ<Self>.self }
    } // static属性用类是可以访问出来的, 返回类型属性
    //注意: xx类.self 的类型是 xx类.Type. 比如上面 MJ.self 的类型是 MJ.Type
}
extension String: MJCompatible{} //让String遵守这个协议
//sp3.给String.mj、String().mj前缀扩展功能
extension MJ where Base == String {
    var numberCount: Int {//扩展实例方法
        var count = 0
        for c in base where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    mutating func run(){//mutating作用是让MJ(结构体)实例是可修改的
        //切记常量是不可修改的, 不要用常量调用mutating方法
        print("执行了mutating方法")
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}
var str = "123321"
str.mj.run()

//注意: 父类扩展了功能, 子类也会拥有这些功能.

二、给NSString、String、NSMutableString同时扩展方法

//NSString、String、NSMutableString共同点: 因为它们都遵循ExpressibleByStringLiteral这个协议,才能用字符串字面量进行初始化.
struct MJ<Base> {
    var base: Base
    init(_ base: Base){
        self.base = base
    }
}
protocol MJCompatible {}
extension MJCompatible{
    var mj: MJ<Self> {
        set {}
        get { MJ(self) }
    }
    static var mj: MJ<Self>.Type {
        set {}
        get { MJ<Self>.self }
    }
}
extension String: MJCompatible{}
extension NSString: MJCompatible{}//NSString遵守了和这个协议, 它的子类NSMutableString也就遵守了这个协议

//代表我这个泛型遵守ExpressibleByStringLiteral这个协议的, 都可以用numberCount方法
extension MJ where Base: ExpressibleByStringLiteral {//Base:后面能放协议、结构体、类型、枚举
    var numberCount: Int {
//        // 只要是能通过字符串字面量初始化的, 都是能转成Swift的String的
//        // 能够通过字符串字面量初始化的只有NSString、String、NSMutableString
//        var string = base as! String
        var count = 0
        for c in (base as! String) where ("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    mutating func run(){//mutating作用是让MJ(结构体)实例是可修改的
        //切记常量是不可修改的, 不要用常量调用mutating方法
        print("执行了mutating方法")
    }
    
    static func test(){//扩展类方法
        print("执行了类方法")
    }
}

var str1 = "123321"
var str2: NSString = "123xxx"
var str3: NSMutableString = "123xxx"
print(str1.mj.numberCount)
print(str2.mj.numberCount)
print(str3.mj.numberCount)

三、利用协议实现类型判断

需求1: 判断传入的实例对象是否为数组.
func isArray(_ value: Any)->Bool{
//        return value is Array<Any>
    return value is [Any] //对上面👆的简写: Any表示数组里可以装入任何东西
}

print(isArray([1, 2]))
print(isArray(["1", 2]))
print(isArray(NSArray())) //OC的类型都是能直接桥接Swift里对应的类型, 例如: NSArray() as Array
print(isArray(NSMutableArray()))
print(isArray("23123"))

需求2: 判断传入的类型是否是数组类型
// 一般判断某个类型是否是数组类型,这么判断:
let type = NSArray.self
print(type is NSArray.Type
      
//实例中, 判断传入的类型是否是数组类型
//sp1.定义一个协议,让所有数组类型都遵守这个协议
protocol ArrayType {}
extension Array: ArrayType{}
extension NSArray: ArrayType{}
//sp2.判断类型是否是协议的type
func isArrayType(_ type: Any.Type) ->Bool {
//        type is [Any].Type//这样写无法正确判断出数组类型, 因为[Any].Type和[Int].type等不是一个东西
    //解决方法: 使用协议来完成这个判断
    type is ArrayType.Type//协议最终就是一个具体类型
}
//sp3.实战中使用
print(isArrayType([Int].self))
print(isArrayType([Any].self))
print(isArrayType(NSArray.self))
print(isArrayType(NSMutableArray.self))
print(isArrayType(String.self))

//不管是枚举、协议、类、结构体, 都有.Type
//定义ttt这个变量, 表示遵守ArrayType协议的类, 都可以写过来
var ttt: ArrayType.Type //xxx.Type存放的是xxx.self
ttt = Array<Int>.self
ttt = NSArray.self
ttt = NSMutableArray.self
ttt = String.self //报错

小补充:

⚠️传递多个枚举值类型的参数(包装成数组): [xx,xx...]
func run(){
  addObserver(self, forKeyPath:"age", options: [.old,.new], context:nil)
}