Swift中instance method是curried函数

概述

最近看到Ole Begemann的文章,先把要点列一下:

An instance method in Swift is just a type method that takes the instance as an argument and returns a function which will then be applied to the instance.

意思是说,实例方法是一种类型方法,该类型方法的参数是该类型实例,返回作用于该实例的函数。
如果有ObjC经验的,会将type method和ObjC的类方法联系在一起——并且,官方文档上也是这么类比的。但是,却没有指出instance和type method这种"本质"的联系。

Curried function 柯里化函数

柯里化函数是将多参数的函数转化为单个参数(首个参数)的函数,并返回接受剩余参数且返回结果的新函数。举个SwiftProgrammingLanguage中的例子:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementByTwenty = makeIncrementer(forIncrement: 20)

Instance Methods 实例方法本质

先把oleb中的代码挪过来:

class BankAccount {
    var balance: Double = 0.0
    func deposit(amount: Double) {
        balance += amount
    }
}
//1
let account = BankAccount()
account.deposit(100) // balance is now 100
//2
let depositor = BankAccount.deposit
depositor(account)(100) // balance is now 200
//3
BankAccount.deposit(account)(100) // balance is now 300

1是我们通常使用instance method的场景,2中则是先定义了一个function,该函数以BankAccount实例为参数,返回一个带参数的新函数。其中,depositor的类型是: BankAccount -> (Double) -> ()。 3则是2的直接调用形式。

Target-Action的Swift实现

Target-Action是iOS/Cocoa开发中最常见的模式。ObjC时代,该模式的实现依赖于ObjC的runtime特性。在Swift中,使用selector需要添加@objc关键字。下面采用curried function的概念, 实现一个纯Swift版本的target-action。

protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper<T: AnyObject> : TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()

    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case TouchUpInside
    case ValueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction]()

    func setTarget<T: AnyObject>(target: T, action: @escaping (T) -> () -> (), controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }

    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }

    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}

class MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
    }

    func onButtonTap() {
        println("Button was tapped")
    }
}

注意,escaping必不可少,因为closure作为一个参数,并且该closure在函数返回后才会被执行——这也就是escaping closure。

最后,文中代码都可以在Xcode8.2.1, Swift3正确运行。

补充说明

另外,查阅 SwiftProgrammingLanguage 修改历史, 发现在1.1版本中引入的curried function描述,在2.2版本中移除了。